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>
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
;
22 fopen (const char *filename
,
25 static FILE * (*real_fopen
) (const char *, const char *);
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
;
44 dconf_engine_change_notify (DConfEngine
*engine
,
46 const gchar
* const *changes
,
52 g_string_append_printf (change_log
, "%s:%d:%s:%s;",
53 prefix
, g_strv_length ((gchar
**) changes
), changes
[0],
58 verify_and_free (DConfEngineSource
**sources
,
60 const gchar
* const *expected_names
,
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
]);
79 test_five_times (const gchar
*filename
,
83 const gchar
**expected_names
;
84 DConfEngineSource
**sources
;
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
*);
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
);
138 test_profile_parser (void)
140 DConfEngineSource
**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
);
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
);
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
);
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,
184 "verylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongnameverylongname",
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]);
205 dconf_mock_shm_reset ();
209 test_signal_threadsafety_worker (gpointer user_data
)
211 gint
*finished
= user_data
;
214 for (i
= 0; i
< 20000; i
++)
218 engine
= dconf_engine_new (NULL
, NULL
);
219 dconf_engine_unref (engine
);
222 g_atomic_int_inc (finished
);
228 test_signal_threadsafety (void)
231 GVariant
*parameters
;
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
,
244 "/ca/desrt/dconf/Writer/user",
245 "Notify", parameters
);
246 g_variant_unref (parameters
);
248 dconf_mock_shm_reset ();
252 test_user_source (void)
254 DConfEngineSource
*source
;
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
);
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
);
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
);
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
);
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
);
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
;
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
,
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 ("()");
383 g_set_error_literal (error
, G_FILE_ERROR
, G_FILE_ERROR_NOENT
, "Unknown DB type");
390 test_service_source (void)
392 DConfEngineSource
*source
;
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
);
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
);
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
440 dconf_engine_source_free (source
);
441 source
= dconf_engine_source_new ("service-db:shm/nil");
442 reopened
= dconf_engine_source_refresh (source
);
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
);
457 /* ...and we should find it to be empty */
458 g_assert (!gvdb_table_has_value (source
->values
, "/values/int32"));
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
;
471 test_system_source (void)
473 DConfEngineSource
*source
;
474 GvdbTable
*first_table
;
475 GvdbTable
*next_table
;
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
);
503 g_assert (source
->values
!= NULL
);
505 dconf_engine_source_free (source
);
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
);
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
);
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
);
555 invalidate_state (guint n_sources
,
561 for (i
= 0; i
< n_sources
; i
++)
562 if (source_types
& (1u << i
))
566 dconf_mock_gvdb_table_invalidate (state
[i
]);
567 gvdb_table_free (state
[i
]);
572 dconf_mock_shm_flag (state
[i
]);
578 setup_state (guint n_sources
,
580 guint database_state
,
585 for (i
= 0; i
< n_sources
; i
++)
587 guint contents
= database_state
% 7;
588 GvdbTable
*table
= NULL
;
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 */
604 locks
= dconf_mock_gvdb_table_new ();
606 /* Numbers above 4 get the lock set */
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
))
619 state
[i
] = dconf_mock_gvdb_table_ref (table
);
624 filename
= g_strdup_printf ("/etc/dconf/db/db%d", i
);
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
);
642 create_profile (const gchar
*filename
,
646 GError
*error
= NULL
;
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
);
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];
664 check_read (DConfEngine
*engine
,
667 guint database_state
)
669 gboolean any_values
= FALSE
;
670 gboolean any_locks
= FALSE
;
671 guint first_contents
;
672 gint underlying
= -1;
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
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
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 */
705 /* Locks in the first database don't count... */
711 /* A value here should be read */
712 if (contents
&& !(contents
& 1))
714 if (i
!= 0 && underlying
== -1)
727 value
= dconf_engine_read (engine
, NULL
, "/value");
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
);
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
;
753 /* If writable, see what our changeset did.
756 * 1: reset value (should see underlying value)
757 * 2: set value to 123
760 our_expected
= underlying
;
761 else if ((i
% 3) == 2)
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
);
774 g_assert (value
== NULL
);
778 g_strfreev (dconf_engine_list (engine
, "/", &n
));
779 list
= dconf_engine_list (engine
, "/", NULL
);
780 g_assert_cmpint (g_strv_length (list
), ==, n
);
783 g_assert_cmpstr (list
[0], ==, "value");
784 g_assert (list
[1] == NULL
);
787 g_assert (list
[0] == NULL
);
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");
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
);
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:
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. */
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);
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);
858 g_variant_unref (value
);
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");
873 fatal_handler (const gchar
*log_domain
,
874 GLogLevelFlags log_level
,
875 const gchar
*message
,
878 return !is_expected (log_domain
, log_level
, message
);
882 normal_handler (const gchar
*log_domain
,
883 GLogLevelFlags log_level
,
884 const gchar
*message
,
887 if (!is_expected (log_domain
, log_level
, message
))
888 g_error ("unexpected error: %s\n", message
);
894 #define MAX_N_SOURCES 2
895 gpointer state
[MAX_N_SOURCES
];
896 gchar
*profile_filename
;
897 GError
*error
= NULL
;
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
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 * ----------------------------------------------------
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
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
982 * ' ' - empty changeset
983 * 's' - set value to 123
985 * 'x' - set value to 321
987 const gchar
*queue_configs
[] = {
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
];
1001 for (j
= 0; conf
[j
]; j
++)
1003 DConfChangeset
*changeset
;
1005 changeset
= dconf_changeset_new ();
1012 dconf_changeset_set (changeset
, "/value", NULL
);
1015 dconf_changeset_set (changeset
, "/value", g_variant_new_uint32 (123));
1018 dconf_changeset_set (changeset
, "/value", g_variant_new_uint32 (321));
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 */
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
));
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
);
1087 test_watch_fast (void)
1089 DConfEngine
*engine
;
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
);
1154 g_variant_unref (triv
);
1157 static const gchar
*match_request_type
;
1158 static gboolean got_match_request
[5];
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
,
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 ("()");
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
;
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
,
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
;
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
;
1248 GError
*error
= NULL
;
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
);
1274 success
= dconf_engine_change_sync (engine
, empty
, NULL
, &error
);
1275 g_assert_no_error (error
);
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
);
1299 g_assert_cmpstr (tag
, ==, "mytag");
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 ();