summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHavoc Pennington <hp@pobox.com>1999-10-12 05:54:29 +0000
committerHavoc Pennington <hp@src.gnome.org>1999-10-12 05:54:29 +0000
commita1725faf5cc0d24404598bab793a953f894c9bc1 (patch)
treead19336eae37536878809504b921d11104d1a0ed
parent595e9d4475e42116294afa1e0905c590a571b1e5 (diff)
downloadgconf-a1725faf5cc0d24404598bab793a953f894c9bc1.tar.gz
Was failing to init the node in the flat array of nodes if the listener
1999-10-12 Havoc Pennington <hp@pobox.com> * gconf/gconf-listeners.c (ltable_insert): Was failing to init the node in the flat array of nodes if the listener location was NULL. (gconf_listeners_count): new function reports number of listeners (ltable_new): init next_cnxn to 1 instead of 0 (ltable_remove): Do a better job of cleaning up dead tree nodes (go up looking for empty parent nodes) (ltable_destroy): type error, was calling listener_destroy() on the GNodes. Oops. fixed, now use g_node_traverse() to destroy the node contents. (ltable_new): don't create the root node until it's needed (ltable_insert): add root node here if needed (ltable_remove): zero lt->tree if we destroy it * tests/testlisteners.c: New test program for GConfListeners, fairly comprehensive * tests/Makefile.am: add testlisteners, put EFENCE at the end of the link line
-rw-r--r--ChangeLog22
-rw-r--r--gconf/gconf-listeners.c163
-rw-r--r--gconf/gconf-listeners.h5
-rw-r--r--tests/Makefile.am11
-rw-r--r--tests/testlisteners.c560
5 files changed, 720 insertions, 41 deletions
diff --git a/ChangeLog b/ChangeLog
index ada13ed0..b7f3b41b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,26 @@
1999-10-12 Havoc Pennington <hp@pobox.com>
+
+ * gconf/gconf-listeners.c (ltable_insert): Was failing to init
+ the node in the flat array of nodes if the listener location
+ was NULL.
+ (gconf_listeners_count): new function reports number of listeners
+ (ltable_new): init next_cnxn to 1 instead of 0
+ (ltable_remove): Do a better job of cleaning up dead
+ tree nodes (go up looking for empty parent nodes)
+ (ltable_destroy): type error, was calling listener_destroy()
+ on the GNodes. Oops. fixed, now use g_node_traverse() to
+ destroy the node contents.
+ (ltable_new): don't create the root node until it's needed
+ (ltable_insert): add root node here if needed
+ (ltable_remove): zero lt->tree if we destroy it
+
+ * tests/testlisteners.c: New test program for GConfListeners,
+ fairly comprehensive
+
+ * tests/Makefile.am: add testlisteners, put EFENCE at the end of
+ the link line
+
+1999-10-12 Havoc Pennington <hp@pobox.com>
* gconf/Makefile.am: Fixes - remove some public headers from
libgconf_client_SOURCES, look for GConf.idl in $(srcdir) (bug from
diff --git a/gconf/gconf-listeners.c b/gconf/gconf-listeners.c
index 83844250..5de75852 100644
--- a/gconf/gconf-listeners.c
+++ b/gconf/gconf-listeners.c
@@ -18,6 +18,12 @@
*/
#include "gconf-listeners.h"
+#include "gconf.h"
+
+/* #define DEBUG_LISTENERS 1 */
+#ifdef DEBUG_LISTENERS
+#include <stdio.h>
+#endif
typedef struct _Listener Listener;
@@ -68,7 +74,7 @@ static void ltable_notify(LTable* ltable,
static guint ltable_next_cnxn(LTable* ltable);
-#if 0
+#ifdef DEBUG_LISTENERS
static void ltable_spew(LTable* ltable);
#endif
@@ -122,18 +128,28 @@ gconf_listeners_remove (GConfListeners* listeners,
{
LTable* lt = (LTable*)listeners;
+ g_return_if_fail(cnxn_id > 0);
+
ltable_remove(lt, cnxn_id);
}
void
gconf_listeners_notify (GConfListeners* listeners,
- const gchar* all_below,
+ const gchar* all_above,
GConfListenersCallback callback,
gpointer user_data)
{
LTable* lt = (LTable*)listeners;
- ltable_notify(lt, all_below, callback, user_data);
+ ltable_notify(lt, all_above, callback, user_data);
+}
+
+guint
+gconf_listeners_count (GConfListeners* listeners)
+{
+ LTable* lt = (LTable*)listeners;
+
+ return lt->active_listeners;
}
/*
@@ -165,23 +181,20 @@ static LTable*
ltable_new(void)
{
LTable* lt;
- LTableEntry* lte;
lt = g_new0(LTable, 1);
lt->listeners = g_ptr_array_new();
- /* Set initial size and init error value (0) to NULL */
+ /* Set initial size; note that GPtrArray's are initialized
+ to 0-filled */
g_ptr_array_set_size(lt->listeners, 5);
- g_ptr_array_index(lt->listeners, 0) = NULL;
-
- lte = ltable_entry_new("/");
-
- lt->tree = g_node_new(lte);
-
+
lt->active_listeners = 0;
lt->removed_cnxns = NULL;
+
+ lt->next_cnxn = 1; /* 0 is invalid */
return lt;
}
@@ -214,6 +227,17 @@ ltable_insert(LTable* lt, const gchar* where, Listener* l)
LTableEntry* lte;
const gchar* noroot_where = where + 1;
+ g_return_if_fail(gconf_valid_key(where, NULL));
+
+ if (lt->tree == NULL)
+ {
+ lte = ltable_entry_new("/");
+
+ lt->tree = g_node_new(lte);
+
+ lte = NULL; /* paranoia */
+ }
+
/* Add to the tree */
dirnames = g_strsplit(noroot_where, "/", -1);
@@ -280,10 +304,16 @@ ltable_insert(LTable* lt, const gchar* where, Listener* l)
g_strfreev(dirnames);
/* Add tree node to the flat table */
- g_ptr_array_set_size(lt->listeners, lt->next_cnxn);
- g_ptr_array_index(lt->listeners, l->cnxn) = found;
+ g_ptr_array_set_size(lt->listeners, MAX(lt->next_cnxn, l->cnxn));
+ g_ptr_array_index(lt->listeners, l->cnxn) = cur;
lt->active_listeners += 1;
+
+#ifdef DEBUG_LISTENERS
+ printf("Added %u at %s, spewing:\n",
+ l->cnxn, where);
+ ltable_spew(lt);
+#endif
}
static void
@@ -305,6 +335,8 @@ ltable_remove(LTable* lt, guint cnxn)
if (node == NULL)
return;
+ g_assert(lt->tree != NULL);
+
lte = node->data;
tmp = lte->listeners;
@@ -342,40 +374,74 @@ ltable_remove(LTable* lt, guint cnxn)
tmp = g_list_next(tmp);
}
-
+
+ /* note that tmp is invalid, but should be nonzero */
g_return_if_fail(tmp != NULL);
/* Remove from the tree if this node is now pointless */
- if (lte->listeners == NULL && node->children == NULL)
{
- ltable_entry_destroy(lte);
- g_node_destroy(node);
+ GNode* cur = node;
+ while (cur != NULL)
+ {
+ GNode* parent = cur->parent;
+ lte = cur->data;
+ if (lte->listeners == NULL && cur->children == NULL)
+ {
+ if (cur == lt->tree)
+ lt->tree = NULL;
+
+ ltable_entry_destroy(lte);
+ g_node_destroy(cur);
+ }
+ else
+ break; /* don't delete more parents since this node exists */
+
+ cur = parent;
+ }
}
lt->active_listeners -= 1;
+
+#ifdef DEBUG_LISTENERS
+ printf("Removed %u, spewing:\n", cnxn);
+ ltable_spew(lt);
+#endif
}
-static void
-ltable_destroy(LTable* ltable)
+static gboolean
+destroy_func(GNode* node, gpointer data)
{
- guint i;
-
- i = ltable->listeners->len - 1;
+ LTableEntry* lte = node->data;
+ GList* tmp;
- while (i > 0) /* 0 position in array is invalid */
+ tmp = lte->listeners;
+ while (tmp != NULL)
{
- if (g_ptr_array_index(ltable->listeners, i) != NULL)
- {
- listener_destroy(g_ptr_array_index(ltable->listeners, i));
- g_ptr_array_index(ltable->listeners, i) = NULL;
- }
+ Listener* l = tmp->data;
- --i;
+ listener_destroy(l);
+
+ tmp = g_list_next(tmp);
}
+ g_list_free(lte->listeners);
+ lte->listeners = NULL;
- g_ptr_array_free(ltable->listeners, TRUE);
+ ltable_entry_destroy(lte);
+
+ return FALSE;
+}
- g_node_destroy(ltable->tree);
+static void
+ltable_destroy(LTable* ltable)
+{
+ if (ltable->tree != NULL)
+ {
+ g_node_traverse(ltable->tree, G_POST_ORDER, G_TRAVERSE_ALL, -1,
+ destroy_func, NULL);
+ g_node_destroy(ltable->tree);
+ }
+
+ g_ptr_array_free(ltable->listeners, TRUE);
g_slist_free(ltable->removed_cnxns);
@@ -392,7 +458,7 @@ notify_listener_list(GConfListeners* listeners, GList* list, const gchar* key, G
{
Listener* l = tmp->data;
- (*callback)(listeners, l->cnxn, l->listener_data, user_data);
+ (*callback)(listeners, key, l->cnxn, l->listener_data, user_data);
tmp = g_list_next(tmp);
}
@@ -410,6 +476,10 @@ ltable_notify(LTable* lt, const gchar* key,
noroot_key = key + 1;
g_return_if_fail(*key == '/');
+ g_return_if_fail(gconf_valid_key(key, NULL));
+
+ if (lt->tree == NULL)
+ return; /* no one to notify */
/* Notify "/" listeners */
notify_listener_list((GConfListeners*)lt,
@@ -470,32 +540,51 @@ ltable_entry_destroy(LTableEntry* lte)
g_free(lte);
}
-#if 0
+#ifdef DEBUG_LISTENERS
/* Debug */
gboolean
spew_func(GNode* node, gpointer data)
{
LTableEntry* lte = node->data;
GList* tmp;
+ gchar spaces[256];
+ memset(spaces, ' ', 256);
+ spaces[g_node_depth(node)] = '\0';
- gconf_log(GCL_DEBUG, " Spewing node `%s'", lte->name);
+
+ printf(" %sSpewing node `%s' (%p): ", spaces, lte->name, node);
tmp = lte->listeners;
while (tmp != NULL)
{
Listener* l = tmp->data;
-
- gconf_log(GCL_DEBUG, " listener %u is here", (guint)l->cnxn);
+
+ printf(" %slistener %u is here\n", spaces, (guint)l->cnxn);
tmp = g_list_next(tmp);
}
+ if (lte->listeners == NULL)
+ printf("\n");
+
return FALSE;
}
static void
ltable_spew(LTable* lt)
{
- g_node_traverse(lt->tree, G_LEVEL_ORDER, G_TRAVERSE_ALL, -1, spew_func, NULL);
+ guint i;
+ printf("Flat table:\n");
+ i = 0;
+ while (i < lt->listeners->len)
+ {
+ GNode* node = g_ptr_array_index(lt->listeners, i);
+ LTableEntry* lte = node ? node->data : NULL;
+ printf("%u `%s' %p\n", i, lte ? lte->name : "", node);
+
+ ++i;
+ }
+
+ g_node_traverse(lt->tree, G_PRE_ORDER, G_TRAVERSE_ALL, -1, spew_func, NULL);
}
#endif
diff --git a/gconf/gconf-listeners.h b/gconf/gconf-listeners.h
index 6eb0f046..0917a5a4 100644
--- a/gconf/gconf-listeners.h
+++ b/gconf/gconf-listeners.h
@@ -41,6 +41,7 @@ struct _GConfListeners {
};
typedef void (*GConfListenersCallback)(GConfListeners* listeners,
+ const gchar* all_above_key,
guint cnxn_id,
gpointer listener_data,
gpointer user_data);
@@ -62,10 +63,12 @@ void gconf_listeners_remove (GConfListeners* listeners,
guint cnxn_id);
void gconf_listeners_notify (GConfListeners* listeners,
- const gchar* all_below,
+ const gchar* all_above,
GConfListenersCallback callback,
gpointer user_data);
+guint gconf_listeners_count (GConfListeners* listeners);
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index d27b2ebd..188f5dd8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -2,10 +2,15 @@
EFENCE=
INCLUDES = -I$(top_srcdir)/gconf -I$(includedir) $(ORBIT_CFLAGS) $(GLIB_CFLAGS) \
- -DG_LOG_DOMAIN=\"testgconf\"
+ -DG_LOG_DOMAIN=\"GConf-Tests\"
-noinst_PROGRAMS=testgconf
+noinst_PROGRAMS=testgconf testlisteners
testgconf_SOURCES=testgconf.c
-testgconf_LDADD = $(EFENCE) $(ORBIT_LIBS) $(GLIB_LIBS) $(top_builddir)/gconf/libgconf-client.la
+testgconf_LDADD = $(ORBIT_LIBS) $(GLIB_LIBS) $(top_builddir)/gconf/libgconf-client.la $(EFENCE)
+
+testlisteners_SOURCES=testlisteners.c
+
+testlisteners_LDADD = $(ORBIT_LIBS) $(GLIB_LIBS) $(top_builddir)/gconf/libgconf-client.la $(EFENCE)
+
diff --git a/tests/testlisteners.c b/tests/testlisteners.c
new file mode 100644
index 00000000..23867922
--- /dev/null
+++ b/tests/testlisteners.c
@@ -0,0 +1,560 @@
+/* GConf
+ * Copyright (C) 1999 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <gconf-listeners.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <math.h>
+#include <string.h>
+#include <stdlib.h>
+
+static void
+check(gboolean condition, const gchar* fmt, ...)
+{
+ va_list args;
+ gchar* description;
+
+ va_start (args, fmt);
+ description = g_strdup_vprintf(fmt, args);
+ va_end (args);
+
+ if (condition)
+ {
+ printf(".");
+ fflush(stdout);
+ }
+ else
+ {
+ fprintf(stderr, "\n*** FAILED: %s\n", description);
+ exit(1);
+ }
+
+ g_free(description);
+}
+
+static const gchar*
+keys[] = {
+ "/testing/foo/tar",
+ "/testing/foo/bar",
+ "/testing/quad",
+ "/testing/blah",
+ "/testing/q/a/b/c/z/w/x/y/z",
+ "/testing/foo/baz",
+ "/testing/dup",
+ "/testing/oops/bloo",
+ "/testing/oops/snoo",
+ "/testing/dup",
+ "/testing/oops/kwoo",
+ "/testing/foo/quaz",
+ "/testing",
+ "/testing/oops",
+ "/testing/dup",
+ "/",
+ "/blah/blah/blah",
+ "/blah/blah/foo",
+ "/blah/blah/bar",
+ "/boo",
+ "/baz",
+ "/bap",
+ "/duplicate",
+ "/duplicate",
+ NULL
+};
+
+struct stuff {
+ guint id;
+ gchar* key;
+ guint* id_retloc;
+ gchar** key_retloc;
+};
+
+static void
+test_destroy_notify(gpointer data)
+{
+ struct stuff* s = data;
+
+ *s->id_retloc = s->id;
+
+ *s->key_retloc = s->key;
+
+ g_free(s);
+}
+
+static void
+check_add_remove(GConfListeners* listeners)
+{
+ guint ids[128];
+ guint i;
+ const gchar** keyp;
+ guint id_retloc;
+ gchar* key_retloc;
+
+ memset(ids, 0, sizeof(ids[0]) * 128);
+
+ i = 0;
+ keyp = keys;
+
+ while (i < 128 && *keyp)
+ {
+ struct stuff* s;
+
+ s = g_new0(struct stuff, 1);
+
+ s->id_retloc = &id_retloc;
+ s->key_retloc = &key_retloc;
+
+ s->id = ids[i] = gconf_listeners_add(listeners,
+ *keyp,
+ s,
+ test_destroy_notify);
+
+ s->key = g_strdup(*keyp);
+
+ check(ids[i] != 0, "invalid connection ID returned for added listener");
+
+ ++i;
+ ++keyp;
+ }
+
+ check(gconf_listeners_count(listeners) == i,
+ "number of listeners added (%u) don't now exist in the GConfListeners (%u exist)", i, gconf_listeners_count(listeners));
+
+ i = 0;
+ keyp = keys;
+
+ while (i < 128 && *keyp)
+ {
+ id_retloc = 0;
+ key_retloc = NULL;
+
+ gconf_listeners_remove(listeners, ids[i]);
+
+ check(strcmp(key_retloc, *keyp) == 0,
+ "listener removed has different key from listener added (`%s' vs. `%s')", *keyp, key_retloc);
+
+ g_free(key_retloc);
+
+ check(ids[i] == id_retloc, "listener removed had different id from that added (%u vs. %u)", ids[i], id_retloc);
+
+ ++i;
+ ++keyp;
+ }
+
+ check(gconf_listeners_count(listeners) == 0,
+ "listener count isn't 0 after removing all the listeners");
+}
+
+static void
+check_immediate_remove_after_add(GConfListeners* listeners)
+{
+ guint ids[128];
+ guint i;
+ const gchar** keyp;
+ guint id_retloc;
+ gchar* key_retloc;
+
+ memset(ids, 0, sizeof(ids[0]) * 128);
+
+ i = 0;
+ keyp = keys;
+
+ while (i < 128 && *keyp)
+ {
+ struct stuff* s;
+
+ s = g_new0(struct stuff, 1);
+
+ s->id_retloc = &id_retloc;
+ s->key_retloc = &key_retloc;
+
+ s->id = ids[i] = gconf_listeners_add(listeners,
+ *keyp,
+ s,
+ test_destroy_notify);
+
+ s->key = g_strdup(*keyp);
+
+ check(ids[i] != 0, "invalid connection ID returned for added listener");
+
+ if (i > 0)
+ check(ids[i] == ids[i-1], "connection ID was not properly recycled");
+
+ check(gconf_listeners_count(listeners) == 1,
+ "didn't have 1 listener as expected");
+
+ id_retloc = 0;
+ key_retloc = NULL;
+
+ gconf_listeners_remove(listeners, ids[i]);
+
+ check(strcmp(key_retloc, *keyp) == 0,
+ "listener removed has different key from listener added (`%s' vs. `%s')", *keyp, key_retloc);
+
+ g_free(key_retloc);
+
+ check(ids[i] == id_retloc, "listener removed had different id from that added (%u vs. %u)", ids[i], id_retloc);
+
+ check(gconf_listeners_count(listeners) == 0,
+ "didn't have 0 listeners as expected");
+
+ ++i;
+ ++keyp;
+ }
+
+ check(gconf_listeners_count(listeners) == 0,
+ "listener count isn't 0 after removing all the listeners");
+}
+
+static void
+check_double_add_remove(GConfListeners* listeners)
+{
+ guint ids[128];
+ guint i;
+ const gchar** keyp;
+ guint id_retloc;
+ gchar* key_retloc;
+
+ memset(ids, 0, sizeof(ids[0]) * 128);
+
+ i = 0;
+ keyp = keys;
+
+ while (i < 128 && *keyp)
+ {
+ struct stuff* s;
+
+ s = g_new0(struct stuff, 1);
+
+ s->id_retloc = &id_retloc;
+ s->key_retloc = &key_retloc;
+
+ s->id = ids[i] = gconf_listeners_add(listeners,
+ *keyp,
+ s,
+ test_destroy_notify);
+
+ s->key = g_strdup(*keyp);
+
+ check(ids[i] != 0, "invalid connection ID returned for added listener");
+
+ ++i;
+ if ((i % 2) == 0)
+ ++keyp;
+ }
+
+ check(gconf_listeners_count(listeners) == i,
+ "number of listeners added (%u) don't now exist in the GConfListeners (%u exist)", i, gconf_listeners_count(listeners));
+
+ i = 0;
+ keyp = keys;
+
+ while (i < 128 && *keyp)
+ {
+ id_retloc = 0;
+ key_retloc = NULL;
+
+ gconf_listeners_remove(listeners, ids[i]);
+
+ check(strcmp(key_retloc, *keyp) == 0,
+ "listener removed has different key from listener added (`%s' vs. `%s')", *keyp, key_retloc);
+
+ g_free(key_retloc);
+
+ check(ids[i] == id_retloc, "listener removed had different id from that added (%u vs. %u)", ids[i], id_retloc);
+
+ ++i;
+ if ((i % 2) == 0)
+ ++keyp;
+ }
+
+ check(gconf_listeners_count(listeners) == 0,
+ "listener count isn't 0 after removing all the listeners");
+}
+
+static gboolean
+should_be_notified(const gchar* changed_key,
+ const gchar* listener_watchpoint)
+{
+ return strncmp(changed_key, listener_watchpoint, strlen(listener_watchpoint)) == 0;
+}
+
+struct notify_data {
+ const gchar* notify_key;
+ GSList* notified;
+};
+
+void
+notify_callback(GConfListeners* listeners,
+ const gchar* all_above_key,
+ guint cnxn_id,
+ gpointer listener_data,
+ gpointer user_data)
+{
+ struct notify_data* nd = user_data;
+ struct stuff* s = listener_data;
+
+ check(strcmp(all_above_key, nd->notify_key) == 0, "notify key `%s' is not the notify key received in callback `%s'",
+ nd->notify_key, all_above_key);
+
+ check(cnxn_id == s->id,
+ "listener ID is wrong in callback (%u vs. %u)",
+ cnxn_id, s->id);
+
+ check(should_be_notified(all_above_key, s->key),
+ "listener %u at `%s' shouldn't have been notified of change to `%s'",
+ s->id, s->key, all_above_key);
+
+ nd->notified = g_slist_prepend(nd->notified, s);
+}
+
+void
+check_notification(GConfListeners* listeners)
+{
+ guint ids[128];
+ guint i;
+ const gchar** keyp;
+ guint id_retloc;
+ gchar* key_retloc;
+
+ memset(ids, 0, sizeof(ids[0]) * 128);
+
+ i = 0;
+ keyp = keys;
+
+ while (i < 128 && *keyp)
+ {
+ struct stuff* s;
+
+ s = g_new0(struct stuff, 1);
+
+ s->id_retloc = &id_retloc;
+ s->key_retloc = &key_retloc;
+
+ s->id = ids[i] = gconf_listeners_add(listeners,
+ *keyp,
+ s,
+ test_destroy_notify);
+
+ s->key = g_strdup(*keyp);
+
+ check(ids[i] != 0, "invalid connection ID returned for added listener");
+
+ /* printf("%u added at `%s'\n", s->id, s->key); */
+
+ ++i;
+ ++keyp;
+ }
+
+ check(gconf_listeners_count(listeners) == i,
+ "number of listeners added (%u) don't now exist in the GConfListeners (%u exist)", i, gconf_listeners_count(listeners));
+
+ keyp = keys;
+
+ while (*keyp)
+ {
+ struct notify_data nd = { *keyp, NULL };
+ GSList* tmp = NULL;
+ const gchar** sub_keyp;
+
+ nd.notified = NULL;
+
+ gconf_listeners_notify(listeners, *keyp,
+ notify_callback,
+ &nd);
+
+ /* Check that the list that was notified matches
+ the list that should have been */
+ sub_keyp = keys;
+ while (*sub_keyp)
+ {
+ struct stuff* s = NULL;
+ gboolean should_be = should_be_notified(*keyp, *sub_keyp);
+
+ tmp = nd.notified;
+ while (tmp != NULL)
+ {
+ s = tmp->data;
+
+ if (strcmp(s->key, *sub_keyp) == 0)
+ break;
+
+ tmp = g_slist_next(tmp);
+ }
+
+ if (should_be)
+ {
+ check (tmp != NULL, "listener at `%s' should have been notified of change to `%s' and was not", *sub_keyp, *keyp);
+ s = tmp->data;
+ /* remove so we can handle duplicate keys */
+ nd.notified = g_slist_remove(nd.notified, tmp->data);
+
+#if 0
+ g_assert(strcmp(s->key, *sub_keyp) == 0);
+ printf("%u at `%s' notified properly of `%s'\n",
+ s->id, *sub_keyp, *keyp);
+#endif
+ }
+ else
+ {
+ check(tmp == NULL, "listener at `%s' should not have been notified of change to `%s' but it was", *sub_keyp, *keyp);
+#if 0
+ printf("`%s' properly not notified of `%s'\n",
+ *sub_keyp, *keyp);
+#endif
+ }
+
+ ++sub_keyp;
+ }
+
+ if (nd.notified != NULL)
+ {
+ GSList* tmp = nd.notified;
+ while (tmp != NULL)
+ {
+ struct stuff* s = tmp->data;
+ fprintf(stderr, "leftover: %u at `%s' notified of `%s'\n",
+ s->id, s->key, *keyp);
+ tmp = g_slist_next(tmp);
+ }
+ }
+ check (nd.notified == NULL, "superfluous listeners were notified of `%s'; perhaps listeners were notified twice?", *keyp);
+
+ ++keyp;
+ }
+
+ i = 0;
+ keyp = keys;
+
+ while (i < 128 && *keyp)
+ {
+ id_retloc = 0;
+ key_retloc = NULL;
+
+ gconf_listeners_remove(listeners, ids[i]);
+
+ check(strcmp(key_retloc, *keyp) == 0,
+ "listener removed has different key from listener added (`%s' vs. `%s')", *keyp, key_retloc);
+
+ g_free(key_retloc);
+
+ check(ids[i] == id_retloc, "listener removed had different id from that added (%u vs. %u)", ids[i], id_retloc);
+
+ ++i;
+ ++keyp;
+ }
+
+ check(gconf_listeners_count(listeners) == 0,
+ "listener count isn't 0 after removing all the listeners");
+}
+
+struct destroy_data {
+ gchar* key;
+ guint* destroy_count_loc;
+};
+
+static void
+destroy_test_destroy_notify(gpointer data)
+{
+ struct destroy_data* d = data;
+
+ *d->destroy_count_loc += 1;
+
+ g_free(d->key);
+
+ g_free(d);
+}
+
+/* This is mostly for use with memory leak detection tools */
+void
+check_destroy(void)
+{
+ GConfListeners* listeners;
+ guint ids[128];
+ guint i;
+ const gchar** keyp;
+ guint destroy_count = 0;
+
+ listeners = gconf_listeners_new();
+
+ memset(ids, 0, sizeof(ids[0]) * 128);
+
+ i = 0;
+ keyp = keys;
+
+ while (i < 128 && *keyp)
+ {
+ struct destroy_data* d;
+
+ d = g_new0(struct destroy_data, 1);
+
+ d->destroy_count_loc = &destroy_count;
+
+ ids[i] = gconf_listeners_add(listeners,
+ *keyp,
+ d,
+ destroy_test_destroy_notify);
+
+ d->key = g_strdup(*keyp);
+
+ check(ids[i] != 0, "invalid connection ID returned for added listener");
+
+ ++i;
+ ++keyp;
+ }
+
+ check(gconf_listeners_count(listeners) == i,
+ "number of listeners added (%u) don't now exist in the GConfListeners (%u exist)", i, gconf_listeners_count(listeners));
+
+ gconf_listeners_destroy(listeners);
+
+ check(destroy_count == i,
+ "number of listeners added (%u) doesn't match number destroyed (%u) on GConfListeners destruction", i, destroy_count);
+}
+
+int
+main (int argc, char** argv)
+{
+ GConfListeners* listeners;
+
+ listeners = gconf_listeners_new();
+
+ check_add_remove(listeners);
+
+ g_assert(gconf_listeners_count(listeners) == 0);
+
+ check_immediate_remove_after_add(listeners);
+
+ g_assert(gconf_listeners_count(listeners) == 0);
+
+ check_double_add_remove(listeners);
+
+ g_assert(gconf_listeners_count(listeners) == 0);
+
+ check_notification(listeners);
+
+ g_assert(gconf_listeners_count(listeners) == 0);
+
+ gconf_listeners_destroy(listeners);
+
+ check_destroy();
+
+ printf("\n");
+
+ return 0;
+}