summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Lortie <desrt@desrt.ca>2012-07-15 19:54:19 -0400
committerRyan Lortie <desrt@desrt.ca>2012-07-15 19:54:19 -0400
commit294003a9aec3a47c9fc2aff97f92d84a1f4f143d (patch)
tree2ed87c1d41770b1c1ab301191f728b1a9440fc6a
parent0c9bee1cc0963f6c57dc8f7ffe9e86f216f6ac13 (diff)
downloaddconf-294003a9aec3a47c9fc2aff97f92d84a1f4f143d.tar.gz
tests/: test reading from various profile setups
Add a testcase that tests the engine by reading from an exhaustive combination of different profile types and states (missing databases, empty databases, databases with values, databases with locks, etc). Among other things, this makes sure the lockdown logic is sane. This is the testcase that caught the bug fixed in the last commit (listing with a missing database file).
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/dconf-mock-gvdb.c16
-rw-r--r--tests/engine.c318
3 files changed, 330 insertions, 6 deletions
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 361b55e..f37b363 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -84,7 +84,7 @@ engine_LDADD = \
../common/libdconf-common.a \
libdconf-mock.a \
$(glib_LIBS) \
- -ldl
+ -ldl -lm
engine_SOURCES = engine.c
EXTRA_DIST += \
profile/broken-profile \
diff --git a/tests/dconf-mock-gvdb.c b/tests/dconf-mock-gvdb.c
index cb639e3..6bc6d14 100644
--- a/tests/dconf-mock-gvdb.c
+++ b/tests/dconf-mock-gvdb.c
@@ -147,19 +147,25 @@ GVariant *
gvdb_table_get_value (GvdbTable *table,
const gchar *key)
{
- GHashTable *hash_table = (GHashTable *) table;
DConfMockGvdbItem *item;
- item = g_hash_table_lookup (hash_table, key);
+ item = g_hash_table_lookup (table->table, key);
- return item ? g_variant_ref (item->value) : NULL;
+ return (item && item->value) ? g_variant_ref (item->value) : NULL;
}
gchar **
-gvdb_table_list (GvdbTable *table,
+gvdb_table_list (GvdbTable *table,
const gchar *key)
{
- g_assert_not_reached ();
+ const gchar * const result[] = { "value", NULL };
+
+ g_assert_cmpstr (key, ==, "/");
+
+ if (!gvdb_table_has_value (table, "/value"))
+ return NULL;
+
+ return g_strdupv ((gchar **) result);
}
GvdbTable *
diff --git a/tests/engine.c b/tests/engine.c
index 7e4ea9c..6e06ddd 100644
--- a/tests/engine.c
+++ b/tests/engine.c
@@ -2,10 +2,12 @@
#include "../engine/dconf-engine.h"
#include "../engine/dconf-engine-profile.h"
+#include <glib/gstdio.h>
#include "dconf-mock.h"
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
+#include <math.h>
/* Interpose to catch fopen("/etc/dconf/profile/user") */
static const gchar *filename_to_replace;
@@ -409,6 +411,321 @@ test_system_source (void)
dconf_engine_source_free (source);
}
+static void
+invalidate_state (guint n_sources,
+ guint source_types,
+ gpointer *state)
+{
+ gint i;
+
+ for (i = 0; i < n_sources; i++)
+ if (source_types & (1u << i))
+ {
+ if (state[i])
+ {
+ dconf_mock_gvdb_table_invalidate (state[i]);
+ gvdb_table_unref (state[i]);
+ }
+ }
+ else
+ {
+ dconf_mock_shm_flag (state[i]);
+ g_free (state[i]);
+ }
+}
+
+static void
+setup_state (guint n_sources,
+ guint source_types,
+ guint database_state,
+ gpointer *state)
+{
+ gint i;
+
+ for (i = 0; i < n_sources; i++)
+ {
+ guint contents = database_state % 7;
+ GvdbTable *table = NULL;
+ gchar *filename;
+
+ if (contents)
+ {
+ table = dconf_mock_gvdb_table_new ();
+
+ /* Even numbers get the value setup */
+ if ((contents & 1) == 0)
+ dconf_mock_gvdb_table_insert (table, "/value", g_variant_new_uint32 (i), NULL);
+
+ /* Numbers above 2 get the locks table */
+ if (contents > 2)
+ {
+ GvdbTable *locks;
+
+ locks = dconf_mock_gvdb_table_new ();
+
+ /* Numbers above 4 get the lock set */
+ if (contents > 4)
+ dconf_mock_gvdb_table_insert (locks, "/value", g_variant_new_boolean (TRUE), NULL);
+
+ dconf_mock_gvdb_table_insert (table, ".locks", NULL, locks);
+ }
+ }
+
+ if (source_types & (1u << i))
+ {
+ if (state)
+ {
+ if (table)
+ state[i] = gvdb_table_ref (table);
+ else
+ state[i] = NULL;
+ }
+
+ filename = g_strdup_printf ("/etc/dconf/db/db%d", i);
+ }
+ else
+ {
+ if (state)
+ state[i] = g_strdup_printf ("db%d", i);
+
+ filename = g_strdup_printf ("/HOME/.config/dconf/db%d", i);
+ }
+
+ dconf_mock_gvdb_install (filename, table);
+ g_free (filename);
+
+ database_state /= 7;
+ }
+}
+
+static void
+create_profile (const gchar *filename,
+ guint n_sources,
+ guint source_types)
+{
+ GError *error = NULL;
+ GString *profile;
+ gint i;
+
+ profile = g_string_new (NULL);
+ for (i = 0; i < n_sources; i++)
+ if (source_types & (1u << i))
+ g_string_append_printf (profile, "system-db:db%d\n", i);
+ else
+ g_string_append_printf (profile, "user-db:db%d\n", i);
+ g_file_set_contents (filename, profile->str, profile->len, &error);
+ g_assert_no_error (error);
+ g_string_free (profile, TRUE);
+}
+
+static void
+check_read (DConfEngine *engine,
+ guint n_sources,
+ guint source_types,
+ guint database_state)
+{
+ gboolean any_values = FALSE;
+ gboolean any_locks = FALSE;
+ gint expected = -1;
+ gboolean writable;
+ GVariant *value;
+ gchar **list;
+ guint i;
+
+ /* The value we expect to read is number of the first source that has
+ * the value set (ie: odd digit in database_state) up to the lowest
+ * level lock.
+ *
+ * We go over each database. If 'expected' has not yet been set and
+ * we find that we should have a value in this database, we set it.
+ * If we find that we should have a lock in this database, we unset
+ * any previous values (since they should not have been written).
+ *
+ * We initially code this loop in a different way than the one in
+ * dconf itself is currently implemented...
+ *
+ * We also take note of if we saw any locks and cross-check that with
+ * dconf_engine_is_writable(). We check if we saw and values at all
+ * and cross-check that with dconf_engine_list() (which ignores
+ * locks).
+ */
+ for (i = 0; i < n_sources; i++)
+ {
+ guint contents = database_state % 7;
+
+ /* A lock here should prevent higher reads */
+ if (contents > 4)
+ {
+ /* Locks in the first database don't count... */
+ if (i != 0)
+ any_locks = TRUE;
+ expected = -1;
+ }
+
+ /* A value here should be read */
+ if (contents && !(contents & 1) && expected == -1)
+ {
+ any_values = TRUE;
+ expected = i;
+ }
+
+ database_state /= 7;
+ }
+
+ value = dconf_engine_read (engine, NULL, "/value");
+
+ if (expected != -1)
+ {
+ g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32));
+ g_assert_cmpint (g_variant_get_uint32 (value), ==, expected);
+ g_variant_unref (value);
+ }
+ else
+ g_assert (value == NULL);
+
+ /* We are writable if the first database is a user database and we
+ * didn't encounter any locks...
+ */
+ writable = dconf_engine_is_writable (engine, "/value");
+ g_assert_cmpint (writable, ==, n_sources && !(source_types & 1) && !any_locks);
+
+ list = dconf_engine_list (engine, "/", NULL);
+ if (any_values)
+ {
+ g_assert_cmpstr (list[0], ==, "value");
+ g_assert (list[1] == NULL);
+ }
+ else
+ g_assert (list[0] == NULL);
+ g_strfreev (list);
+}
+
+static void
+test_read (void)
+{
+#define MAX_N_SOURCES 3
+ gpointer state[MAX_N_SOURCES];
+ gchar *profile_filename;
+ GError *error = NULL;
+ DConfEngine *engine;
+ guint i, j, k;
+ guint n;
+
+ /* Hack to silence warning */
+ if (!g_test_trap_fork (0, G_TEST_TRAP_SILENCE_STDERR))
+ {
+ g_test_trap_assert_passed ();
+ g_test_trap_assert_stderr ("*this gvdb does not exist; expect degraded performance*");
+ return;
+ }
+ g_log_set_always_fatal (G_LOG_LEVEL_ERROR);
+
+ /* Our test strategy is as follows:
+ *
+ * We only test a single key name. It is assumed that gvdb is working
+ * properly already so we are only interested in interactions between
+ * multiple databases for a given key name.
+ *
+ * The outermost loop is over 'n'. This is how many sources are in
+ * our test. We test 0 to 3 (which should be enough to cover all
+ * 'interesting' possibilities). 4 takes too long to run (2*7*7 ~=
+ * 100 times as long as 3).
+ *
+ * The next loop is over 'i'. This goes from 0 to 2^n - 1, with each
+ * bit deciding the type of source of the i-th element
+ *
+ * 0: user
+ *
+ * 1: system
+ *
+ * The next loop is over 'j'. This goes from 0 to 7^n - 1, with each
+ * base-7 digit deciding the state of the database file associated
+ * with the i-th source:
+ *
+ * j file has value has ".locks" has lock
+ * ----------------------------------------------------
+ * 0 0 - - -
+ * 1 1 0 0 -
+ * 2 1 1 0 -
+ * 3 1 0 1 0
+ * 4 1 1 1 0
+ * 5 1 0 1 1
+ * 6 1 1 1 1
+ *
+ * Where 'file' is if the database file exists, 'has value' is if a
+ * value exists at '/value' within the file, 'has ".locks"' is if
+ * there is a ".locks" subtable and 'has lock' is if there is a lock
+ * for '/value' within that table.
+ *
+ * Finally, we loop over 'k' as a state to transition to ('k' works
+ * the same way as 'j').
+ *
+ * Once we know 'n' and 'i', we can write a profile file.
+ *
+ * Once we know 'j' we can setup the initial state, create the engine
+ * and check that we got the expected value. Then we transition to
+ * state 'k' and make sure everything still works as expected.
+ *
+ * Since we want to test all j->k transitions, we do the initial setup
+ * of the engine (according to j) inside of the 'k' loop, since we
+ * need to test all possible transitions from 'j'.
+ */
+
+ /* We need a place to put the profile files we use for this test */
+ close (g_file_open_tmp ("dconf-testcase.XXXXXX", &profile_filename, &error));
+ g_assert_no_error (error);
+
+ g_setenv ("DCONF_PROFILE", profile_filename, TRUE);
+
+ for (n = 0; n < MAX_N_SOURCES; n++)
+ for (i = 0; i < pow (2, n); i++)
+ {
+ gint n_possible_states = pow (7, n);
+
+ /* Step 1: write out the profile file */
+ create_profile (profile_filename, n, i);
+
+ for (j = 0; j < n_possible_states; j++)
+ for (k = 0; k < n_possible_states; k++)
+ {
+ guint64 old_state, new_state;
+
+ /* Step 2: setup the state */
+ setup_state (n, i, j, state);
+
+ /* Step 3: create the engine */
+ engine = dconf_engine_new (NULL, NULL);
+
+ /* Step 4: read, and check result */
+ check_read (engine, n, i, j);
+ old_state = dconf_engine_get_state (engine);
+
+ /* Step 5: change to the new state */
+ if (j != k)
+ {
+ setup_state (n, i, k, NULL);
+ invalidate_state (n, i, state);
+ }
+
+ /* Step 6: read, and check result */
+ check_read (engine, n, i, k);
+ new_state = dconf_engine_get_state (engine);
+
+ g_assert ((j == k) == (new_state == old_state));
+
+ /* Clean up */
+ setup_state (n, i, 0, NULL);
+ dconf_engine_unref (engine);
+ }
+ }
+
+ /* Clean up the tempfile we were using... */
+ g_unsetenv ("DCONF_PROFILE");
+ g_unlink (profile_filename);
+ g_free (profile_filename);
+ exit (0);
+}
+
int
main (int argc, char **argv)
{
@@ -423,6 +740,7 @@ main (int argc, char **argv)
g_test_add_func ("/engine/signal-threadsafety", test_signal_threadsafety);
g_test_add_func ("/engine/sources/user", test_user_source);
g_test_add_func ("/engine/sources/system", test_system_source);
+ g_test_add_func ("/engine/read", test_read);
return g_test_run ();
}