tests: fix a small lie
[dconf.git] / tests / engine.c
blob8908dfebe6a5dc862b84fb52007963ef679aa8f2
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-error.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 gpointer origin_tag,
49 gpointer user_data)
51 if (change_log)
52 g_string_append_printf (change_log, "%s:%d:%s:%s;",
53 prefix, g_strv_length ((gchar **) changes), changes[0],
54 tag ? tag : "nil");
57 static void
58 verify_and_free (DConfEngineSource **sources,
59 gint n_sources,
60 const gchar * const *expected_names,
61 gint n_expected)
63 gint i;
65 g_assert_cmpint (n_sources, ==, n_expected);
67 g_assert ((sources == NULL) == (n_sources == 0));
69 for (i = 0; i < n_sources; i++)
71 g_assert_cmpstr (sources[i]->name, ==, expected_names[i]);
72 dconf_engine_source_free (sources[i]);
75 g_free (sources);
78 static void
79 test_five_times (const gchar *filename,
80 gint n_expected,
81 ...)
83 const gchar **expected_names;
84 DConfEngineSource **sources;
85 gint n_sources;
86 va_list ap;
87 gint i;
89 expected_names = g_new (const gchar *, n_expected);
90 va_start (ap, n_expected);
91 for (i = 0; i < n_expected; i++)
92 expected_names[i] = va_arg (ap, const gchar *);
93 va_end (ap);
95 /* first try by supplying the profile filename via the API */
96 g_assert (g_getenv ("DCONF_PROFILE") == NULL);
97 g_assert (filename_to_replace == NULL);
98 sources = dconf_engine_profile_open (filename, &n_sources);
99 verify_and_free (sources, n_sources, expected_names, n_expected);
101 /* next try supplying it via the environment */
102 g_setenv ("DCONF_PROFILE", filename, TRUE);
103 g_assert (filename_to_replace == NULL);
104 sources = dconf_engine_profile_open (NULL, &n_sources);
105 verify_and_free (sources, n_sources, expected_names, n_expected);
106 g_unsetenv ("DCONF_PROFILE");
108 /* next try supplying a profile name via API and intercepting fopen */
109 filename_to_replace = "/etc/dconf/profile/myprofile";
110 filename_to_replace_it_with = filename;
111 g_assert (g_getenv ("DCONF_PROFILE") == NULL);
112 sources = dconf_engine_profile_open ("myprofile", &n_sources);
113 verify_and_free (sources, n_sources, expected_names, n_expected);
114 filename_to_replace = NULL;
116 /* next try the same, via the environment */
117 g_setenv ("DCONF_PROFILE", "myprofile", TRUE);
118 filename_to_replace = "/etc/dconf/profile/myprofile";
119 filename_to_replace_it_with = filename;
120 sources = dconf_engine_profile_open (NULL, &n_sources);
121 verify_and_free (sources, n_sources, expected_names, n_expected);
122 g_unsetenv ("DCONF_PROFILE");
123 filename_to_replace = NULL;
125 /* next try to have dconf pick it up as the default user profile */
126 filename_to_replace = "/etc/dconf/profile/user";
127 filename_to_replace_it_with = filename;
128 g_assert (g_getenv ("DCONF_PROFILE") == NULL);
129 sources = dconf_engine_profile_open (NULL, &n_sources);
130 verify_and_free (sources, n_sources, expected_names, n_expected);
131 filename_to_replace = NULL;
133 filename_to_replace_it_with = NULL;
134 g_free (expected_names);
137 static void
138 test_profile_parser (void)
140 DConfEngineSource **sources;
141 gint n_sources;
143 if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
145 g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
147 sources = dconf_engine_profile_open (SRCDIR "/profile/this-file-does-not-exist", &n_sources);
148 g_assert_cmpint (n_sources, ==, 0);
149 g_assert (sources == NULL);
150 exit (0);
152 g_test_trap_assert_passed ();
153 g_test_trap_assert_stderr ("*WARNING*: unable to open named profile*");
155 if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
157 g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
159 sources = dconf_engine_profile_open (SRCDIR "/profile/broken-profile", &n_sources);
160 g_assert_cmpint (n_sources, ==, 0);
161 g_assert (sources == NULL);
162 exit (0);
164 g_test_trap_assert_passed ();
165 g_test_trap_assert_stderr ("*WARNING*: unknown dconf database*unknown dconf database*");
167 if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
169 g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
171 sources = dconf_engine_profile_open (SRCDIR "/profile/gdm", &n_sources);
172 g_assert_cmpint (n_sources, ==, 0);
173 g_assert (sources == NULL);
174 exit (0);
176 g_test_trap_assert_passed ();
177 g_test_trap_assert_stderr ("*WARNING*: unknown dconf database*unknown dconf database*");
179 test_five_times (SRCDIR "/profile/empty-profile", 0);
180 test_five_times (SRCDIR "/profile/test-profile", 1, "test");
181 test_five_times (SRCDIR "/profile/colourful", 4,
182 "user",
183 "other",
184 "verylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongname",
185 "nonewline");
186 test_five_times (SRCDIR "/profile/dos", 2, "user", "site");
187 test_five_times (SRCDIR "/profile/no-newline-longline", 0);
188 test_five_times (SRCDIR "/profile/many-sources", 10,
189 "user", "local", "room", "floor", "building",
190 "site", "region", "division", "country", "global");
192 /* finally, test that we get the default profile if the user profile
193 * file cannot be located and we do not specify another profile.
195 filename_to_replace = "/etc/dconf/profile/user";
196 filename_to_replace_it_with = SRCDIR "/profile/this-file-does-not-exist";
197 g_assert (g_getenv ("DCONF_PROFILE") == NULL);
198 sources = dconf_engine_profile_open (NULL, &n_sources);
199 filename_to_replace = NULL;
200 g_assert_cmpint (n_sources, ==, 1);
201 g_assert_cmpstr (sources[0]->name, ==, "user");
202 dconf_engine_source_free (sources[0]);
203 g_free (sources);
205 dconf_mock_shm_reset ();
208 static gpointer
209 test_signal_threadsafety_worker (gpointer user_data)
211 gint *finished = user_data;
212 gint i;
214 for (i = 0; i < 20000; i++)
216 DConfEngine *engine;
218 engine = dconf_engine_new (NULL, NULL);
219 dconf_engine_unref (engine);
222 g_atomic_int_inc (finished);
224 return NULL;
227 static void
228 test_signal_threadsafety (void)
230 #define N_WORKERS 4
231 GVariant *parameters;
232 gint finished = 0;
233 gint i;
235 parameters = g_variant_new_parsed ("('/test/key', [''], 'tag')");
236 g_variant_ref_sink (parameters);
238 for (i = 0; i < N_WORKERS; i++)
239 g_thread_unref (g_thread_new ("testcase worker", test_signal_threadsafety_worker, &finished));
241 while (g_atomic_int_get (&finished) < N_WORKERS)
242 dconf_engine_handle_dbus_signal (G_BUS_TYPE_SESSION,
243 ":1.2.3",
244 "/ca/desrt/dconf/Writer/user",
245 "Notify", parameters);
246 g_variant_unref (parameters);
248 dconf_mock_shm_reset ();
251 static void
252 test_user_source (void)
254 DConfEngineSource *source;
255 GvdbTable *table;
256 GvdbTable *locks;
257 gboolean reopened;
259 /* Create the source from a clean slate */
260 source = dconf_engine_source_new ("user-db:user");
261 g_assert (source != NULL);
262 g_assert (source->values == NULL);
263 g_assert (source->locks == NULL);
265 /* Refresh it the first time.
266 * This should cause it to open the shm.
267 * FALSE should be returned because there is no database file.
269 reopened = dconf_engine_source_refresh (source);
270 g_assert (!reopened);
271 dconf_mock_shm_assert_log ("open user;");
273 /* Try to refresh it. There must be no IO at this point. */
274 reopened = dconf_engine_source_refresh (source);
275 g_assert (!reopened);
276 dconf_mock_shm_assert_log ("");
278 /* Add a real database. */
279 table = dconf_mock_gvdb_table_new ();
280 dconf_mock_gvdb_table_insert (table, "/values/int32", g_variant_new_int32 (123456), NULL);
281 dconf_mock_gvdb_install ("/HOME/.config/dconf/user", table);
283 /* Try to refresh it again.
284 * Because we didn't flag the change there must still be no IO.
286 reopened = dconf_engine_source_refresh (source);
287 g_assert (!reopened);
288 g_assert (source->values == NULL);
289 g_assert (source->locks == NULL);
290 dconf_mock_shm_assert_log ("");
292 /* Now flag it and reopen. */
293 dconf_mock_shm_flag ("user");
294 reopened = dconf_engine_source_refresh (source);
295 g_assert (reopened);
296 g_assert (source->values != NULL);
297 g_assert (source->locks == NULL);
298 g_assert (gvdb_table_has_value (source->values, "/values/int32"));
299 dconf_mock_shm_assert_log ("close;open user;");
301 /* Do it again -- should get the same result, after some IO */
302 dconf_mock_shm_flag ("user");
303 reopened = dconf_engine_source_refresh (source);
304 g_assert (reopened);
305 g_assert (source->values != NULL);
306 g_assert (source->locks == NULL);
307 dconf_mock_shm_assert_log ("close;open user;");
309 /* "Delete" the gvdb and make sure dconf notices after a flag */
310 dconf_mock_gvdb_install ("/HOME/.config/dconf/user", NULL);
311 dconf_mock_shm_flag ("user");
312 reopened = dconf_engine_source_refresh (source);
313 g_assert (reopened);
314 g_assert (source->values == NULL);
315 g_assert (source->locks == NULL);
316 dconf_mock_shm_assert_log ("close;open user;");
318 /* Add a gvdb with a lock */
319 table = dconf_mock_gvdb_table_new ();
320 locks = dconf_mock_gvdb_table_new ();
321 dconf_mock_gvdb_table_insert (table, "/values/int32", g_variant_new_int32 (123456), NULL);
322 dconf_mock_gvdb_table_insert (locks, "/values/int32", g_variant_new_boolean (TRUE), NULL);
323 dconf_mock_gvdb_table_insert (table, ".locks", NULL, locks);
324 dconf_mock_gvdb_install ("/HOME/.config/dconf/user", table);
326 /* Reopen and check if we have the lock */
327 dconf_mock_shm_flag ("user");
328 reopened = dconf_engine_source_refresh (source);
329 g_assert (reopened);
330 g_assert (source->values != NULL);
331 g_assert (source->locks != NULL);
332 g_assert (gvdb_table_has_value (source->values, "/values/int32"));
333 g_assert (gvdb_table_has_value (source->locks, "/values/int32"));
334 dconf_mock_shm_assert_log ("close;open user;");
336 /* Reopen one last time */
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 dconf_mock_shm_assert_log ("close;open user;");
344 dconf_engine_source_free (source);
345 dconf_mock_shm_assert_log ("close;");
347 dconf_mock_gvdb_install ("/HOME/.config/dconf/user", NULL);
348 dconf_mock_shm_reset ();
351 static gboolean service_db_created;
352 static GvdbTable *service_db_table;
354 static GVariant *
355 handle_service_request (GBusType bus_type,
356 const gchar *bus_name,
357 const gchar *object_path,
358 const gchar *interface_name,
359 const gchar *method_name,
360 GVariant *parameters,
361 const GVariantType *expected_type,
362 GError **error)
364 g_assert_cmpstr (bus_name, ==, "ca.desrt.dconf");
365 g_assert_cmpstr (interface_name, ==, "ca.desrt.dconf.Writer");
366 g_assert_cmpstr (method_name, ==, "Init");
367 g_assert_cmpstr (g_variant_get_type_string (parameters), ==, "()");
369 if (g_str_equal (object_path, "/ca/desrt/dconf/shm/nil"))
371 service_db_table = dconf_mock_gvdb_table_new ();
372 dconf_mock_gvdb_table_insert (service_db_table, "/values/int32", g_variant_new_int32 (123456), NULL);
373 dconf_mock_gvdb_install ("/RUNTIME/dconf-service/shm/nil", service_db_table);
375 /* Make sure this only happens the first time... */
376 g_assert (!service_db_created);
377 service_db_created = TRUE;
379 return g_variant_new ("()");
381 else
383 g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_NOENT, "Unknown DB type");
384 return NULL;
389 static void
390 test_service_source (void)
392 DConfEngineSource *source;
393 gboolean reopened;
395 /* Make sure we deal with errors from the service sensibly */
396 if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
398 g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
400 source = dconf_engine_source_new ("service-db:unknown/nil");
401 dconf_mock_dbus_sync_call_handler = handle_service_request;
402 g_assert (source != NULL);
403 g_assert (source->values == NULL);
404 g_assert (source->locks == NULL);
405 reopened = dconf_engine_source_refresh (source);
407 exit (0);
409 g_test_trap_assert_passed ();
410 g_test_trap_assert_stderr ("*WARNING*: unable to open file*unknown/nil*expect degraded performance*");
412 /* Set up one that will work */
413 source = dconf_engine_source_new ("service-db:shm/nil");
414 g_assert (source != NULL);
415 g_assert (source->values == NULL);
416 g_assert (source->locks == NULL);
418 /* Refresh it the first time.
420 * This should cause the service to be asked to create it.
422 * This should return TRUE because we just opened it.
424 dconf_mock_dbus_sync_call_handler = handle_service_request;
425 reopened = dconf_engine_source_refresh (source);
426 dconf_mock_dbus_sync_call_handler = NULL;
427 g_assert (service_db_created);
428 g_assert (reopened);
430 /* After that, a refresh should be a no-op. */
431 reopened = dconf_engine_source_refresh (source);
432 g_assert (!reopened);
434 /* Close it and reopen it, ensuring that we don't hit the service
435 * again (because the file already exists).
437 * Note: dconf_mock_dbus_sync_call_handler = NULL, so D-Bus calls will
438 * assert.
440 dconf_engine_source_free (source);
441 source = dconf_engine_source_new ("service-db:shm/nil");
442 reopened = dconf_engine_source_refresh (source);
443 g_assert (reopened);
445 /* Make sure it has the content we expect to see */
446 g_assert (gvdb_table_has_value (source->values, "/values/int32"));
448 /* Now invalidate it and replace it with an empty one */
449 dconf_mock_gvdb_table_invalidate (service_db_table);
450 service_db_table = dconf_mock_gvdb_table_new ();
451 dconf_mock_gvdb_install ("/RUNTIME/dconf-service/shm/nil", service_db_table);
453 /* Now reopening should get the new one */
454 reopened = dconf_engine_source_refresh (source);
455 g_assert (reopened);
457 /* ...and we should find it to be empty */
458 g_assert (!gvdb_table_has_value (source->values, "/values/int32"));
460 /* We're done. */
461 dconf_engine_source_free (source);
463 /* This should not have done any shm... */
464 dconf_mock_shm_assert_log ("");
466 dconf_mock_gvdb_install ("/RUNTIME/dconf-service/shm/nil", NULL);
467 service_db_table = NULL;
470 static void
471 test_system_source (void)
473 DConfEngineSource *source;
474 GvdbTable *first_table;
475 GvdbTable *next_table;
476 gboolean reopened;
478 source = dconf_engine_source_new ("system-db:site");
479 g_assert (source != NULL);
481 /* Check to see that we get the warning about the missing file. */
482 if (g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
484 g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
486 /* Failing to open should return FALSE from refresh */
487 reopened = dconf_engine_source_refresh (source);
488 g_assert (!reopened);
489 g_assert (source->values == NULL);
491 /* Attempt the reopen to make sure we don't get two warnings.
492 * We should see FALSE again since we go from NULL to NULL.
494 reopened = dconf_engine_source_refresh (source);
495 g_assert (!reopened);
497 /* Create the file after the fact and make sure it opens properly */
498 first_table = dconf_mock_gvdb_table_new ();
499 dconf_mock_gvdb_install ("/etc/dconf/db/site", first_table);
501 reopened = dconf_engine_source_refresh (source);
502 g_assert (reopened);
503 g_assert (source->values != NULL);
505 dconf_engine_source_free (source);
507 exit (0);
509 g_test_trap_assert_passed ();
510 /* Check that we only saw the warning, but only one time. */
511 g_test_trap_assert_stderr ("*this gvdb does not exist; expect degraded performance*");
512 g_test_trap_assert_stderr_unmatched ("*degraded*degraded*");
514 /* Create the file before the first refresh attempt */
515 first_table = dconf_mock_gvdb_table_new ();
516 dconf_mock_gvdb_install ("/etc/dconf/db/site", first_table);
517 /* Hang on to a copy for ourselves for below... */
518 dconf_mock_gvdb_table_ref (first_table);
520 /* See that we get the database. */
521 reopened = dconf_engine_source_refresh (source);
522 g_assert (reopened);
523 g_assert (source->values == first_table);
525 /* Do a refresh, make sure there is no change. */
526 reopened = dconf_engine_source_refresh (source);
527 g_assert (!reopened);
528 g_assert (source->values == first_table);
530 /* Replace the table on "disk" but don't invalidate the old one */
531 next_table = dconf_mock_gvdb_table_new ();
532 dconf_mock_gvdb_install ("/etc/dconf/db/site", next_table);
534 /* Make sure the old table remains open (ie: no IO performed) */
535 reopened = dconf_engine_source_refresh (source);
536 g_assert (!reopened);
537 g_assert (source->values == first_table);
539 /* Now mark the first table invalid and reopen */
540 dconf_mock_gvdb_table_invalidate (first_table);
541 gvdb_table_free (first_table);
542 reopened = dconf_engine_source_refresh (source);
543 g_assert (reopened);
544 g_assert (source->values == next_table);
546 /* Remove the file entirely and do the same thing */
547 dconf_mock_gvdb_install ("/etc/dconf/db/site", NULL);
548 reopened = dconf_engine_source_refresh (source);
549 g_assert (!reopened);
551 dconf_engine_source_free (source);
554 static void
555 invalidate_state (guint n_sources,
556 guint source_types,
557 gpointer *state)
559 gint i;
561 for (i = 0; i < n_sources; i++)
562 if (source_types & (1u << i))
564 if (state[i])
566 dconf_mock_gvdb_table_invalidate (state[i]);
567 gvdb_table_free (state[i]);
570 else
572 dconf_mock_shm_flag (state[i]);
573 g_free (state[i]);
577 static void
578 setup_state (guint n_sources,
579 guint source_types,
580 guint database_state,
581 gpointer *state)
583 gint i;
585 for (i = 0; i < n_sources; i++)
587 guint contents = database_state % 7;
588 GvdbTable *table = NULL;
589 gchar *filename;
591 if (contents)
593 table = dconf_mock_gvdb_table_new ();
595 /* Even numbers get the value setup */
596 if ((contents & 1) == 0)
597 dconf_mock_gvdb_table_insert (table, "/value", g_variant_new_uint32 (i), NULL);
599 /* Numbers above 2 get the locks table */
600 if (contents > 2)
602 GvdbTable *locks;
604 locks = dconf_mock_gvdb_table_new ();
606 /* Numbers above 4 get the lock set */
607 if (contents > 4)
608 dconf_mock_gvdb_table_insert (locks, "/value", g_variant_new_boolean (TRUE), NULL);
610 dconf_mock_gvdb_table_insert (table, ".locks", NULL, locks);
614 if (source_types & (1u << i))
616 if (state)
618 if (table)
619 state[i] = dconf_mock_gvdb_table_ref (table);
620 else
621 state[i] = NULL;
624 filename = g_strdup_printf ("/etc/dconf/db/db%d", i);
626 else
628 if (state)
629 state[i] = g_strdup_printf ("db%d", i);
631 filename = g_strdup_printf ("/HOME/.config/dconf/db%d", i);
634 dconf_mock_gvdb_install (filename, table);
635 g_free (filename);
637 database_state /= 7;
641 static void
642 create_profile (const gchar *filename,
643 guint n_sources,
644 guint source_types)
646 GError *error = NULL;
647 GString *profile;
648 gint i;
650 profile = g_string_new (NULL);
651 for (i = 0; i < n_sources; i++)
652 if (source_types & (1u << i))
653 g_string_append_printf (profile, "system-db:db%d\n", i);
654 else
655 g_string_append_printf (profile, "user-db:db%d\n", i);
656 g_file_set_contents (filename, profile->str, profile->len, &error);
657 g_assert_no_error (error);
658 g_string_free (profile, TRUE);
661 static GQueue read_through_queues[12];
663 static void
664 check_read (DConfEngine *engine,
665 guint n_sources,
666 guint source_types,
667 guint database_state)
669 gboolean any_values = FALSE;
670 gboolean any_locks = FALSE;
671 guint first_contents;
672 gint underlying = -1;
673 gint expected = -1;
674 gboolean writable;
675 GVariant *value;
676 gchar **list;
677 guint i;
678 gint n;
680 /* The value we expect to read is number of the first source that has
681 * the value set (ie: odd digit in database_state) up to the lowest
682 * level lock.
684 * We go over each database. If 'expected' has not yet been set and
685 * we find that we should have a value in this database, we set it.
686 * If we find that we should have a lock in this database, we unset
687 * any previous values (since they should not have been written).
689 * We intentionally code this loop in a different way than the one in
690 * dconf itself is currently implemented...
692 * We also take note of if we saw any locks and cross-check that with
693 * dconf_engine_is_writable(). We check if we saw and values at all
694 * and cross-check that with dconf_engine_list() (which ignores
695 * locks).
697 first_contents = database_state % 7;
698 for (i = 0; i < n_sources; i++)
700 guint contents = database_state % 7;
702 /* A lock here should prevent higher reads */
703 if (contents > 4)
705 /* Locks in the first database don't count... */
706 if (i != 0)
707 any_locks = TRUE;
708 expected = -1;
711 /* A value here should be read */
712 if (contents && !(contents & 1))
714 if (i != 0 && underlying == -1)
715 underlying = i;
717 if (expected == -1)
719 any_values = TRUE;
720 expected = i;
724 database_state /= 7;
727 value = dconf_engine_read (engine, NULL, "/value");
729 if (expected != -1)
731 g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32));
732 g_assert_cmpint (g_variant_get_uint32 (value), ==, expected);
733 g_variant_unref (value);
735 else
736 g_assert (value == NULL);
738 /* We are writable if the first database is a user database and we
739 * didn't encounter any locks...
741 writable = dconf_engine_is_writable (engine, "/value");
742 g_assert_cmpint (writable, ==, n_sources && !(source_types & 1) && !any_locks);
744 /* Check various read-through scenarios. Read-through should only be
745 * effective if the database is writable.
747 for (i = 0; i < G_N_ELEMENTS (read_through_queues); i++)
749 gint our_expected = expected;
751 if (writable)
753 /* If writable, see what our changeset did.
755 * 0: nothing
756 * 1: reset value (should see underlying value)
757 * 2: set value to 123
759 if ((i % 3) == 1)
760 our_expected = underlying;
761 else if ((i % 3) == 2)
762 our_expected = 123;
765 value = dconf_engine_read (engine, &read_through_queues[i], "/value");
767 if (our_expected != -1)
769 g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32));
770 g_assert_cmpint (g_variant_get_uint32 (value), ==, our_expected);
771 g_variant_unref (value);
773 else
774 g_assert (value == NULL);
777 /* Check listing */
778 g_strfreev (dconf_engine_list (engine, "/", &n));
779 list = dconf_engine_list (engine, "/", NULL);
780 g_assert_cmpint (g_strv_length (list), ==, n);
781 if (any_values)
783 g_assert_cmpstr (list[0], ==, "value");
784 g_assert (list[1] == NULL);
786 else
787 g_assert (list[0] == NULL);
788 g_strfreev (list);
790 /* Check the user value.
792 * This should be set only in the case that the first database is a
793 * user database (ie: writable) and the contents of that database are
794 * set (ie: 2, 4 or 6). See the table in the comment below.
796 * Note: we do not consider locks.
798 value = dconf_engine_read_user_value (engine, NULL, "/value");
799 if (value)
801 g_assert (first_contents && !(first_contents & 1) && !(source_types & 1));
802 g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32));
803 g_assert_cmpint (g_variant_get_uint32 (value), ==, 0);
804 g_variant_unref (value);
806 else
808 /* Three possibilities for failure:
809 * - first db did not exist
810 * - value was missing from first db
811 * - first DB was system-db
813 g_assert (!first_contents || (first_contents & 1) || (source_types & 1));
816 /* Check read_through vs. user-value */
817 for (i = 0; i < G_N_ELEMENTS (read_through_queues); i++)
819 /* It is only possible here to see one of three possibilities:
821 * - NULL
822 * - 0 (value from user's DB)
823 * - 123 (value from queue)
825 * We see these values regardless of writability. We do however
826 * ensure that we have a writable database as the first one.
828 value = dconf_engine_read_user_value (engine, &read_through_queues[i], "/value");
830 /* If we have no first source, or the first source is non-user
831 * than we should always do nothing (since we can't queue changes
832 * against a system db or one that doesn't exist).
834 if (n_sources == 0 || (source_types & 1) || (i % 3) == 0)
836 /* Changeset did nothing, so it should be same as above. */
837 if (value)
839 g_assert (first_contents && !(first_contents & 1) && !(source_types & 1));
840 g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32));
841 g_assert_cmpint (g_variant_get_uint32 (value), ==, 0);
843 else
844 g_assert (!first_contents || (first_contents & 1) || (source_types & 1));
846 else if ((i % 3) == 1)
848 /* Changeset did a reset, so we should always see NULL */
849 g_assert (value == NULL);
851 else if ((i % 3) == 2)
853 /* Changeset set a value, so we should see it */
854 g_assert_cmpint (g_variant_get_uint32 (value), ==, 123);
857 if (value)
858 g_variant_unref (value);
862 static gboolean
863 is_expected (const gchar *log_domain,
864 GLogLevelFlags log_level,
865 const gchar *message)
867 return g_str_equal (log_domain, "dconf") &&
868 log_level == (G_LOG_LEVEL_WARNING | G_LOG_FLAG_FATAL) &&
869 strstr (message, "unable to open file '/etc/dconf/db");
872 static gboolean
873 fatal_handler (const gchar *log_domain,
874 GLogLevelFlags log_level,
875 const gchar *message,
876 gpointer user_data)
878 return !is_expected (log_domain, log_level, message);
881 static void
882 normal_handler (const gchar *log_domain,
883 GLogLevelFlags log_level,
884 const gchar *message,
885 gpointer user_data)
887 if (!is_expected (log_domain, log_level, message))
888 g_error ("unexpected error: %s\n", message);
891 static void
892 test_read (void)
894 #define MAX_N_SOURCES 2
895 gpointer state[MAX_N_SOURCES];
896 gchar *profile_filename;
897 GError *error = NULL;
898 DConfEngine *engine;
899 guint i, j, k;
900 guint n;
901 guint handler_id;
903 /* This test throws a lot of messages about missing databases.
904 * Capture and ignore them.
906 g_test_log_set_fatal_handler (fatal_handler, NULL);
907 handler_id = g_log_set_handler ("dconf", G_LOG_LEVEL_WARNING | G_LOG_FLAG_FATAL, normal_handler, NULL);
909 /* Our test strategy is as follows:
911 * We only test a single key name. It is assumed that gvdb is working
912 * properly already so we are only interested in interactions between
913 * multiple databases for a given key name.
915 * The outermost loop is over 'n'. This is how many sources are in
916 * our test. We test 0 to 3 (which should be enough to cover all
917 * 'interesting' possibilities). 4 takes too long to run (2*7*7 ~=
918 * 100 times as long as 3).
920 * The next loop is over 'i'. This goes from 0 to 2^n - 1, with each
921 * bit deciding the type of source of the i-th element
923 * 0: user
925 * 1: system
927 * The next loop is over 'j'. This goes from 0 to 7^n - 1, with each
928 * base-7 digit deciding the state of the database file associated
929 * with the i-th source:
931 * j file has value has ".locks" has lock
932 * ----------------------------------------------------
933 * 0 0 - - -
934 * 1 1 0 0 -
935 * 2 1 1 0 -
936 * 3 1 0 1 0
937 * 4 1 1 1 0
938 * 5 1 0 1 1
939 * 6 1 1 1 1
941 * Where 'file' is if the database file exists, 'has value' is if a
942 * value exists at '/value' within the file, 'has ".locks"' is if
943 * there is a ".locks" subtable and 'has lock' is if there is a lock
944 * for '/value' within that table.
946 * Finally, we loop over 'k' as a state to transition to ('k' works
947 * the same way as 'j').
949 * Once we know 'n' and 'i', we can write a profile file.
951 * Once we know 'j' we can setup the initial state, create the engine
952 * and check that we got the expected value. Then we transition to
953 * state 'k' and make sure everything still works as expected.
955 * Since we want to test all j->k transitions, we do the initial setup
956 * of the engine (according to j) inside of the 'k' loop, since we
957 * need to test all possible transitions from 'j'.
959 * We additionally test the effect of read-through queues in 4
960 * situations:
962 * - NULL: no queue
963 * - 0: queue with no effect
964 * - 1: queue that resets the value
965 * - 2: queue that sets the value to 123
967 * For the cases (0, 1, 2) we can have multiple types of queue that
968 * achieve the desired effect. We can put more than 3 items in
969 * read_through_queues -- the expected behaviour is dictated by the
970 * value of (i % 3) where i is the array index.
973 /* We use a scheme to set up each queue. Again, we assume that
974 * GHashTable is working OK, so we only bother having "/value" as a
975 * changeset item (or not).
977 * We have an array of strings, each string defining the
978 * configuration of one queue. In each string, each character
979 * represents the contents of a changeset within the queue, in
980 * order.
982 * ' ' - empty changeset
983 * 's' - set value to 123
984 * 'r' - reset value
985 * 'x' - set value to 321
987 const gchar *queue_configs[] = {
988 "", "r", "s",
989 " ", "rr", "ss",
990 " ", "rs", "sr",
991 " ", "rx", "sx"
993 gint i;
995 G_STATIC_ASSERT (G_N_ELEMENTS (queue_configs) == G_N_ELEMENTS (read_through_queues));
996 for (i = 0; i < G_N_ELEMENTS (read_through_queues); i++)
998 const gchar *conf = queue_configs[i];
999 gint j;
1001 for (j = 0; conf[j]; j++)
1003 DConfChangeset *changeset;
1005 changeset = dconf_changeset_new ();
1007 switch (conf[j])
1009 case ' ':
1010 break;
1011 case 'r':
1012 dconf_changeset_set (changeset, "/value", NULL);
1013 break;
1014 case 's':
1015 dconf_changeset_set (changeset, "/value", g_variant_new_uint32 (123));
1016 break;
1017 case 'x':
1018 dconf_changeset_set (changeset, "/value", g_variant_new_uint32 (321));
1019 break;
1020 default:
1021 g_assert_not_reached ();
1024 g_queue_push_head (&read_through_queues[i], changeset);
1029 /* We need a place to put the profile files we use for this test */
1030 close (g_file_open_tmp ("dconf-testcase.XXXXXX", &profile_filename, &error));
1031 g_assert_no_error (error);
1033 g_setenv ("DCONF_PROFILE", profile_filename, TRUE);
1035 for (n = 0; n <= MAX_N_SOURCES; n++)
1036 for (i = 0; i < pow (2, n); i++)
1038 gint n_possible_states = pow (7, n);
1040 /* Step 1: write out the profile file */
1041 create_profile (profile_filename, n, i);
1043 for (j = 0; j < n_possible_states; j++)
1044 for (k = 0; k < n_possible_states; k++)
1046 guint64 old_state, new_state;
1048 /* Step 2: setup the state */
1049 setup_state (n, i, j, (j != k) ? state : NULL);
1051 /* Step 3: create the engine */
1052 engine = dconf_engine_new (NULL, NULL);
1054 /* Step 4: read, and check result */
1055 check_read (engine, n, i, j);
1056 old_state = dconf_engine_get_state (engine);
1058 /* Step 5: change to the new state */
1059 if (j != k)
1061 setup_state (n, i, k, NULL);
1062 invalidate_state (n, i, state);
1065 /* Step 6: read, and check result */
1066 check_read (engine, n, i, k);
1067 new_state = dconf_engine_get_state (engine);
1069 g_assert ((j == k) == (new_state == old_state));
1071 /* Clean up */
1072 setup_state (n, i, 0, NULL);
1073 dconf_engine_unref (engine);
1077 /* Clean up the tempfile we were using... */
1078 g_unsetenv ("DCONF_PROFILE");
1079 g_unlink (profile_filename);
1080 g_free (profile_filename);
1081 dconf_mock_shm_reset ();
1083 g_log_remove_handler ("dconf", handler_id);
1086 static void
1087 test_watch_fast (void)
1089 DConfEngine *engine;
1090 GvdbTable *table;
1091 GVariant *triv;
1092 guint64 a, b;
1094 change_log = g_string_new (NULL);
1096 table = dconf_mock_gvdb_table_new ();
1097 dconf_mock_gvdb_install ("/HOME/.config/dconf/user", table);
1098 table = dconf_mock_gvdb_table_new ();
1099 dconf_mock_gvdb_install ("/etc/dconf/db/site", table);
1101 triv = g_variant_ref_sink (g_variant_new ("()"));
1103 g_setenv ("DCONF_PROFILE", SRCDIR "/profile/dos", TRUE);
1104 engine = dconf_engine_new (NULL, NULL);
1105 g_unsetenv ("DCONF_PROFILE");
1107 /* Check that establishing a watch works properly in the normal case.
1109 a = dconf_engine_get_state (engine);
1110 dconf_engine_watch_fast (engine, "/a/b/c");
1111 /* watches do not count as outstanding changes */
1112 g_assert (!dconf_engine_has_outstanding (engine));
1113 dconf_engine_sync (engine);
1114 b = dconf_engine_get_state (engine);
1115 g_assert_cmpuint (a, ==, b);
1116 /* both AddMatch results come back before shm is flagged */
1117 dconf_mock_dbus_async_reply (triv, NULL);
1118 dconf_mock_dbus_async_reply (triv, NULL);
1119 dconf_mock_dbus_assert_no_aync ();
1120 dconf_mock_shm_flag ("user");
1121 b = dconf_engine_get_state (engine);
1122 g_assert_cmpuint (a, !=, b);
1123 g_assert_cmpstr (change_log->str, ==, "");
1124 dconf_engine_unwatch_fast (engine, "/a/b/c");
1125 dconf_mock_dbus_async_reply (triv, NULL);
1126 dconf_mock_dbus_async_reply (triv, NULL);
1127 dconf_mock_dbus_assert_no_aync ();
1129 /* Establish a watch and fail the race. */
1130 a = dconf_engine_get_state (engine);
1131 dconf_engine_watch_fast (engine, "/a/b/c");
1132 g_assert (!dconf_engine_has_outstanding (engine));
1133 dconf_engine_sync (engine);
1134 b = dconf_engine_get_state (engine);
1135 g_assert_cmpuint (a, ==, b);
1136 /* one AddMatch result comes back -after- shm is flagged */
1137 dconf_mock_dbus_async_reply (triv, NULL);
1138 dconf_mock_shm_flag ("user");
1139 dconf_mock_dbus_async_reply (triv, NULL);
1140 dconf_mock_dbus_assert_no_aync ();
1141 b = dconf_engine_get_state (engine);
1142 g_assert_cmpuint (a, !=, b);
1143 g_assert_cmpstr (change_log->str, ==, "/:1::nil;");
1144 dconf_engine_unwatch_fast (engine, "/a/b/c");
1145 dconf_mock_dbus_async_reply (triv, NULL);
1146 dconf_mock_dbus_async_reply (triv, NULL);
1147 dconf_mock_dbus_assert_no_aync ();
1149 dconf_mock_gvdb_install ("/HOME/.config/dconf/user", NULL);
1150 dconf_mock_gvdb_install ("/etc/dconf/db/site", NULL);
1151 dconf_engine_unref (engine);
1152 g_string_free (change_log, TRUE);
1153 change_log = NULL;
1154 g_variant_unref (triv);
1157 static const gchar *match_request_type;
1158 static gboolean got_match_request[5];
1160 static GVariant *
1161 handle_match_request (GBusType bus_type,
1162 const gchar *bus_name,
1163 const gchar *object_path,
1164 const gchar *interface_name,
1165 const gchar *method_name,
1166 GVariant *parameters,
1167 const GVariantType *expected_type,
1168 GError **error)
1170 const gchar *match_rule;
1172 g_assert_cmpstr (bus_name, ==, "org.freedesktop.DBus");
1173 /* any object path works... */
1174 g_assert_cmpstr (interface_name, ==, "org.freedesktop.DBus");
1175 g_assert_cmpstr (method_name, ==, match_request_type);
1176 g_assert_cmpstr (g_variant_get_type_string (parameters), ==, "(s)");
1177 g_variant_get (parameters, "(&s)", &match_rule);
1178 g_assert (strstr (match_rule, "arg0path='/a/b/c'"));
1179 g_assert (!got_match_request[bus_type]);
1180 got_match_request[bus_type] = TRUE;
1182 return g_variant_new ("()");
1185 static void
1186 test_watch_sync (void)
1188 DConfEngine *engine;
1190 dconf_mock_dbus_sync_call_handler = handle_match_request;
1192 g_setenv ("DCONF_PROFILE", SRCDIR "/profile/dos", TRUE);
1193 engine = dconf_engine_new (NULL, NULL);
1194 g_unsetenv ("DCONF_PROFILE");
1196 match_request_type = "AddMatch";
1197 dconf_engine_watch_sync (engine, "/a/b/c");
1198 g_assert (got_match_request[G_BUS_TYPE_SESSION]);
1199 g_assert (got_match_request[G_BUS_TYPE_SYSTEM]);
1200 got_match_request[G_BUS_TYPE_SESSION] = FALSE;
1201 got_match_request[G_BUS_TYPE_SYSTEM] = FALSE;
1203 match_request_type = "RemoveMatch";
1204 dconf_engine_unwatch_sync (engine, "/a/b/c");
1205 g_assert (got_match_request[G_BUS_TYPE_SESSION]);
1206 g_assert (got_match_request[G_BUS_TYPE_SYSTEM]);
1207 got_match_request[G_BUS_TYPE_SESSION] = FALSE;
1208 got_match_request[G_BUS_TYPE_SYSTEM] = FALSE;
1210 dconf_engine_unref (engine);
1212 dconf_mock_dbus_sync_call_handler = NULL;
1213 match_request_type = NULL;
1216 static GError *change_sync_error;
1217 static GVariant *change_sync_result;
1219 static GVariant *
1220 handle_write_request (GBusType bus_type,
1221 const gchar *bus_name,
1222 const gchar *object_path,
1223 const gchar *interface_name,
1224 const gchar *method_name,
1225 GVariant *parameters,
1226 const GVariantType *expected_type,
1227 GError **error)
1229 g_assert_cmpstr (bus_name, ==, "ca.desrt.dconf");
1230 g_assert_cmpstr (interface_name, ==, "ca.desrt.dconf.Writer");
1232 /* Assume that the engine can format the method call properly, but
1233 * test that it can properly handle weird replies.
1236 *error = change_sync_error;
1237 return change_sync_result;
1241 static void
1242 test_change_sync (void)
1244 DConfChangeset *empty, *good_write, *bad_write, *very_good_write, *slightly_bad_write;
1245 GvdbTable *table, *locks;
1246 DConfEngine *engine;
1247 gboolean success;
1248 GError *error = NULL;
1249 gchar *tag;
1251 table = dconf_mock_gvdb_table_new ();
1252 locks = dconf_mock_gvdb_table_new ();
1253 dconf_mock_gvdb_table_insert (locks, "/locked", g_variant_new_boolean (TRUE), NULL);
1254 dconf_mock_gvdb_table_insert (table, ".locks", NULL, locks);
1255 dconf_mock_gvdb_install ("/etc/dconf/db/site", table);
1257 empty = dconf_changeset_new ();
1258 good_write = dconf_changeset_new_write ("/value", g_variant_new_string ("value"));
1259 bad_write = dconf_changeset_new_write ("/locked", g_variant_new_string ("value"));
1260 very_good_write = dconf_changeset_new_write ("/value", g_variant_new_string ("value"));
1261 dconf_changeset_set (very_good_write, "/to-reset", NULL);
1262 slightly_bad_write = dconf_changeset_new_write ("/locked", g_variant_new_string ("value"));
1263 dconf_changeset_set (slightly_bad_write, "/to-reset", NULL);
1265 g_setenv ("DCONF_PROFILE", SRCDIR "/profile/dos", TRUE);
1266 engine = dconf_engine_new (NULL, NULL);
1267 g_unsetenv ("DCONF_PROFILE");
1269 success = dconf_engine_change_sync (engine, empty, &tag, &error);
1270 g_assert_no_error (error);
1271 g_assert (success);
1272 g_free (tag);
1274 success = dconf_engine_change_sync (engine, empty, NULL, &error);
1275 g_assert_no_error (error);
1276 g_assert (success);
1278 success = dconf_engine_change_sync (engine, bad_write, &tag, &error);
1279 g_assert_error (error, DCONF_ERROR, DCONF_ERROR_NOT_WRITABLE);
1280 g_clear_error (&error);
1281 g_assert (!success);
1283 success = dconf_engine_change_sync (engine, slightly_bad_write, NULL, &error);
1284 g_assert_error (error, DCONF_ERROR, DCONF_ERROR_NOT_WRITABLE);
1285 g_clear_error (&error);
1286 g_assert (!success);
1288 /* Up to now, no D-Bus traffic should have been sent at all because we
1289 * only had trivial and non-writable attempts.
1291 * Now try some working cases
1293 dconf_mock_dbus_sync_call_handler = handle_write_request;
1294 change_sync_result = g_variant_new ("(s)", "mytag");
1296 success = dconf_engine_change_sync (engine, good_write, &tag, &error);
1297 g_assert_no_error (error);
1298 g_assert (success);
1299 g_assert_cmpstr (tag, ==, "mytag");
1300 g_free (tag);
1301 change_sync_result = NULL;
1303 change_sync_error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_NOENT, "something failed");
1304 success = dconf_engine_change_sync (engine, very_good_write, &tag, &error);
1305 g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_NOENT);
1306 g_assert (!success);
1307 g_clear_error (&error);
1308 change_sync_error = NULL;
1312 main (int argc, char **argv)
1314 g_setenv ("XDG_RUNTIME_DIR", "/RUNTIME/", TRUE);
1315 g_setenv ("XDG_CONFIG_HOME", "/HOME/.config", TRUE);
1316 g_unsetenv ("DCONF_PROFILE");
1318 main_thread = g_thread_self ();
1320 g_test_init (&argc, &argv, NULL);
1322 g_test_add_func ("/engine/profile-parser", test_profile_parser);
1323 g_test_add_func ("/engine/signal-threadsafety", test_signal_threadsafety);
1324 g_test_add_func ("/engine/sources/user", test_user_source);
1325 g_test_add_func ("/engine/sources/system", test_system_source);
1326 g_test_add_func ("/engine/sources/service", test_service_source);
1327 g_test_add_func ("/engine/read", test_read);
1328 g_test_add_func ("/engine/watch/fast", test_watch_fast);
1329 g_test_add_func ("/engine/watch/sync", test_watch_sync);
1330 g_test_add_func ("/engine/write/sync", test_change_sync);
1332 return g_test_run ();