[evolution-data-server/mmeeks-gdbus-import] add the tests



commit 6bb725ff168c36ece73f69a087da2b712a6474a8
Author: Michael Meeks <michael meeks novell com>
Date:   Fri Feb 26 11:03:23 2010 +0000

    add the tests

 Makefile.am                     |    4 +-
 edbus/ebitlock.c                |    2 +-
 edbus/edbusenumtypes.c          |    2 +-
 edbus/edbusenumtypes.c.template |    2 +-
 edbus/tests/Makefile.am         |   51 ++
 edbus/tests/connection.c        |  486 +++++++++++++++++
 edbus/tests/error.c             |  198 +++++++
 edbus/tests/export.c            | 1118 +++++++++++++++++++++++++++++++++++++++
 edbus/tests/introspection.c     |  163 ++++++
 edbus/tests/names.c             |  651 +++++++++++++++++++++++
 edbus/tests/peer.c              |  448 ++++++++++++++++
 edbus/tests/proxy.c             |  387 ++++++++++++++
 edbus/tests/sessionbus.c        |  342 ++++++++++++
 edbus/tests/sessionbus.h        |   38 ++
 edbus/tests/tests.c             |  131 +++++
 edbus/tests/tests.h             |  117 ++++
 edbus/tests/threading.c         |  529 ++++++++++++++++++
 17 files changed, 4664 insertions(+), 5 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 0c00c9c..d1c567b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -6,8 +6,8 @@ if ENABLE_CALENDAR
 CALENDAR_DIR = calendar
 endif
 
-SUBDIRS = win32 libedataserver libebackend servers camel addressbook $(CALENDAR_DIR) libedataserverui docs art po edbus
-DIST_SUBDIRS = win32 libedataserver libebackend servers camel addressbook calendar libedataserverui docs art po edbus
+SUBDIRS = win32 edbus libedataserver libebackend servers camel addressbook $(CALENDAR_DIR) libedataserverui docs art po
+DIST_SUBDIRS = win32 edbus libedataserver libebackend servers camel addressbook calendar libedataserverui docs art po
 
 changelogs =			\
 	ChangeLog
diff --git a/edbus/ebitlock.c b/edbus/ebitlock.c
index a1dcaa5..1d245ea 100644
--- a/edbus/ebitlock.c
+++ b/edbus/ebitlock.c
@@ -38,7 +38,7 @@ static GSList *g_futex_address_list = NULL;
 static GMutex *g_futex_mutex = NULL;
 #endif
 
-#warning Need to initialize me ...
+/* called by gdbus_threads_init */
 extern void _g_futex_thread_init(void);
 
 void
diff --git a/edbus/edbusenumtypes.c b/edbus/edbusenumtypes.c
index 39fb906..a853fa9 100644
--- a/edbus/edbusenumtypes.c
+++ b/edbus/edbusenumtypes.c
@@ -1,7 +1,7 @@
 
 /* Generated data (by glib-mkenums) */
 
-#include <gdbus.h>
+#include <edbus.h>
 
 /* enumerations from "evariant.h" */
 GType
diff --git a/edbus/edbusenumtypes.c.template b/edbus/edbusenumtypes.c.template
index 317fa22..935f3e0 100644
--- a/edbus/edbusenumtypes.c.template
+++ b/edbus/edbusenumtypes.c.template
@@ -1,5 +1,5 @@
 /*** BEGIN file-header ***/
-#include <gdbus.h>
+#include <edbus.h>
 
 /*** END file-header ***/
 
diff --git a/edbus/tests/Makefile.am b/edbus/tests/Makefile.am
new file mode 100644
index 0000000..765a7f1
--- /dev/null
+++ b/edbus/tests/Makefile.am
@@ -0,0 +1,51 @@
+include $(srcdir)/Makefile.decl
+
+NULL =
+
+INCLUDES =                      \
+        -g                      \
+        -I$(top_srcdir)         \
+	$(EDBUS_CFLAGS)		\
+	$(NULL)
+
+noinst_PROGRAMS = $(TEST_PROGS)
+progs_ldadd     =				\
+	$(EDBUS_LIBS)				\
+        $(top_builddir)/edbus/libedbus.la	\
+	$(NULL)
+
+TEST_PROGS += connection
+TEST_PROGS += names
+TEST_PROGS += proxy
+TEST_PROGS += introspection
+TEST_PROGS += threading
+TEST_PROGS += export
+TEST_PROGS += error
+TEST_PROGS += peer
+
+connection_SOURCES = connection.c sessionbus.c sessionbus.h tests.h tests.c
+connection_LDADD = $(progs_ldadd)
+
+names_SOURCES = names.c sessionbus.c sessionbus.h tests.h tests.c
+names_LDADD = $(progs_ldadd)
+
+proxy_SOURCES = proxy.c sessionbus.c sessionbus.h tests.h tests.c
+proxy_LDADD = $(progs_ldadd)
+
+introspection_SOURCES = introspection.c sessionbus.c sessionbus.h tests.h tests.c
+introspection_LDADD = $(progs_ldadd)
+
+threading_SOURCES = threading.c sessionbus.c sessionbus.h tests.h tests.c
+threading_LDADD = $(progs_ldadd)
+
+export_SOURCES = export.c sessionbus.c sessionbus.h tests.h tests.c
+export_CFLAGS = $(DBUS1_CFLAGS)
+export_LDADD = $(progs_ldadd)
+
+error_SOURCES = error.c sessionbus.c sessionbus.h tests.h tests.c
+error_CFLAGS = $(DBUS1_CFLAGS)
+error_LDADD = $(progs_ldadd)
+
+peer_SOURCES = peer.c sessionbus.c sessionbus.h tests.h tests.c
+peer_CFLAGS = $(DBUS1_CFLAGS)
+peer_LDADD = $(progs_ldadd)
diff --git a/edbus/tests/connection.c b/edbus/tests/connection.c
new file mode 100644
index 0000000..709b575
--- /dev/null
+++ b/edbus/tests/connection.c
@@ -0,0 +1,486 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <edbus/edbus.h>
+#include <unistd.h>
+
+#include "tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Connection life-cycle testing */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_connection_life_cycle (void)
+{
+  EDBusConnection *c;
+  EDBusConnection *c2;
+  GError *error;
+
+  error = NULL;
+
+  /**
+   * Check for correct behavior when no bus is present
+   *
+   */
+  c = e_dbus_connection_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_error (error, E_DBUS_ERROR, E_DBUS_ERROR_FILE_NOT_FOUND);
+  g_assert (!e_dbus_error_is_remote_error (error));
+  g_assert (c == NULL);
+  g_error_free (error);
+  error = NULL;
+
+  /**
+   *  Check for correct behavior when a bus is present
+   */
+  session_bus_up ();
+  /* case 1 */
+  c = e_dbus_connection_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (c != NULL);
+  g_assert (!e_dbus_connection_get_is_disconnected (c));
+
+  /**
+   * Check that singleton handling work
+   */
+  c2 = e_dbus_connection_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (c2 != NULL);
+  g_assert (c == c2);
+  g_object_unref (c2);
+
+  /**
+   * Check that private connections work
+   */
+  c2 = e_dbus_connection_bus_get_private_sync (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (c2 != NULL);
+  g_assert (c != c2);
+  g_object_unref (c2);
+
+  /**
+   *  Check for correct behavior when the bus goes away
+   *
+   */
+  e_dbus_connection_set_exit_on_disconnect (c, FALSE);
+  session_bus_down ();
+  _g_assert_signal_received (c, "disconnected");
+  g_assert (e_dbus_connection_get_is_disconnected (c));
+  g_object_unref (c);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that sending and receiving messages work as expected */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+msg_cb_expect_error_disconnected (EDBusConnection *connection,
+                                  GAsyncResult    *res,
+                                  gpointer         user_data)
+{
+  GError *error;
+  EVariant *result;
+
+  error = NULL;
+  result = e_dbus_connection_invoke_method_finish (connection,
+                                                   res,
+                                                   &error);
+  g_assert_error (error, E_DBUS_ERROR, E_DBUS_ERROR_DISCONNECTED);
+  g_assert (!e_dbus_error_is_remote_error (error));
+  g_error_free (error);
+  g_assert (result == NULL);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+msg_cb_expect_error_unknown_method (EDBusConnection *connection,
+                                    GAsyncResult    *res,
+                                    gpointer         user_data)
+{
+  GError *error;
+  EVariant *result;
+
+  error = NULL;
+  result = e_dbus_connection_invoke_method_finish (connection,
+                                                   res,
+                                                   &error);
+  g_assert_error (error, E_DBUS_ERROR, E_DBUS_ERROR_UNKNOWN_METHOD);
+  g_assert (e_dbus_error_is_remote_error (error));
+  g_assert (result == NULL);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+msg_cb_expect_success (EDBusConnection *connection,
+                       GAsyncResult    *res,
+                       gpointer         user_data)
+{
+  GError *error;
+  EVariant *result;
+
+  error = NULL;
+  result = e_dbus_connection_invoke_method_finish (connection,
+                                                   res,
+                                                   &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  e_variant_unref (result);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+msg_cb_expect_error_cancelled (EDBusConnection *connection,
+                               GAsyncResult    *res,
+                               gpointer         user_data)
+{
+  GError *error;
+  EVariant *result;
+
+  error = NULL;
+  result = e_dbus_connection_invoke_method_finish (connection,
+                                                   res,
+                                                   &error);
+  g_assert_error (error, E_DBUS_ERROR, E_DBUS_ERROR_CANCELLED);
+  g_assert (!e_dbus_error_is_remote_error (error));
+  g_error_free (error);
+  g_assert (result == NULL);
+
+  g_main_loop_quit (loop);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_connection_send (void)
+{
+  EDBusConnection *c;
+  GCancellable *ca;
+
+  session_bus_up ();
+
+  /* First, get an unopened connection */
+  c = e_dbus_connection_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+  g_assert (c != NULL);
+  g_assert (!e_dbus_connection_get_is_disconnected (c));
+
+  /**
+   * Check that we never actually send a message if the GCancellable is already cancelled - i.e.
+   * we should get #E_DBUS_ERROR_CANCELLED instead of #E_DBUS_ERROR_FAILED even when the actual
+   * connection is not up.
+   */
+  ca = g_cancellable_new ();
+  g_cancellable_cancel (ca);
+  e_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetId",                 /* method name */
+                                   NULL,
+                                   -1,
+                                   ca,
+                                   (GAsyncReadyCallback) msg_cb_expect_error_cancelled,
+                                   NULL);
+  g_main_loop_run (loop);
+  g_object_unref (ca);
+
+  /**
+   * Check that we get a reply to the GetId() method call.
+   */
+  e_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetId",                 /* method name */
+                                   NULL,
+                                   -1,
+                                   NULL,
+                                   (GAsyncReadyCallback) msg_cb_expect_success,
+                                   NULL);
+  g_main_loop_run (loop);
+
+  /**
+   * Check that we get an error reply to the NonExistantMethod() method call.
+   */
+  e_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "NonExistantMethod",     /* method name */
+                                   NULL,
+                                   -1,
+                                   NULL,
+                                   (GAsyncReadyCallback) msg_cb_expect_error_unknown_method,
+                                   NULL);
+  g_main_loop_run (loop);
+
+  /**
+   * Check that cancellation works when the message is already in flight.
+   */
+  ca = g_cancellable_new ();
+  e_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetId",                 /* method name */
+                                   NULL,
+                                   -1,
+                                   ca,
+                                   (GAsyncReadyCallback) msg_cb_expect_error_cancelled,
+                                   NULL);
+  g_cancellable_cancel (ca);
+  g_main_loop_run (loop);
+  g_object_unref (ca);
+
+  /**
+   * Check that we get an error when sending to a connection that is disconnected.
+   */
+  e_dbus_connection_set_exit_on_disconnect (c, FALSE);
+  session_bus_down ();
+  _g_assert_signal_received (c, "disconnected");
+  g_assert (e_dbus_connection_get_is_disconnected (c));
+
+  e_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetId",                 /* method name */
+                                   NULL,
+                                   -1,
+                                   NULL,
+                                   (GAsyncReadyCallback) msg_cb_expect_error_disconnected,
+                                   NULL);
+  g_main_loop_run (loop);
+
+  g_object_unref (c);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Connection signal tests */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_connection_signal_handler (EDBusConnection *connection,
+                                const gchar      *sender_name,
+                                const gchar      *object_path,
+                                const gchar      *interface_name,
+                                const gchar      *signal_name,
+                                EVariant         *parameters,
+                                gpointer         user_data)
+{
+  gint *counter = user_data;
+  *counter += 1;
+  g_main_loop_quit (loop);
+}
+
+static gboolean
+test_connection_signal_quit_mainloop (gpointer user_data)
+{
+  g_main_loop_quit (loop);
+  return FALSE;
+}
+
+static void
+test_connection_signals (void)
+{
+  EDBusConnection *c1;
+  EDBusConnection *c2;
+  EDBusConnection *c3;
+  guint s1;
+  guint s2;
+  guint s3;
+  gint count_s1;
+  gint count_s2;
+  gint count_name_owner_changed;
+  GError *error;
+  gboolean ret;
+
+  error = NULL;
+
+  /**
+   * Bring up first separate connections
+   */
+  session_bus_up ();
+  /* if running with dbus-monitor, it claims the name :1.0 - so if we don't run with the monitor
+   * emulate this
+   */
+  if (g_getenv ("E_DBUS_MONITOR") == NULL)
+    {
+      c1 = e_dbus_connection_bus_get_private_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+      g_assert (c1 != NULL);
+      g_assert (!e_dbus_connection_get_is_disconnected (c1));
+      g_object_unref (c1);
+    }
+  c1 = e_dbus_connection_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+  g_assert (c1 != NULL);
+  g_assert (!e_dbus_connection_get_is_disconnected (c1));
+  g_assert_cmpstr (e_dbus_connection_get_unique_name (c1), ==, ":1.1");
+
+  /**
+   * Install two signal handlers for the first connection
+   *
+   *  - Listen to the signal "Foo" from :1.2 (e.g. c2)
+   *  - Listen to the signal "Foo" from anyone (e.g. both c2 and c3)
+   *
+   * and then count how many times this signal handler was invoked.
+   */
+  s1 = e_dbus_connection_signal_subscribe (c1,
+                                           ":1.2",
+                                           "org.gtk.EDBus.ExampleInterface",
+                                           "Foo",
+                                           "/org/gtk/EDBus/ExampleInterface",
+                                           NULL,
+                                           test_connection_signal_handler,
+                                           &count_s1,
+                                           NULL);
+  s2 = e_dbus_connection_signal_subscribe (c1,
+                                           NULL, /* match any sender */
+                                           "org.gtk.EDBus.ExampleInterface",
+                                           "Foo",
+                                           "/org/gtk/EDBus/ExampleInterface",
+                                           NULL,
+                                           test_connection_signal_handler,
+                                           &count_s2,
+                                           NULL);
+  s3 = e_dbus_connection_signal_subscribe (c1,
+                                           "org.freedesktop.DBus",  /* sender */
+                                           "org.freedesktop.DBus",  /* interface */
+                                           "NameOwnerChanged",      /* member */
+                                           "/org/freedesktop/DBus", /* path */
+                                           NULL,
+                                           test_connection_signal_handler,
+                                           &count_name_owner_changed,
+                                           NULL);
+  g_assert (s1 != 0);
+  g_assert (s2 != 0);
+  g_assert (s3 != 0);
+
+  count_s1 = 0;
+  count_s2 = 0;
+  count_name_owner_changed = 0;
+
+  /**
+   * Bring up two other connections
+   */
+  c2 = e_dbus_connection_bus_get_private_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+  g_assert (c2 != NULL);
+  g_assert (!e_dbus_connection_get_is_disconnected (c2));
+  g_assert_cmpstr (e_dbus_connection_get_unique_name (c2), ==, ":1.2");
+  c3 = e_dbus_connection_bus_get_private_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+  g_assert (c3 != NULL);
+  g_assert (!e_dbus_connection_get_is_disconnected (c3));
+  g_assert_cmpstr (e_dbus_connection_get_unique_name (c3), ==, ":1.3");
+
+  /**
+   * Make c2 emit "Foo" - we should catch it twice
+   */
+  ret = e_dbus_connection_emit_signal (c2,
+                                       NULL, /* destination bus name */
+                                       "/org/gtk/EDBus/ExampleInterface",
+                                       "org.gtk.EDBus.ExampleInterface",
+                                       "Foo",
+                                       NULL,
+                                       &error);
+  g_assert_no_error (error);
+  g_assert (ret);
+  while (!(count_s1 == 1 && count_s2 == 1))
+    g_main_loop_run (loop);
+  g_assert_cmpint (count_s1, ==, 1);
+  g_assert_cmpint (count_s2, ==, 1);
+
+  /**
+   * Make c3 emit "Foo" - we should catch it only once
+   */
+  ret = e_dbus_connection_emit_signal (c3,
+                                       NULL, /* destination bus name */
+                                       "/org/gtk/EDBus/ExampleInterface",
+                                       "org.gtk.EDBus.ExampleInterface",
+                                       "Foo",
+                                       NULL,
+                                       &error);
+  g_assert_no_error (error);
+  g_assert (ret);
+  while (!(count_s1 == 1 && count_s2 == 2))
+    g_main_loop_run (loop);
+  g_assert_cmpint (count_s1, ==, 1);
+  g_assert_cmpint (count_s2, ==, 2);
+
+  /**
+   * Tool around in the mainloop to avoid race conditions and also to check the
+   * total amount of NameOwnerChanged signals
+   */
+  g_timeout_add (500, test_connection_signal_quit_mainloop, NULL);
+  g_main_loop_run (loop);
+  g_assert_cmpint (count_s1, ==, 1);
+  g_assert_cmpint (count_s2, ==, 2);
+  g_assert_cmpint (count_name_owner_changed, ==, 2);
+
+  /**
+   * Now bring down the session bus and check we get the :disconnected signal from each connection.
+   */
+  session_bus_down ();
+  e_dbus_connection_set_exit_on_disconnect (c1, FALSE);
+  e_dbus_connection_set_exit_on_disconnect (c2, FALSE);
+  e_dbus_connection_set_exit_on_disconnect (c3, FALSE);
+  if (!e_dbus_connection_get_is_disconnected (c1))
+    _g_assert_signal_received (c1, "disconnected");
+  if (!e_dbus_connection_get_is_disconnected (c2))
+    _g_assert_signal_received (c2, "disconnected");
+  if (!e_dbus_connection_get_is_disconnected (c3))
+    _g_assert_signal_received (c3, "disconnected");
+
+  e_dbus_connection_signal_unsubscribe (c1, s1);
+  e_dbus_connection_signal_unsubscribe (c1, s2);
+  e_dbus_connection_signal_unsubscribe (c1, s3);
+  g_object_unref (c1);
+  g_object_unref (c2);
+  g_object_unref (c3);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  /* all the tests rely on a shared main loop */
+  loop = g_main_loop_new (NULL, FALSE);
+
+  /* all the tests use a session bus with a well-known address that we can bring up and down
+   * using session_bus_up() and session_bus_down().
+   */
+  g_unsetenv ("DISPLAY");
+  g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+  g_test_add_func ("/gdbus/connection-life-cycle", test_connection_life_cycle);
+  g_test_add_func ("/gdbus/connection-send", test_connection_send);
+  g_test_add_func ("/gdbus/connection-signals", test_connection_signals);
+  return g_test_run();
+}
diff --git a/edbus/tests/error.c b/edbus/tests/error.c
new file mode 100644
index 0000000..5914f36
--- /dev/null
+++ b/edbus/tests/error.c
@@ -0,0 +1,198 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <edbus/edbus.h>
+#include <unistd.h>
+#include <string.h>
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that registered errors are properly mapped */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+check_registered_error (const gchar *given_dbus_error_name,
+                        GQuark       error_domain,
+                        gint         error_code)
+{
+  GError *error;
+  gchar *dbus_error_name;
+
+  error = e_dbus_error_new_for_dbus_error (given_dbus_error_name, "test message");
+  g_assert_error (error, error_domain, error_code);
+  g_assert (e_dbus_error_is_remote_error (error));
+  g_assert (e_dbus_error_strip_remote_error (error));
+  g_assert_cmpstr (error->message, ==, "test message");
+  dbus_error_name = e_dbus_error_get_remote_error (error);
+  g_assert_cmpstr (dbus_error_name, ==, given_dbus_error_name);
+  g_free (dbus_error_name);
+  g_error_free (error);
+}
+
+static void
+test_registered_errors (void)
+{
+  /* Here we check that we are able to map to GError and back for registered
+   * errors.
+   *
+   * For example, if "org.freedesktop.DBus.Error.AddressInUse" is
+   * associated with (E_DBUS_ERROR, E_DBUS_ERROR_DBUS_FAILED), check
+   * that
+   *
+   *  - Creating a GError for e.g. "org.freedesktop.DBus.Error.AddressInUse"
+   *    has (error_domain, code) == (E_DBUS_ERROR, E_DBUS_ERROR_DBUS_FAILED)
+   *
+   *  - That it is possible to recover e.g. "org.freedesktop.DBus.Error.AddressInUse"
+   *    as the D-Bus error name when dealing with an error with (error_domain, code) ==
+   *    (E_DBUS_ERROR, E_DBUS_ERROR_DBUS_FAILED)
+   *
+   * We just check a couple of well-known errors.
+   */
+  check_registered_error ("org.freedesktop.DBus.Error.Failed",
+                          E_DBUS_ERROR,
+                          E_DBUS_ERROR_DBUS_FAILED);
+  check_registered_error ("org.freedesktop.DBus.Error.AddressInUse",
+                          E_DBUS_ERROR,
+                          E_DBUS_ERROR_ADDRESS_IN_USE);
+  check_registered_error ("org.freedesktop.DBus.Error.UnknownMethod",
+                          E_DBUS_ERROR,
+                          E_DBUS_ERROR_UNKNOWN_METHOD);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+check_unregistered_error (const gchar *given_dbus_error_name)
+{
+  GError *error;
+  gchar *dbus_error_name;
+
+  error = e_dbus_error_new_for_dbus_error (given_dbus_error_name, "test message");
+  g_assert_error (error, E_DBUS_ERROR, E_DBUS_ERROR_REMOTE_ERROR);
+  g_assert (e_dbus_error_is_remote_error (error));
+  dbus_error_name = e_dbus_error_get_remote_error (error);
+  g_assert_cmpstr (dbus_error_name, ==, given_dbus_error_name);
+  g_free (dbus_error_name);
+
+  /* strip the message */
+  g_assert (e_dbus_error_strip_remote_error (error));
+  g_assert_cmpstr (error->message, ==, "test message");
+
+  /* check that we can no longer recover the D-Bus error name */
+  g_assert (e_dbus_error_get_remote_error (error) == NULL);
+
+  g_error_free (error);
+
+}
+
+static void
+test_unregistered_errors (void)
+{
+  /* Here we check that we are able to map to GError and back for unregistered
+   * errors.
+   *
+   * For example, if "com.example.Error.Failed" is not registered, then check
+   *
+   *  - Creating a GError for e.g. "com.example.Error.Failed" has (error_domain, code) ==
+   *    (E_DBUS_ERROR, E_DBUS_ERROR_REMOTE_ERROR)
+   *
+   *  - That it is possible to recover e.g. "com.example.Error.Failed" from that
+   *    GError.
+   *
+   * We just check a couple of random errors.
+   */
+
+  check_unregistered_error ("com.example.Error.Failed");
+  check_unregistered_error ("foobar.buh");
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+check_transparent_gerror (GQuark error_domain,
+                          gint   error_code)
+{
+  GError *error;
+  gchar *given_dbus_error_name;
+  gchar *dbus_error_name;
+
+  error = g_error_new (error_domain, error_code, "test message");
+  given_dbus_error_name = e_dbus_error_encode_gerror (error);
+  g_assert (g_str_has_prefix (given_dbus_error_name, "org.gtk.EDBus.UnmappedGError.Quark"));
+  g_error_free (error);
+
+  error = e_dbus_error_new_for_dbus_error (given_dbus_error_name, "test message");
+  g_assert_error (error, error_domain, error_code);
+  g_assert (e_dbus_error_is_remote_error (error));
+  dbus_error_name = e_dbus_error_get_remote_error (error);
+  g_assert_cmpstr (dbus_error_name, ==, given_dbus_error_name);
+  g_free (dbus_error_name);
+  g_free (given_dbus_error_name);
+
+  /* strip the message */
+  g_assert (e_dbus_error_strip_remote_error (error));
+  g_assert_cmpstr (error->message, ==, "test message");
+
+  /* check that we can no longer recover the D-Bus error name */
+  g_assert (e_dbus_error_get_remote_error (error) == NULL);
+
+  g_error_free (error);
+}
+
+static void
+test_transparent_gerror (void)
+{
+  /* Here we check that we are able to transparent pass unregistered GError's
+   * over the wire.
+   *
+   * For example, if G_IO_ERROR_FAILED is not registered, then check
+   *
+   *  - e_dbus_error_encode_gerror() returns something of the form
+   *    org.gtk.EDBus.UnmappedGError.Quark_HEXENCODED_QUARK_NAME_.Code_ERROR_CODE
+   *
+   *  - mapping back the D-Bus error name gives us G_IO_ERROR_FAILED
+   *
+   *  - That it is possible to recover the D-Bus error name from the
+   *    GError.
+   *
+   * We just check a couple of random errors.
+   */
+
+  check_transparent_gerror (G_IO_ERROR, G_IO_ERROR_FAILED);
+  check_transparent_gerror (G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_PARSE);
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  g_test_add_func ("/gdbus/registered-errors", test_registered_errors);
+  g_test_add_func ("/gdbus/unregistered-errors", test_unregistered_errors);
+  g_test_add_func ("/gdbus/transparent-gerror", test_transparent_gerror);
+
+  return g_test_run();
+}
diff --git a/edbus/tests/export.c b/edbus/tests/export.c
new file mode 100644
index 0000000..bb4c9ad
--- /dev/null
+++ b/edbus/tests/export.c
@@ -0,0 +1,1118 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <edbus/edbus.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <dbus/dbus.h>
+
+#include "tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that we can export objects, the hierarchy is correct and the right handlers are invoked */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static const EDBusMethodInfo foo_method_info[] =
+{
+  {
+    "Method1",
+    "", 0, NULL,
+    "", 0, NULL,
+    NULL
+  },
+  {
+    "Method2",
+    "", 0, NULL,
+    "", 0, NULL,
+    NULL
+  }
+};
+
+static const EDBusSignalInfo foo_signal_info[] =
+{
+  {
+    "SignalAlpha",
+    "", 0, NULL,
+    NULL
+  }
+};
+
+static const EDBusPropertyInfo foo_property_info[] =
+{
+  {
+    "PropertyUno",
+    "s",
+    E_DBUS_PROPERTY_INFO_FLAGS_READABLE,
+    NULL
+  }
+};
+
+static const EDBusInterfaceInfo foo_interface_info =
+{
+  "org.example.Foo",
+  2,
+  foo_method_info,
+  1,
+  foo_signal_info,
+  1,
+  foo_property_info,
+  NULL,
+};
+
+/* -------------------- */
+
+static const EDBusMethodInfo bar_method_info[] =
+{
+  {
+    "MethodA",
+    "", 0, NULL,
+    "", 0, NULL,
+    NULL
+  },
+  {
+    "MethodB",
+    "", 0, NULL,
+    "", 0, NULL,
+    NULL
+  }
+};
+
+static const EDBusSignalInfo bar_signal_info[] =
+{
+  {
+    "SignalMars",
+    "", 0, NULL,
+    NULL
+  }
+};
+
+static const EDBusPropertyInfo bar_property_info[] =
+{
+  {
+    "PropertyDuo",
+    "s",
+    E_DBUS_PROPERTY_INFO_FLAGS_READABLE,
+    NULL
+  }
+};
+
+static const EDBusInterfaceInfo bar_interface_info =
+{
+  "org.example.Bar",
+  2,
+  bar_method_info,
+  1,
+  bar_signal_info,
+  1,
+  bar_property_info,
+  NULL,
+};
+
+/* -------------------- */
+
+static const EDBusMethodInfo dyna_method_info[] =
+{
+  {
+    "DynaCyber",
+    "", 0, NULL,
+    "", 0, NULL,
+    NULL
+  }
+};
+
+static const EDBusInterfaceInfo dyna_interface_info =
+{
+  "org.example.Dyna",
+  1,    /* 1 method*/
+  dyna_method_info,
+  0,    /* 0 signals */
+  NULL,
+  0,    /* 0 properties */
+  NULL,
+  NULL,
+};
+
+static void
+dyna_cyber (EDBusConnection *connection,
+            gpointer user_data,
+            const gchar *sender,
+            const gchar *object_path,
+            const gchar *interface_name,
+            const gchar *method_name,
+            EVariant *parameters,
+            EDBusMethodInvocation *invocation)
+{
+  GPtrArray *data = user_data;
+  gchar *node_name;
+  guint n;
+
+  node_name = strrchr (object_path, '/') + 1;
+
+  /* Add new node if it is not already known */
+  for (n = 0; n < data->len ; n++)
+    {
+      if (g_strcmp0 (g_ptr_array_index (data, n), node_name) == 0)
+        goto out;
+    }
+  g_ptr_array_add (data, g_strdup(node_name));
+
+  out:
+    e_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static const EDBusInterfaceVTable dyna_interface_vtable =
+{
+  dyna_cyber,
+  NULL,
+  NULL
+};
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+introspect_callback (EDBusProxy   *proxy,
+                     GAsyncResult *res,
+                     gpointer      user_data)
+{
+  const gchar *s;
+  gchar **xml_data = user_data;
+  EVariant *result;
+  GError *error;
+
+  error = NULL;
+  result = e_dbus_proxy_invoke_method_finish (proxy,
+                                              res,
+                                              &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  e_variant_get (result, "(s)", &s);
+  *xml_data = g_strdup (s);
+  e_variant_unref (result);
+
+  g_main_loop_quit (loop);
+}
+
+static gchar **
+get_nodes_at (EDBusConnection  *c,
+              const gchar      *object_path)
+{
+  GError *error;
+  EDBusProxy *proxy;
+  gchar *xml_data;
+  GPtrArray *p;
+  EDBusNodeInfo *node_info;
+  guint n;
+
+  error = NULL;
+  proxy = e_dbus_proxy_new_sync (c,
+                                 E_TYPE_DBUS_PROXY,
+                                 E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+                                 E_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+                                 e_dbus_connection_get_unique_name (c),
+                                 object_path,
+                                 "org.freedesktop.DBus.Introspectable",
+                                 NULL,
+                                 &error);
+  g_assert_no_error (error);
+  g_assert (proxy != NULL);
+
+  /* do this async to avoid libdbus-1 deadlocks */
+  xml_data = NULL;
+  e_dbus_proxy_invoke_method (proxy,
+                              "Introspect",
+                              NULL,
+                              -1,
+                              NULL,
+                              (GAsyncReadyCallback) introspect_callback,
+                              &xml_data);
+  g_main_loop_run (loop);
+  g_assert (xml_data != NULL);
+
+  node_info = e_dbus_node_info_new_for_xml (xml_data, &error);
+  g_assert_no_error (error);
+  g_assert (node_info != NULL);
+
+  p = g_ptr_array_new ();
+  for (n = 0; n < node_info->num_nodes; n++)
+    {
+      const EDBusNodeInfo *sub_node_info = node_info->nodes + n;
+      g_ptr_array_add (p, g_strdup (sub_node_info->path));
+    }
+  g_ptr_array_add (p, NULL);
+
+  g_object_unref (proxy);
+  g_free (xml_data);
+  e_dbus_node_info_free (node_info);
+
+  return (gchar **) g_ptr_array_free (p, FALSE);
+}
+
+static gboolean
+has_interface (EDBusConnection *c,
+               const gchar     *object_path,
+               const gchar     *interface_name)
+{
+  GError *error;
+  EDBusProxy *proxy;
+  gchar *xml_data;
+  EDBusNodeInfo *node_info;
+  gboolean ret;
+
+  error = NULL;
+  proxy = e_dbus_proxy_new_sync (c,
+                                 E_TYPE_DBUS_PROXY,
+                                 E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+                                 E_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+                                 e_dbus_connection_get_unique_name (c),
+                                 object_path,
+                                 "org.freedesktop.DBus.Introspectable",
+                                 NULL,
+                                 &error);
+  g_assert_no_error (error);
+  g_assert (proxy != NULL);
+
+  /* do this async to avoid libdbus-1 deadlocks */
+  xml_data = NULL;
+  e_dbus_proxy_invoke_method (proxy,
+                              "Introspect",
+                              NULL,
+                              -1,
+                              NULL,
+                              (GAsyncReadyCallback) introspect_callback,
+                              &xml_data);
+  g_main_loop_run (loop);
+  g_assert (xml_data != NULL);
+
+  node_info = e_dbus_node_info_new_for_xml (xml_data, &error);
+  g_assert_no_error (error);
+  g_assert (node_info != NULL);
+
+  ret = (e_dbus_node_info_lookup_interface (node_info, interface_name) != NULL);
+
+  g_object_unref (proxy);
+  g_free (xml_data);
+  e_dbus_node_info_free (node_info);
+
+  return ret;
+}
+
+static guint
+count_interfaces (EDBusConnection *c,
+                  const gchar     *object_path)
+{
+  GError *error;
+  EDBusProxy *proxy;
+  gchar *xml_data;
+  EDBusNodeInfo *node_info;
+  guint ret;
+
+  error = NULL;
+  proxy = e_dbus_proxy_new_sync (c,
+                                 E_TYPE_DBUS_PROXY,
+                                 E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+                                 E_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+                                 e_dbus_connection_get_unique_name (c),
+                                 object_path,
+                                 "org.freedesktop.DBus.Introspectable",
+                                 NULL,
+                                 &error);
+  g_assert_no_error (error);
+  g_assert (proxy != NULL);
+
+  /* do this async to avoid libdbus-1 deadlocks */
+  xml_data = NULL;
+  e_dbus_proxy_invoke_method (proxy,
+                              "Introspect",
+                              NULL,
+                              -1,
+                              NULL,
+                              (GAsyncReadyCallback) introspect_callback,
+                              &xml_data);
+  g_main_loop_run (loop);
+  g_assert (xml_data != NULL);
+
+  node_info = e_dbus_node_info_new_for_xml (xml_data, &error);
+  g_assert_no_error (error);
+  g_assert (node_info != NULL);
+
+  ret = node_info->num_interfaces;
+
+  g_object_unref (proxy);
+  g_free (xml_data);
+  e_dbus_node_info_free (node_info);
+
+  return ret;
+}
+
+static void
+dyna_create_callback (EDBusProxy   *proxy,
+                      GAsyncResult  *res,
+                      gpointer      user_data)
+{
+  EVariant *result;
+  GError *error;
+
+  error = NULL;
+  result = e_dbus_proxy_invoke_method_finish (proxy,
+                                              res,
+                                              &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  e_variant_unref (result);
+
+  g_main_loop_quit (loop);
+}
+
+/* Dynamically create @object_name under /foo/dyna */
+static void
+dyna_create (EDBusConnection *c,
+             const gchar     *object_name)
+{
+  GError *error;
+  EDBusProxy *proxy;
+  gchar *object_path;
+
+  object_path = g_strconcat ("/foo/dyna/", object_name, NULL);
+
+  error = NULL;
+  proxy = e_dbus_proxy_new_sync (c,
+                                 E_TYPE_DBUS_PROXY,
+                                 E_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
+                                 E_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+                                 e_dbus_connection_get_unique_name (c),
+                                 object_path,
+                                 "org.example.Dyna",
+                                 NULL,
+                                 &error);
+  g_assert_no_error (error);
+  g_assert (proxy != NULL);
+
+  /* do this async to avoid libdbus-1 deadlocks */
+  e_dbus_proxy_invoke_method (proxy,
+                              "DynaCyber",
+                              e_variant_new ("()"),
+                              -1,
+                              NULL,
+                              (GAsyncReadyCallback) dyna_create_callback,
+                              NULL);
+  g_main_loop_run (loop);
+
+  g_assert_no_error (error);
+
+  g_object_unref (proxy);
+  g_free (object_path);
+
+  return;
+}
+
+typedef struct
+{
+  guint num_unregistered_calls;
+  guint num_unregistered_subtree_calls;
+  guint num_subtree_nodes;
+} ObjectRegistrationData;
+
+static void
+on_object_unregistered (gpointer user_data)
+{
+  ObjectRegistrationData *data = user_data;
+
+  data->num_unregistered_calls++;
+}
+
+static void
+on_subtree_unregistered (gpointer user_data)
+{
+  ObjectRegistrationData *data = user_data;
+
+  data->num_unregistered_subtree_calls++;
+}
+
+static gboolean
+_g_strv_has_string (const gchar* const * haystack,
+                    const gchar *needle)
+{
+  guint n;
+
+  for (n = 0; haystack != NULL && haystack[n] != NULL; n++)
+    {
+      if (g_strcmp0 (haystack[n], needle) == 0)
+        return TRUE;
+    }
+  return FALSE;
+}
+
+static DBusHandlerResult
+dc_message_func (DBusConnection *connection,
+                 DBusMessage    *message,
+                 void           *user_data)
+{
+  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+/* -------------------- */
+
+static gchar **
+subtree_enumerate (EDBusConnection       *connection,
+                   gpointer               user_data,
+                   const gchar           *sender,
+                   const gchar           *object_path)
+{
+  ObjectRegistrationData *data = user_data;
+  GPtrArray *p;
+  gchar **nodes;
+  guint n;
+
+  p = g_ptr_array_new ();
+
+  for (n = 0; n < data->num_subtree_nodes; n++)
+    {
+      g_ptr_array_add (p, g_strdup_printf ("vp%d", n));
+      g_ptr_array_add (p, g_strdup_printf ("evp%d", n));
+    }
+  g_ptr_array_add (p, NULL);
+
+  nodes = (gchar **) g_ptr_array_free (p, FALSE);
+
+  return nodes;
+}
+
+/* Only allows certain objects, and aborts on unknowns */
+static GPtrArray *
+subtree_introspect (EDBusConnection       *connection,
+                    gpointer               user_data,
+                    const gchar           *sender,
+                    const gchar           *object_path,
+                    const gchar           *node)
+{
+  GPtrArray *interfaces;
+
+  /* VPs implement the Foo interface, EVPs implement the Bar interface. The root
+   * does not implement any interfaces
+   */
+  interfaces = g_ptr_array_new ();
+  if (g_str_has_prefix (node, "vp"))
+    {
+      g_ptr_array_add (interfaces, (gpointer) &foo_interface_info);
+    }
+  else if (g_str_has_prefix (node, "evp"))
+    {
+      g_ptr_array_add (interfaces, (gpointer) &bar_interface_info);
+    }
+  else if (g_strcmp0 (node, "/") == 0)
+    {
+      /* do nothing */
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+
+  return interfaces;
+}
+
+static const EDBusInterfaceVTable *
+subtree_dispatch (EDBusConnection             *connection,
+                  gpointer                     user_data,
+                  const gchar                 *sender,
+                  const gchar                 *object_path,
+                  const gchar                 *interface_name,
+                  const gchar                 *node,
+                  gpointer                    *out_user_data)
+{
+  return NULL;
+}
+
+static const EDBusSubtreeVTable subtree_vtable =
+{
+  subtree_enumerate,
+  subtree_introspect,
+  subtree_dispatch
+};
+
+/* -------------------- */
+
+static gchar **
+dynamic_subtree_enumerate (EDBusConnection       *connection,
+                           gpointer               user_data,
+                           const gchar           *sender,
+                           const gchar           *object_path)
+{
+  GPtrArray *data = user_data;
+  gchar **nodes = g_new (gchar*, data->len + 1);
+  guint n;
+
+  for (n = 0; n < data->len; n++)
+    {
+      nodes[n] = g_strdup (g_ptr_array_index (data, n));
+    }
+  nodes[data->len] = NULL;
+
+  return nodes;
+}
+
+/* Allow all objects to be introspected */
+static GPtrArray *
+dynamic_subtree_introspect (EDBusConnection       *connection,
+                            gpointer               user_data,
+                            const gchar           *sender,
+                            const gchar           *object_path,
+                            const gchar           *node)
+{
+  GPtrArray *interfaces;
+
+  /* All nodes (including the root node) implements the Dyna interface */
+  interfaces = g_ptr_array_new ();
+  g_ptr_array_add (interfaces, (gpointer) &dyna_interface_info);
+
+  return interfaces;
+}
+
+static const EDBusInterfaceVTable *
+dynamic_subtree_dispatch (EDBusConnection             *connection,
+                          gpointer                     user_data,
+                          const gchar                 *sender,
+                          const gchar                 *object_path,
+                          const gchar                 *interface_name,
+                          const gchar                 *node,
+                          gpointer                    *out_user_data)
+{
+  *out_user_data = user_data;
+  return &dyna_interface_vtable;
+}
+
+static const EDBusSubtreeVTable dynamic_subtree_vtable =
+{
+  dynamic_subtree_enumerate,
+  dynamic_subtree_introspect,
+  dynamic_subtree_dispatch
+};
+
+/* -------------------- */
+
+static void
+test_object_registration (void)
+{
+  EDBusConnection *c;
+  GError *error;
+  ObjectRegistrationData data;
+  GPtrArray *dyna_data;
+  gchar **nodes;
+  guint boss_foo_reg_id;
+  guint boss_bar_reg_id;
+  guint worker1_foo_reg_id;
+  guint worker2_bar_reg_id;
+  guint intern1_foo_reg_id;
+  guint intern2_bar_reg_id;
+  guint intern2_foo_reg_id;
+  guint intern3_bar_reg_id;
+  guint registration_id;
+  guint subtree_registration_id;
+  guint non_subtree_object_path_foo_reg_id;
+  guint non_subtree_object_path_bar_reg_id;
+  guint dyna_subtree_registration_id;
+  guint num_successful_registrations;
+  DBusConnection *dc;
+  DBusError dbus_error;
+  DBusObjectPathVTable dc_obj_vtable =
+    {
+      NULL,
+      dc_message_func,
+      NULL,
+      NULL,
+      NULL,
+      NULL
+    };
+
+  data.num_unregistered_calls = 0;
+  data.num_unregistered_subtree_calls = 0;
+  data.num_subtree_nodes = 0;
+
+  num_successful_registrations = 0;
+
+  error = NULL;
+  c = e_dbus_connection_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (c != NULL);
+
+  registration_id = e_dbus_connection_register_object (c,
+                                                       "/foo/boss",
+                                                       foo_interface_info.name,
+                                                       &foo_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  boss_foo_reg_id = registration_id;
+  num_successful_registrations++;
+
+  registration_id = e_dbus_connection_register_object (c,
+                                                       "/foo/boss",
+                                                       bar_interface_info.name,
+                                                       &bar_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  boss_bar_reg_id = registration_id;
+  num_successful_registrations++;
+
+  registration_id = e_dbus_connection_register_object (c,
+                                                       "/foo/boss/worker1",
+                                                       foo_interface_info.name,
+                                                       &foo_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  worker1_foo_reg_id = registration_id;
+  num_successful_registrations++;
+
+  registration_id = e_dbus_connection_register_object (c,
+                                                       "/foo/boss/worker2",
+                                                       bar_interface_info.name,
+                                                       &bar_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  worker2_bar_reg_id = registration_id;
+  num_successful_registrations++;
+
+  registration_id = e_dbus_connection_register_object (c,
+                                                       "/foo/boss/interns/intern1",
+                                                       foo_interface_info.name,
+                                                       &foo_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  intern1_foo_reg_id = registration_id;
+  num_successful_registrations++;
+
+  /* Now check we get an error if trying to register a path already registered by another D-Bus binding
+   *
+   * To check this we need to pretend, for a while, that we're another binding.
+   */
+  dbus_error_init (&dbus_error);
+  dc = dbus_bus_get (DBUS_BUS_SESSION, &dbus_error);
+  g_assert (!dbus_error_is_set (&dbus_error));
+  g_assert (dc != NULL);
+  g_assert (dbus_connection_try_register_object_path (dc,
+                                                      "/foo/boss/interns/other_intern",
+                                                      &dc_obj_vtable,
+                                                      NULL,
+                                                      &dbus_error));
+  registration_id = e_dbus_connection_register_object (c,
+                                                       "/foo/boss/interns/other_intern",
+                                                       bar_interface_info.name,
+                                                       &bar_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_error (error, E_DBUS_ERROR, E_DBUS_ERROR_OBJECT_PATH_IN_USE);
+  g_assert (!e_dbus_error_is_remote_error (error));
+  g_error_free (error);
+  error = NULL;
+  g_assert (registration_id == 0);
+
+  /* ... and try again at another path */
+  registration_id = e_dbus_connection_register_object (c,
+                                                       "/foo/boss/interns/intern2",
+                                                       bar_interface_info.name,
+                                                       &bar_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  intern2_bar_reg_id = registration_id;
+  num_successful_registrations++;
+
+  /* register at the same path/interface - this should fail */
+  registration_id = e_dbus_connection_register_object (c,
+                                                       "/foo/boss/interns/intern2",
+                                                       bar_interface_info.name,
+                                                       &bar_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_error (error, E_DBUS_ERROR, E_DBUS_ERROR_OBJECT_PATH_IN_USE);
+  g_assert (!e_dbus_error_is_remote_error (error));
+  g_error_free (error);
+  error = NULL;
+  g_assert (registration_id == 0);
+
+  /* register at different interface - shouldn't fail */
+  registration_id = e_dbus_connection_register_object (c,
+                                                       "/foo/boss/interns/intern2",
+                                                       foo_interface_info.name,
+                                                       &foo_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  intern2_foo_reg_id = registration_id;
+  num_successful_registrations++;
+
+  /* unregister it via the id */
+  g_assert (e_dbus_connection_unregister_object (c, registration_id));
+  g_assert_cmpint (data.num_unregistered_calls, ==, 1);
+  intern2_foo_reg_id = 0;
+
+  /* register it back */
+  registration_id = e_dbus_connection_register_object (c,
+                                                       "/foo/boss/interns/intern2",
+                                                       foo_interface_info.name,
+                                                       &foo_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  intern2_foo_reg_id = registration_id;
+  num_successful_registrations++;
+
+  registration_id = e_dbus_connection_register_object (c,
+                                                       "/foo/boss/interns/intern3",
+                                                       bar_interface_info.name,
+                                                       &bar_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  intern3_bar_reg_id = registration_id;
+  num_successful_registrations++;
+
+  /* now register a whole subtree at /foo/boss/executives */
+  subtree_registration_id = e_dbus_connection_register_subtree (c,
+                                                                "/foo/boss/executives",
+                                                                &subtree_vtable,
+                                                                E_DBUS_SUBTREE_FLAGS_NONE,
+                                                                &data,
+                                                                on_subtree_unregistered,
+                                                                &error);
+  g_assert_no_error (error);
+  g_assert (subtree_registration_id > 0);
+  /* try registering it again.. this should fail */
+  registration_id = e_dbus_connection_register_subtree (c,
+                                                        "/foo/boss/executives",
+                                                        &subtree_vtable,
+                                                        E_DBUS_SUBTREE_FLAGS_NONE,
+                                                        &data,
+                                                        on_subtree_unregistered,
+                                                        &error);
+  g_assert_error (error, E_DBUS_ERROR, E_DBUS_ERROR_OBJECT_PATH_IN_USE);
+  g_assert (!e_dbus_error_is_remote_error (error));
+  g_error_free (error);
+  error = NULL;
+  g_assert (registration_id == 0);
+
+  /* unregister it, then register it again */
+  g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 0);
+  g_assert (e_dbus_connection_unregister_subtree (c, subtree_registration_id));
+  g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 1);
+  subtree_registration_id = e_dbus_connection_register_subtree (c,
+                                                                "/foo/boss/executives",
+                                                                &subtree_vtable,
+                                                                E_DBUS_SUBTREE_FLAGS_NONE,
+                                                                &data,
+                                                                on_subtree_unregistered,
+                                                                &error);
+  g_assert_no_error (error);
+  g_assert (subtree_registration_id > 0);
+
+  /* try to register something under /foo/boss/executives - this should work
+   * because registered subtrees and registered objects can coexist.
+   *
+   * Make the exported object implement *two* interfaces so we can check
+   * that the right introspection handler is invoked.
+   */
+  registration_id = e_dbus_connection_register_object (c,
+                                                       "/foo/boss/executives/non_subtree_object",
+                                                       bar_interface_info.name,
+                                                       &bar_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  non_subtree_object_path_bar_reg_id = registration_id;
+  num_successful_registrations++;
+  registration_id = e_dbus_connection_register_object (c,
+                                                       "/foo/boss/executives/non_subtree_object",
+                                                       foo_interface_info.name,
+                                                       &foo_interface_info,
+                                                       NULL,
+                                                       &data,
+                                                       on_object_unregistered,
+                                                       &error);
+  g_assert_no_error (error);
+  g_assert (registration_id > 0);
+  non_subtree_object_path_foo_reg_id = registration_id;
+  num_successful_registrations++;
+
+  /* now register a dynamic subtree, spawning objects as they are called */
+  dyna_data = g_ptr_array_new ();
+  dyna_subtree_registration_id = e_dbus_connection_register_subtree (c,
+                                                                     "/foo/dyna",
+                                                                     &dynamic_subtree_vtable,
+                                                                     E_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES,
+                                                                     dyna_data,
+                                                                     (GDestroyNotify)g_ptr_array_unref,
+                                                                     &error);
+  g_assert_no_error (error);
+  g_assert (dyna_subtree_registration_id > 0);
+
+  /* First assert that we have no nodes in the dynamic subtree */
+  nodes = get_nodes_at (c, "/foo/dyna");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 0);
+  g_strfreev (nodes);
+  g_assert_cmpint (count_interfaces (c, "/foo/dyna"), ==, 4);
+
+  /* Install three nodes in the dynamic subtree via the dyna_data backdoor and
+   * assert that they show up correctly in the introspection data */
+  g_ptr_array_add (dyna_data, (gpointer)"lol");
+  g_ptr_array_add (dyna_data, (gpointer)"cat");
+  g_ptr_array_add (dyna_data, (gpointer)"cheezburger");
+  nodes = get_nodes_at (c, "/foo/dyna");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 3);
+  g_assert_cmpstr (nodes[0], ==, "lol");
+  g_assert_cmpstr (nodes[1], ==, "cat");
+  g_assert_cmpstr (nodes[2], ==, "cheezburger");
+  g_strfreev (nodes);
+  g_assert_cmpint (count_interfaces (c, "/foo/dyna/lol"), ==, 4);
+  g_assert_cmpint (count_interfaces (c, "/foo/dyna/cat"), ==, 4);
+  g_assert_cmpint (count_interfaces (c, "/foo/dyna/cheezburger"), ==, 4);
+
+  /* Call a non-existing object path and assert that it has been created */
+  dyna_create (c, "dynamicallycreated");
+  nodes = get_nodes_at (c, "/foo/dyna");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 4);
+  g_assert_cmpstr (nodes[0], ==, "lol");
+  g_assert_cmpstr (nodes[1], ==, "cat");
+  g_assert_cmpstr (nodes[2], ==, "cheezburger");
+  g_assert_cmpstr (nodes[3], ==, "dynamicallycreated");
+  g_strfreev (nodes);
+  g_assert_cmpint (count_interfaces (c, "/foo/dyna/dynamicallycreated"), ==, 4);
+
+
+  /* now check that the object hierarachy is properly generated... yes, it's a bit
+   * perverse that we round-trip to the bus to introspect ourselves ;-)
+   */
+  nodes = get_nodes_at (c, "/");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 1);
+  g_assert_cmpstr (nodes[0], ==, "foo");
+  g_strfreev (nodes);
+  g_assert_cmpint (count_interfaces (c, "/"), ==, 0);
+
+  nodes = get_nodes_at (c, "/foo");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 2);
+  g_assert_cmpstr (nodes[0], ==, "boss");
+  g_assert_cmpstr (nodes[1], ==, "dyna");
+  g_strfreev (nodes);
+  g_assert_cmpint (count_interfaces (c, "/foo"), ==, 0);
+
+  nodes = get_nodes_at (c, "/foo/boss");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 4);
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "worker1"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "worker2"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "interns"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "executives"));
+  g_strfreev (nodes);
+  /* any registered object always implement org.freedesktop.DBus.[Peer,Introspectable,Properties] */
+  g_assert_cmpint (count_interfaces (c, "/foo/boss"), ==, 5);
+  g_assert (has_interface (c, "/foo/boss", foo_interface_info.name));
+  g_assert (has_interface (c, "/foo/boss", bar_interface_info.name));
+
+  /* check subtree nodes - we should have only non_subtree_object in /foo/boss/executives
+   * because data.num_subtree_nodes is 0
+   */
+  nodes = get_nodes_at (c, "/foo/boss/executives");
+  g_assert (nodes != NULL);
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
+  g_assert_cmpint (g_strv_length (nodes), ==, 1);
+  g_strfreev (nodes);
+  g_assert_cmpint (count_interfaces (c, "/foo/boss/executives"), ==, 0);
+
+  /* now change data.num_subtree_nodes and check */
+  data.num_subtree_nodes = 2;
+  nodes = get_nodes_at (c, "/foo/boss/executives");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 5);
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp0"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp1"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp0"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp1"));
+  /* check that /foo/boss/executives/non_subtree_object is not handled by the
+   * subtree handlers - we can do this because objects from subtree handlers
+   * has exactly one interface and non_subtree_object has two
+   */
+  g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/non_subtree_object"), ==, 5);
+  g_assert (has_interface (c, "/foo/boss/executives/non_subtree_object", foo_interface_info.name));
+  g_assert (has_interface (c, "/foo/boss/executives/non_subtree_object", bar_interface_info.name));
+  /* check that the vp and evp objects are handled by the subtree handlers */
+  g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/vp0"), ==, 4);
+  g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/vp1"), ==, 4);
+  g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/evp0"), ==, 4);
+  g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/evp1"), ==, 4);
+  g_assert (has_interface (c, "/foo/boss/executives/vp0", foo_interface_info.name));
+  g_assert (has_interface (c, "/foo/boss/executives/vp1", foo_interface_info.name));
+  g_assert (has_interface (c, "/foo/boss/executives/evp0", bar_interface_info.name));
+  g_assert (has_interface (c, "/foo/boss/executives/evp1", bar_interface_info.name));
+  g_strfreev (nodes);
+  data.num_subtree_nodes = 3;
+  nodes = get_nodes_at (c, "/foo/boss/executives");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 7);
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp0"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp1"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp2"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp0"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp1"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp2"));
+  g_strfreev (nodes);
+
+  /* check that unregistering the subtree handler works */
+  g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 1);
+  g_assert (e_dbus_connection_unregister_subtree (c, subtree_registration_id));
+  g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 2);
+  nodes = get_nodes_at (c, "/foo/boss/executives");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 1);
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
+  g_strfreev (nodes);
+
+  /* check we catch the object from the other "binding" */
+  nodes = get_nodes_at (c, "/foo/boss/interns");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 4);
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "intern1"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "intern2"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "intern3"));
+  g_assert (_g_strv_has_string ((const gchar* const *) nodes, "other_intern")); /* from the other "binding" */
+  g_strfreev (nodes);
+
+  g_assert (e_dbus_connection_unregister_object (c, boss_foo_reg_id));
+  g_assert (e_dbus_connection_unregister_object (c, boss_bar_reg_id));
+  g_assert (e_dbus_connection_unregister_object (c, worker1_foo_reg_id));
+  g_assert (e_dbus_connection_unregister_object (c, worker2_bar_reg_id));
+  g_assert (e_dbus_connection_unregister_object (c, intern1_foo_reg_id));
+  g_assert (e_dbus_connection_unregister_object (c, intern2_bar_reg_id));
+  g_assert (e_dbus_connection_unregister_object (c, intern2_foo_reg_id));
+  g_assert (e_dbus_connection_unregister_object (c, intern3_bar_reg_id));
+  g_assert (e_dbus_connection_unregister_object (c, non_subtree_object_path_bar_reg_id));
+  g_assert (e_dbus_connection_unregister_object (c, non_subtree_object_path_foo_reg_id));
+
+  g_assert_cmpint (data.num_unregistered_calls, ==, num_successful_registrations);
+
+  /* Shutdown the other "binding" */
+  g_assert (dbus_connection_unregister_object_path (dc, "/foo/boss/interns/other_intern"));
+  dbus_connection_unref (dc);
+
+  /* check that we no longer export any objects - TODO: it looks like there's a bug in
+   * libdbus-1 here: libdbus still reports the '/foo' object; so disable the test for now
+   */
+#if 0
+  nodes = get_nodes_at (c, "/");
+  g_assert (nodes != NULL);
+  g_assert_cmpint (g_strv_length (nodes), ==, 0);
+  g_strfreev (nodes);
+#endif
+
+  /* To prevent from exiting and attaching a DBus tool like D-Feet; uncomment: */
+  /*g_info ("Point D-feet or other tool at: %s", session_bus_get_temporary_address());
+  g_main_loop_run (loop);*/
+
+  g_object_unref (c);
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  gint ret;
+
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  /* all the tests rely on a shared main loop */
+  loop = g_main_loop_new (NULL, FALSE);
+
+  /* all the tests use a session bus with a well-known address that we can bring up and down
+   * using session_bus_up() and session_bus_down().
+   */
+  g_unsetenv ("DISPLAY");
+  g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+  session_bus_up ();
+
+  /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
+   * until one can connect to the bus but that's not how things work right now
+   */
+  usleep (500 * 1000);
+
+  g_test_add_func ("/gdbus/object-registration", test_object_registration);
+  /* TODO: check that we spit out correct introspection data */
+  /* TODO: check that registering a whole subtree works */
+
+  ret = g_test_run();
+
+  /* tear down bus */
+  session_bus_down ();
+
+  return ret;
+}
diff --git a/edbus/tests/introspection.c b/edbus/tests/introspection.c
new file mode 100644
index 0000000..388139c
--- /dev/null
+++ b/edbus/tests/introspection.c
@@ -0,0 +1,163 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <edbus/edbus.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test introspection parser */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+introspection_on_proxy_appeared (EDBusConnection *connection,
+                                 const gchar     *name,
+                                 const gchar     *name_owner,
+                                 EDBusProxy      *proxy,
+                                 gpointer         user_data)
+{
+  GError *error;
+  const gchar *xml_data;
+  EDBusNodeInfo *node_info;
+  const EDBusInterfaceInfo *interface_info;
+  const EDBusMethodInfo *method_info;
+  const EDBusSignalInfo *signal_info;
+  EVariant *result;
+
+  error = NULL;
+
+  /**
+   * Invoke Introspect(), then parse the output.
+   */
+  result = e_dbus_proxy_invoke_method_sync (proxy,
+                                            "org.freedesktop.DBus.Introspectable.Introspect",
+                                            NULL,
+                                            -1,
+                                            NULL,
+                                            &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  e_variant_get (result, "(s)", &xml_data);
+
+  node_info = e_dbus_node_info_new_for_xml (xml_data, &error);
+  g_assert_no_error (error);
+  g_assert (node_info != NULL);
+
+  /* for now we only check a couple of things. TODO: check more things */
+
+  interface_info = e_dbus_node_info_lookup_interface (node_info, "com.example.NonExistantInterface");
+  g_assert (interface_info == NULL);
+
+  interface_info = e_dbus_node_info_lookup_interface (node_info, "org.freedesktop.DBus.Introspectable");
+  g_assert (interface_info != NULL);
+  method_info = e_dbus_interface_info_lookup_method (interface_info, "NonExistantMethod");
+  g_assert (method_info == NULL);
+  method_info = e_dbus_interface_info_lookup_method (interface_info, "Introspect");
+  g_assert (method_info != NULL);
+  g_assert_cmpstr (method_info->in_signature, ==, "");
+  g_assert_cmpint (method_info->in_num_args, ==, 0);
+  g_assert (method_info->in_args == NULL);
+  g_assert_cmpstr (method_info->out_signature, ==, "s");
+  g_assert_cmpint (method_info->out_num_args, ==, 1);
+  g_assert (method_info->out_args != NULL);
+  g_assert (method_info->out_args[0].name != NULL);
+  g_assert_cmpstr (method_info->out_args[0].signature, ==, "s");
+
+  interface_info = e_dbus_node_info_lookup_interface (node_info, "com.example.Frob");
+  g_assert (interface_info != NULL);
+  signal_info = e_dbus_interface_info_lookup_signal (interface_info, "TestSignal");
+  g_assert (signal_info != NULL);
+  g_assert_cmpstr (signal_info->signature, ==, "sov");
+
+  e_dbus_node_info_free (node_info);
+  e_variant_unref (result);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+introspection_on_proxy_vanished (EDBusConnection *connection,
+                                 const gchar     *name,
+                                 gpointer         user_data)
+{
+}
+
+static void
+test_introspection_parser (void)
+{
+  guint watcher_id;
+
+  session_bus_up ();
+
+  watcher_id = e_bus_watch_proxy (G_BUS_TYPE_SESSION,
+                                  "com.example.TestService",
+                                  "/com/example/TestObject",
+                                  "com.example.Frob",
+                                  E_TYPE_DBUS_PROXY,
+                                  E_DBUS_PROXY_FLAGS_NONE,
+                                  introspection_on_proxy_appeared,
+                                  introspection_on_proxy_vanished,
+                                  NULL,
+                                  NULL);
+
+  /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
+   * until one can connect to the bus but that's not how things work right now
+   */
+  usleep (500 * 1000);
+  /* this is safe; testserver will exit once the bus goes away */
+  g_assert (g_spawn_command_line_async ("./testserver.py", NULL));
+
+  g_main_loop_run (loop);
+
+  e_bus_unwatch_proxy (watcher_id);
+
+  /* tear down bus */
+  session_bus_down ();
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  /* all the tests rely on a shared main loop */
+  loop = g_main_loop_new (NULL, FALSE);
+
+  /* all the tests use a session bus with a well-known address that we can bring up and down
+   * using session_bus_up() and session_bus_down().
+   */
+  g_unsetenv ("DISPLAY");
+  g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+  g_test_add_func ("/gdbus/introspection-parser", test_introspection_parser);
+  return g_test_run();
+}
diff --git a/edbus/tests/names.c b/edbus/tests/names.c
new file mode 100644
index 0000000..e2e53ce
--- /dev/null
+++ b/edbus/tests/names.c
@@ -0,0 +1,651 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <edbus/edbus.h>
+#include <unistd.h>
+
+#include "tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that e_bus_own_name() works correctly */
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GMainLoop *loop;
+  gboolean expect_null_connection;
+  guint num_acquired;
+  guint num_lost;
+  guint num_free_func;
+} OwnNameData;
+
+static void
+own_name_data_free_func (OwnNameData *data)
+{
+  data->num_free_func++;
+  g_main_loop_quit (loop);
+}
+
+static void
+name_acquired_handler (EDBusConnection *connection,
+                       const gchar     *name,
+                       gpointer         user_data)
+{
+  OwnNameData *data = user_data;
+  e_dbus_connection_set_exit_on_disconnect (connection, FALSE);
+  data->num_acquired += 1;
+  g_main_loop_quit (loop);
+}
+
+static void
+name_lost_handler (EDBusConnection *connection,
+                   const gchar     *name,
+                   gpointer         user_data)
+{
+  OwnNameData *data = user_data;
+  if (data->expect_null_connection)
+    {
+      g_assert (connection == NULL);
+    }
+  else
+    {
+      g_assert (connection != NULL);
+      e_dbus_connection_set_exit_on_disconnect (connection, FALSE);
+    }
+  data->num_lost += 1;
+  g_main_loop_quit (loop);
+}
+
+static void
+test_bus_own_name (void)
+{
+  guint id;
+  guint id2;
+  OwnNameData data;
+  OwnNameData data2;
+  const gchar *name;
+  EDBusConnection *c;
+  GError *error;
+  gboolean name_has_owner_reply;
+  EDBusConnection *c2;
+  EVariant *result;
+
+  error = NULL;
+  name = "org.gtk.EDBus.Name1";
+
+  /**
+   * First check that name_lost_handler() is invoked if there is no bus.
+   *
+   * Also make sure name_lost_handler() isn't invoked when unowning the name.
+   */
+  data.num_free_func = 0;
+  data.num_acquired = 0;
+  data.num_lost = 0;
+  data.expect_null_connection = TRUE;
+  id = e_bus_own_name (G_BUS_TYPE_SESSION,
+                       name,
+                       G_BUS_NAME_OWNER_FLAGS_NONE,
+                       name_acquired_handler,
+                       name_lost_handler,
+                       &data,
+                       (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data.num_acquired, ==, 0);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 0);
+  g_assert_cmpint (data.num_lost,     ==, 1);
+  e_bus_unown_name (id);
+  g_assert_cmpint (data.num_acquired, ==, 0);
+  g_assert_cmpint (data.num_lost,     ==, 1);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_free_func, ==, 1);
+
+  /**
+   * Bring up a bus, then own a name and check name_acquired_handler() is invoked.
+   */
+  session_bus_up ();
+  data.num_acquired = 0;
+  data.num_lost = 0;
+  data.expect_null_connection = FALSE;
+  id = e_bus_own_name (G_BUS_TYPE_SESSION,
+                       name,
+                       G_BUS_NAME_OWNER_FLAGS_NONE,
+                       name_acquired_handler,
+                       name_lost_handler,
+                       &data,
+                       (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data.num_acquired, ==, 0);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+
+  /**
+   * Check that the name was actually acquired.
+   */
+  c = e_dbus_connection_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+  g_assert (c != NULL);
+  g_assert (!e_dbus_connection_get_is_disconnected (c));
+  result = e_dbus_connection_invoke_method_sync (c,
+                                                 "org.freedesktop.DBus",  /* bus name */
+                                                 "/org/freedesktop/DBus", /* object path */
+                                                 "org.freedesktop.DBus",  /* interface name */
+                                                 "NameHasOwner",          /* method name */
+                                                 e_variant_new ("(s)", name),
+                                                 -1,
+                                                 NULL,
+                                                 &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  e_variant_get (result, "(b)", &name_has_owner_reply);
+  g_assert (name_has_owner_reply);
+  e_variant_unref (result);
+
+  /**
+   * Stop owning the name - this should trigger name_lost_handler()
+   * (in an idle handler) from e_bus_unown_name().
+   */
+  data.expect_null_connection = FALSE;
+  e_bus_unown_name (id);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 1);
+  g_assert_cmpint (data.num_free_func, ==, 2);
+
+  /**
+   * Check that the name was actually relased.
+   */
+  result = e_dbus_connection_invoke_method_sync (c,
+                                                 "org.freedesktop.DBus",  /* bus name */
+                                                 "/org/freedesktop/DBus", /* object path */
+                                                 "org.freedesktop.DBus",  /* interface name */
+                                                 "NameHasOwner",          /* method name */
+                                                 e_variant_new ("(s)", name),
+                                                 -1,
+                                                 NULL,
+                                                 &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  e_variant_get (result, "(b)", &name_has_owner_reply);
+  g_assert (!name_has_owner_reply);
+  e_variant_unref (result);
+
+  /**
+   * Own the name again.
+   */
+  data.num_acquired = 0;
+  data.num_lost = 0;
+  data.expect_null_connection = FALSE;
+  id = e_bus_own_name (G_BUS_TYPE_SESSION,
+                       name,
+                       G_BUS_NAME_OWNER_FLAGS_NONE,
+                       name_acquired_handler,
+                       name_lost_handler,
+                       &data,
+                       (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data.num_acquired, ==, 0);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+
+  /**
+   * Try owning the name with another object on the same connection  - this should
+   * fail because we already own the name.
+   */
+  data2.num_free_func = 0;
+  data2.num_acquired = 0;
+  data2.num_lost = 0;
+  data2.expect_null_connection = FALSE;
+  id2 = e_bus_own_name (G_BUS_TYPE_SESSION,
+                        name,
+                        G_BUS_NAME_OWNER_FLAGS_NONE,
+                        name_acquired_handler,
+                        name_lost_handler,
+                        &data2,
+                        (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  e_bus_unown_name (id2);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data2.num_free_func, ==, 1);
+
+  /**
+   * Create a secondary (e.g. private) connection and try owning the name on that
+   * connection. This should fail both with and without _REPLACE because we
+   * didn't specify ALLOW_REPLACEMENT.
+   */
+  c2 = e_dbus_connection_bus_get_private_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+  g_assert (c2 != NULL);
+  g_assert (!e_dbus_connection_get_is_disconnected (c2));
+  /* first without _REPLACE */
+  data2.num_acquired = 0;
+  data2.num_lost = 0;
+  data2.expect_null_connection = FALSE;
+  data2.num_free_func = 0;
+  id2 = e_bus_own_name_on_connection (c2,
+                                      name,
+                                      G_BUS_NAME_OWNER_FLAGS_NONE,
+                                      name_acquired_handler,
+                                      name_lost_handler,
+                                      &data2,
+                                      (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  e_bus_unown_name (id2);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data2.num_free_func, ==, 1);
+  /* then with _REPLACE */
+  data2.num_acquired = 0;
+  data2.num_lost = 0;
+  data2.expect_null_connection = FALSE;
+  data2.num_free_func = 0;
+  id2 = e_bus_own_name_on_connection (c2,
+                                      name,
+                                      G_BUS_NAME_OWNER_FLAGS_REPLACE,
+                                      name_acquired_handler,
+                                      name_lost_handler,
+                                      &data2,
+                                      (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  e_bus_unown_name (id2);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data2.num_free_func, ==, 1);
+
+  /**
+   * Stop owning the name and grab it again with _ALLOW_REPLACEMENT.
+   */
+  data.expect_null_connection = FALSE;
+  e_bus_unown_name (id);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 1);
+  g_assert_cmpint (data.num_free_func, ==, 3);
+  /* grab it again */
+  data.num_acquired = 0;
+  data.num_lost = 0;
+  data.expect_null_connection = FALSE;
+  id = e_bus_own_name (G_BUS_TYPE_SESSION,
+                       name,
+                       G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT,
+                       name_acquired_handler,
+                       name_lost_handler,
+                       &data,
+                       (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data.num_acquired, ==, 0);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+
+  /**
+   * Now try to grab the name from the secondary connection.
+   *
+   */
+  /* first without _REPLACE - this won't make us acquire the name */
+  data2.num_acquired = 0;
+  data2.num_lost = 0;
+  data2.expect_null_connection = FALSE;
+  data2.num_free_func = 0;
+  id2 = e_bus_own_name_on_connection (c2,
+                                      name,
+                                      G_BUS_NAME_OWNER_FLAGS_NONE,
+                                      name_acquired_handler,
+                                      name_lost_handler,
+                                      &data2,
+                                      (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  e_bus_unown_name (id2);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data2.num_free_func, ==, 1);
+  /* then with _REPLACE - here we should acquire the name - e.g. owner should lose it
+   * and owner2 should acquire it  */
+  data2.num_acquired = 0;
+  data2.num_lost = 0;
+  data2.expect_null_connection = FALSE;
+  data2.num_free_func = 0;
+  id2 = e_bus_own_name_on_connection (c2,
+                                      name,
+                                      G_BUS_NAME_OWNER_FLAGS_REPLACE,
+                                      name_acquired_handler,
+                                      name_lost_handler,
+                                      &data2,
+                                      (GDestroyNotify) own_name_data_free_func);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  g_assert_cmpint (data2.num_acquired, ==, 0);
+  g_assert_cmpint (data2.num_lost,     ==, 0);
+  /* wait for handlers for both owner and owner2 to fire */
+  g_main_loop_run (loop);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 1);
+  g_assert_cmpint (data2.num_acquired, ==, 1);
+  g_assert_cmpint (data2.num_lost,     ==, 0);
+  /* ok, make owner2 release the name - then wait for owner to automagically reacquire it */
+  e_bus_unown_name (id2);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data2.num_acquired, ==, 1);
+  g_assert_cmpint (data2.num_lost,     ==, 1);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data2.num_free_func, ==, 1);
+  g_assert_cmpint (data.num_acquired, ==, 2);
+  g_assert_cmpint (data.num_lost,     ==, 1);
+
+  /**
+   * Finally, nuke the bus and check name_lost_handler() is invoked.
+   *
+   */
+  data.expect_null_connection = TRUE;
+  session_bus_down ();
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 2);
+  g_assert_cmpint (data.num_lost,     ==, 2);
+  e_bus_unown_name (id);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_free_func, ==, 4);
+
+  g_object_unref (c);
+  g_object_unref (c2);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that e_bus_watch_name() works correctly */
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  gboolean expect_null_connection;
+  guint num_acquired;
+  guint num_lost;
+  guint num_appeared;
+  guint num_vanished;
+  guint num_free_func;
+} WatchNameData;
+
+static void
+watch_name_data_free_func (WatchNameData *data)
+{
+  data->num_free_func++;
+  g_main_loop_quit (loop);
+}
+
+static void
+w_name_acquired_handler (EDBusConnection *connection,
+                         const gchar     *name,
+                         gpointer         user_data)
+{
+  WatchNameData *data = user_data;
+  data->num_acquired += 1;
+  g_main_loop_quit (loop);
+}
+
+static void
+w_name_lost_handler (EDBusConnection *connection,
+                     const gchar     *name,
+                     gpointer         user_data)
+{
+  WatchNameData *data = user_data;
+  data->num_lost += 1;
+  g_main_loop_quit (loop);
+}
+
+static void
+name_appeared_handler (EDBusConnection *connection,
+                       const gchar     *name,
+                       const gchar     *name_owner,
+                       gpointer         user_data)
+{
+  WatchNameData *data = user_data;
+  if (data->expect_null_connection)
+    {
+      g_assert (connection == NULL);
+    }
+  else
+    {
+      g_assert (connection != NULL);
+      e_dbus_connection_set_exit_on_disconnect (connection, FALSE);
+    }
+  data->num_appeared += 1;
+  g_main_loop_quit (loop);
+}
+
+static void
+name_vanished_handler (EDBusConnection *connection,
+                       const gchar     *name,
+                       gpointer         user_data)
+{
+  WatchNameData *data = user_data;
+  if (data->expect_null_connection)
+    {
+      g_assert (connection == NULL);
+    }
+  else
+    {
+      g_assert (connection != NULL);
+      e_dbus_connection_set_exit_on_disconnect (connection, FALSE);
+    }
+  data->num_vanished += 1;
+  g_main_loop_quit (loop);
+}
+
+static void
+test_bus_watch_name (void)
+{
+  WatchNameData data;
+  guint id;
+  guint owner_id;
+
+  /**
+   * First check that name_vanished_handler() is invoked if there is no bus.
+   *
+   * Also make sure name_vanished_handler() isn't invoked when unwatching the name.
+   */
+  data.num_free_func = 0;
+  data.num_appeared = 0;
+  data.num_vanished = 0;
+  data.expect_null_connection = TRUE;
+  id = e_bus_watch_name (G_BUS_TYPE_SESSION,
+                         "org.gtk.EDBus.Name1",
+                         name_appeared_handler,
+                         name_vanished_handler,
+                         &data,
+                         (GDestroyNotify) watch_name_data_free_func);
+  g_assert_cmpint (data.num_appeared, ==, 0);
+  g_assert_cmpint (data.num_vanished, ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_appeared, ==, 0);
+  g_assert_cmpint (data.num_vanished, ==, 1);
+  e_bus_unwatch_name (id);
+  g_assert_cmpint (data.num_appeared, ==, 0);
+  g_assert_cmpint (data.num_vanished, ==, 1);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_free_func, ==, 1);
+
+  /**
+   * Now bring up a bus, own a name, and then start watching it.
+   */
+  session_bus_up ();
+  /* own the name */
+  data.num_free_func = 0;
+  data.num_acquired = 0;
+  data.num_lost = 0;
+  data.expect_null_connection = FALSE;
+  owner_id = e_bus_own_name (G_BUS_TYPE_SESSION,
+                             "org.gtk.EDBus.Name1",
+                             G_BUS_NAME_OWNER_FLAGS_NONE,
+                             w_name_acquired_handler,
+                             w_name_lost_handler,
+                             &data,
+                             (GDestroyNotify) watch_name_data_free_func);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  /* now watch the name */
+  data.num_appeared = 0;
+  data.num_vanished = 0;
+  id = e_bus_watch_name (G_BUS_TYPE_SESSION,
+                         "org.gtk.EDBus.Name1",
+                         name_appeared_handler,
+                         name_vanished_handler,
+                         &data,
+                         (GDestroyNotify) watch_name_data_free_func);
+  g_assert_cmpint (data.num_appeared, ==, 0);
+  g_assert_cmpint (data.num_vanished, ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_appeared, ==, 1);
+  g_assert_cmpint (data.num_vanished, ==, 0);
+
+  /**
+   * Unwatch the name - this should trigger name_vanished_handler() because of this
+   * guarantee
+   *
+   *   If the name being watched currently has an owner the name (e.g. @name_appeared_handler
+   *   was the last handler to be invoked), then @name_vanished_handler will be invoked
+   *   before this function returns.
+   *
+   * in e_bus_unwatch_name().
+   */
+  e_bus_unwatch_name (id);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_appeared, ==, 1);
+  g_assert_cmpint (data.num_vanished, ==, 1);
+  g_assert_cmpint (data.num_free_func, ==, 1);
+
+  /* unown the name */
+  e_bus_unown_name (owner_id);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 1);
+  g_assert_cmpint (data.num_free_func, ==, 2);
+
+  /**
+   * Create a watcher and then make a name be owned.
+   *
+   * This should trigger name_appeared_handler() ...
+   */
+  /* watch the name */
+  data.num_appeared = 0;
+  data.num_vanished = 0;
+  data.num_free_func = 0;
+  id = e_bus_watch_name (G_BUS_TYPE_SESSION,
+                         "org.gtk.EDBus.Name1",
+                         name_appeared_handler,
+                         name_vanished_handler,
+                         &data,
+                         (GDestroyNotify) watch_name_data_free_func);
+  g_assert_cmpint (data.num_appeared, ==, 0);
+  g_assert_cmpint (data.num_vanished, ==, 0);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_appeared, ==, 0);
+  g_assert_cmpint (data.num_vanished, ==, 1);
+
+  /* own the name */
+  data.num_acquired = 0;
+  data.num_lost = 0;
+  data.expect_null_connection = FALSE;
+  owner_id = e_bus_own_name (G_BUS_TYPE_SESSION,
+                             "org.gtk.EDBus.Name1",
+                             G_BUS_NAME_OWNER_FLAGS_NONE,
+                             w_name_acquired_handler,
+                             w_name_lost_handler,
+                             &data,
+                             (GDestroyNotify) watch_name_data_free_func);
+  g_main_loop_run (loop);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_acquired, ==, 1);
+  g_assert_cmpint (data.num_lost,     ==, 0);
+  g_assert_cmpint (data.num_appeared, ==, 1);
+  g_assert_cmpint (data.num_vanished, ==, 1);
+
+  /**
+   * Nuke the bus and check that the name vanishes and is lost.
+   */
+  data.expect_null_connection = TRUE;
+  session_bus_down ();
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_lost,     ==, 1);
+  g_assert_cmpint (data.num_vanished, ==, 2);
+
+  e_bus_unwatch_name (id);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_free_func, ==, 1);
+
+  e_bus_unown_name (owner_id);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_free_func, ==, 2);
+
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  gint ret;
+
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  loop = g_main_loop_new (NULL, FALSE);
+
+  /* all the tests use a session bus with a well-known address that we can bring up and down
+   * using session_bus_up() and session_bus_down().
+   */
+  g_unsetenv ("DISPLAY");
+  g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+  g_test_add_func ("/gdbus/bus-own-name", test_bus_own_name);
+  g_test_add_func ("/gdbus/bus-watch-name", test_bus_watch_name);
+
+  ret = g_test_run();
+
+  g_main_loop_unref (loop);
+
+  return ret;
+}
diff --git a/edbus/tests/peer.c b/edbus/tests/peer.c
new file mode 100644
index 0000000..3c2855e
--- /dev/null
+++ b/edbus/tests/peer.c
@@ -0,0 +1,448 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <edbus/edbus.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "tests.h"
+
+static gchar *test_address = NULL;
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that peer-to-peer connections work */
+/* ---------------------------------------------------------------------------------------------------- */
+
+
+typedef struct
+{
+  gboolean accept_connection;
+  gint num_connection_attempts;
+  GPtrArray *current_connections;
+  guint num_method_calls;
+} PeerData;
+
+static const EDBusArgInfo test_interface_hello_peer_method_in_args[] =
+{
+  {"greeting", "s", NULL}
+};
+
+static const EDBusArgInfo test_interface_hello_peer_method_out_args[] =
+{
+  {"response", "s", NULL}
+};
+
+static const EDBusMethodInfo test_interface_method_info[] =
+{
+  {
+    "HelloPeer",
+    "s", 1, test_interface_hello_peer_method_in_args,
+    "s", 1, test_interface_hello_peer_method_out_args,
+    NULL
+  },
+  {
+    "EmitSignal",
+    "", 0, NULL,
+    "", 0, NULL,
+    NULL
+  }
+};
+
+static const EDBusArgInfo test_interface_peer_signal_args[] =
+{
+  {"a_string", "s", NULL}
+};
+
+static const EDBusSignalInfo test_interface_signal_info[] =
+{
+  {
+    "PeerSignal",
+    "s", 1, test_interface_peer_signal_args,
+    NULL
+  }
+};
+
+static const EDBusPropertyInfo test_interface_property_info[] =
+{
+  {
+    "PeerProperty",
+    "s", E_DBUS_PROPERTY_INFO_FLAGS_READABLE,
+    NULL
+  }
+};
+
+static const EDBusInterfaceInfo test_interface_introspection_data =
+{
+  "org.gtk.EDBus.PeerTestInterface",
+  2, test_interface_method_info,
+  1, test_interface_signal_info,
+  1, test_interface_property_info,
+  NULL,
+};
+
+static void
+test_interface_method_call (EDBusConnection       *connection,
+                            gpointer               user_data,
+                            const gchar           *sender,
+                            const gchar           *object_path,
+                            const gchar           *interface_name,
+                            const gchar           *method_name,
+                            EVariant              *parameters,
+                            EDBusMethodInvocation *invocation)
+{
+  PeerData *data = user_data;
+
+  g_assert_cmpstr (object_path, ==, "/org/gtk/EDBus/PeerTestObject");
+  g_assert_cmpstr (interface_name, ==, "org.gtk.EDBus.PeerTestInterface");
+
+  if (g_strcmp0 (method_name, "HelloPeer") == 0)
+    {
+      const gchar *greeting;
+      gchar *response;
+
+      e_variant_get (parameters, "(s)", &greeting);
+
+      response = g_strdup_printf ("You greeted me with '%s'.",
+                                  greeting);
+      e_dbus_method_invocation_return_value (invocation,
+                                             e_variant_new ("(s)", response));
+      g_free (response);
+    }
+  else if (g_strcmp0 (method_name, "EmitSignal") == 0)
+    {
+      GError *error;
+
+      error = NULL;
+      e_dbus_connection_emit_signal (connection,
+                                     NULL,
+                                     "/org/gtk/EDBus/PeerTestObject",
+                                     "org.gtk.EDBus.PeerTestInterface",
+                                     "PeerSignal",
+                                     NULL,
+                                     &error);
+      g_assert_no_error (error);
+      e_dbus_method_invocation_return_value (invocation, NULL);
+    }
+  else
+    {
+      g_assert_not_reached ();
+    }
+
+  data->num_method_calls++;
+}
+
+static EVariant *
+test_interface_get_property (EDBusConnection  *connection,
+                             gpointer          user_data,
+                             const gchar      *sender,
+                             const gchar      *object_path,
+                             const gchar      *interface_name,
+                             const gchar      *property_name,
+                             GError          **error)
+{
+  g_assert_cmpstr (object_path, ==, "/org/gtk/EDBus/PeerTestObject");
+  g_assert_cmpstr (interface_name, ==, "org.gtk.EDBus.PeerTestInterface");
+  g_assert_cmpstr (property_name, ==, "PeerProperty");
+
+  return e_variant_new_string ("ThePropertyValue");
+}
+
+
+static const EDBusInterfaceVTable test_interface_vtable =
+{
+  test_interface_method_call,
+  test_interface_get_property,
+  NULL  /* set_property */
+};
+
+static void
+on_new_connection (EDBusServer      *server,
+                   EDBusConnection  *connection,
+                   gpointer          user_data)
+{
+  PeerData *data = user_data;
+
+  data->num_connection_attempts++;
+
+  if (data->accept_connection)
+    {
+      GError *error;
+      guint reg_id;
+
+      g_ptr_array_add (data->current_connections, g_object_ref (connection));
+
+      /* export object on the newly established connection */
+      error = NULL;
+      reg_id = e_dbus_connection_register_object (connection,
+                                                  "/org/gtk/EDBus/PeerTestObject",
+                                                  "org.gtk.EDBus.PeerTestInterface",
+                                                  &test_interface_introspection_data,
+                                                  &test_interface_vtable,
+                                                  data,
+                                                  NULL, /* GDestroyNotify for data */
+                                                  &error);
+      g_assert_no_error (error);
+      g_assert (reg_id > 0);
+    }
+  else
+    {
+      /* don't ref the connection */
+    }
+
+  g_main_loop_quit (loop);
+}
+
+static void
+new_proxy_cb (GObject      *source_object,
+              GAsyncResult *res,
+              gpointer      user_data)
+{
+  EDBusProxy **proxy = user_data;
+  GError *error;
+
+  error = NULL;
+  *proxy = e_dbus_proxy_new_finish (res, &error);
+  g_assert_no_error (error);
+  g_assert (*proxy != NULL);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+hello_peer_cb (EDBusProxy   *proxy,
+               GAsyncResult *res,
+               gpointer      user_data)
+{
+  GError *error;
+  EVariant *result;
+  const gchar *s;
+
+  error = NULL;
+  result = e_dbus_proxy_invoke_method_finish (proxy, res, &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  e_variant_get (result, "(s)", &s);
+  g_assert_cmpstr (s, ==, "You greeted me with 'Hey Peer!'.");
+  e_variant_unref (result);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+on_proxy_signal_received (EDBusProxy *proxy,
+                          gchar      *sender_name,
+                          gchar      *signal_name,
+                          EVariant   *parameters,
+                          gpointer    user_data)
+{
+  g_assert (sender_name == NULL);
+  g_assert_cmpstr (signal_name, ==, "PeerSignal");
+  g_main_loop_quit (loop);
+}
+
+static void
+test_peer (void)
+{
+  EDBusServer *server;
+  EDBusConnection *c;
+  EDBusConnection *c2;
+  EDBusProxy *proxy;
+  GError *error;
+  PeerData data;
+  EVariant *value;
+
+  error = NULL;
+  data.num_connection_attempts = 0;
+  data.current_connections = g_ptr_array_new_with_free_func (g_object_unref);
+  data.num_method_calls = 0;
+
+  /* first try to connect when there is no server */
+  c = e_dbus_connection_new_sync (test_address,
+                                  NULL,
+                                  &error);
+  g_assert_error (error, E_DBUS_ERROR, E_DBUS_ERROR_FILE_NOT_FOUND);
+  g_assert (!e_dbus_error_is_remote_error (error));
+  g_clear_error (&error);
+  g_assert (c == NULL);
+
+  /* bring up a server */
+  server = e_dbus_server_new (test_address, &error);
+  g_assert_no_error (error);
+  g_assert (server != NULL);
+
+  g_signal_connect (server,
+                    "new-connection",
+                    G_CALLBACK (on_new_connection),
+                    &data);
+
+  /* bring up a connection and accept it */
+  data.accept_connection = TRUE;
+  c = e_dbus_connection_new_sync (test_address,
+                                  NULL,
+                                  &error);
+  g_assert_no_error (error);
+  g_assert (c != NULL);
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.current_connections->len, ==, 1);
+  g_assert_cmpint (data.num_connection_attempts, ==, 1);
+  g_assert (e_dbus_connection_get_bus_type (c) == G_BUS_TYPE_NONE);
+  g_assert (e_dbus_connection_get_unique_name (c) == NULL);
+  g_assert_cmpstr (e_dbus_connection_get_address (c), ==, test_address);
+
+  /* check that we create a proxy, read properties, receive signals and invoke the HelloPeer() method
+   *
+   * Need to do this async to avoid deadlock.
+   */
+  proxy = NULL;
+  e_dbus_proxy_new (c,
+                    E_TYPE_DBUS_PROXY,
+                    E_DBUS_PROXY_FLAGS_NONE,
+                    NULL, /* bus_name */
+                    "/org/gtk/EDBus/PeerTestObject",
+                    "org.gtk.EDBus.PeerTestInterface",
+                    NULL, /* GCancellable */
+                    (GAsyncReadyCallback) new_proxy_cb,
+                    &proxy);
+  g_main_loop_run (loop);
+  g_assert (proxy != NULL);
+  value = e_dbus_proxy_get_cached_property (proxy, "PeerProperty", &error);
+  g_assert_cmpstr (e_variant_get_string (value, NULL), ==, "ThePropertyValue");
+
+  /* try invoking a method - again, async */
+  e_dbus_proxy_invoke_method (proxy,
+                              "HelloPeer",
+                              e_variant_new ("(s)", "Hey Peer!"),
+                              -1,
+                              NULL,  /* GCancellable */
+                              (GAsyncReadyCallback) hello_peer_cb,
+                              NULL); /* user_data */
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_method_calls, ==, 1);
+
+  /* make the other peer emit a signal - catch it */
+  g_signal_connect (proxy,
+                    "g-signal",
+                    G_CALLBACK (on_proxy_signal_received),
+                    NULL);
+  e_dbus_proxy_invoke_method (proxy,
+                              "EmitSignal",
+                              NULL,  /* no arguments */
+                              -1,
+                              NULL,  /* GCancellable */
+                              NULL,  /* GAsyncReadyCallback - we don't care about the result */
+                              NULL); /* user_data */
+  g_main_loop_run (loop);
+  g_object_unref (proxy);
+  g_assert_cmpint (data.num_method_calls, ==, 2);
+
+  /* bring up a connection - don't accept it
+   *
+   * Note that the client will get the connection - but will be disconnected immediately
+   */
+  data.accept_connection = FALSE;
+  c2 = e_dbus_connection_new_sync (test_address,
+                                   NULL,
+                                   &error);
+  g_assert_no_error (error);
+  g_assert (c2 != NULL);
+  g_assert (!e_dbus_connection_get_is_disconnected (c2));
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.current_connections->len, ==, 1);
+  g_assert_cmpint (data.num_connection_attempts, ==, 2);
+  _g_assert_signal_received (c2, "disconnected");
+  g_assert (e_dbus_connection_get_is_disconnected (c2));
+  g_object_unref (c2);
+
+  /* bring up a connection - accept it.. then disconnect from the client side - check
+   * that the server side gets the disconnect signal.
+   */
+  data.accept_connection = TRUE;
+  c2 = e_dbus_connection_new_sync (test_address,
+                                   NULL,
+                                   &error);
+  g_assert_no_error (error);
+  g_assert (c2 != NULL);
+  g_assert (!e_dbus_connection_get_is_disconnected (c2));
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.current_connections->len, ==, 2);
+  g_assert_cmpint (data.num_connection_attempts, ==, 3);
+  g_assert (!e_dbus_connection_get_is_disconnected (E_DBUS_CONNECTION (data.current_connections->pdata[1])));
+  g_object_unref (c2);
+  _g_assert_signal_received (E_DBUS_CONNECTION (data.current_connections->pdata[1]), "disconnected");
+  g_assert (e_dbus_connection_get_is_disconnected (E_DBUS_CONNECTION (data.current_connections->pdata[1])));
+  g_ptr_array_set_size (data.current_connections, 1); /* remove disconnected connection object */
+
+  /* unref the server and stop listening for new connections
+   *
+   * This won't bring down the established connections - check that c is still connected
+   * by invoking a method
+   */
+  g_object_unref (server);
+  e_dbus_proxy_invoke_method (proxy,
+                              "HelloPeer",
+                              e_variant_new ("(s)", "Hey Peer!"),
+                              -1,
+                              NULL,  /* GCancellable */
+                              (GAsyncReadyCallback) hello_peer_cb,
+                              NULL); /* user_data */
+  g_main_loop_run (loop);
+  g_assert_cmpint (data.num_method_calls, ==, 3);
+
+  /* now disconnect from the server side - check that the client side gets the signal */
+  g_assert_cmpint (data.current_connections->len, ==, 1);
+  g_assert (E_DBUS_CONNECTION (data.current_connections->pdata[0]) != c);
+  e_dbus_connection_disconnect (E_DBUS_CONNECTION (data.current_connections->pdata[0]));
+  g_assert (!e_dbus_connection_get_is_disconnected (c));
+  _g_assert_signal_received (c, "disconnected");
+  g_assert (e_dbus_connection_get_is_disconnected (c));
+  g_object_unref (c);
+
+  g_ptr_array_unref (data.current_connections);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  gint ret;
+
+  g_type_init ();
+  g_thread_init (NULL);
+  g_test_init (&argc, &argv, NULL);
+
+  test_address = g_strdup_printf ("unix:path=/tmp/gdbus-test-pid-%d", getpid ());
+
+  /* all the tests rely on a shared main loop */
+  loop = g_main_loop_new (NULL, FALSE);
+
+  g_test_add_func ("/gdbus/peer-to-peer", test_peer);
+
+  ret = g_test_run();
+
+  g_free (test_address);
+  g_main_loop_unref (loop);
+
+  return ret;
+}
diff --git a/edbus/tests/proxy.c b/edbus/tests/proxy.c
new file mode 100644
index 0000000..575ce8d
--- /dev/null
+++ b/edbus/tests/proxy.c
@@ -0,0 +1,387 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <edbus/edbus.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "tests.h"
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that the method aspects of EDBusProxy works */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_methods (EDBusConnection *connection,
+              const gchar     *name,
+              const gchar     *name_owner,
+              EDBusProxy      *proxy)
+{
+  EVariant *result;
+  GError *error;
+  const gchar *str;
+  gchar *dbus_error_name;
+
+  /* check that we can invoke a method */
+  error = NULL;
+  result = e_dbus_proxy_invoke_method_sync (proxy,
+                                            "HelloWorld",
+                                            e_variant_new ("(s)", "Hey"),
+                                            -1,
+                                            NULL,
+                                            &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_assert_cmpstr (e_variant_get_type_string (result), ==, "(s)");
+  e_variant_get (result, "(s)", &str);
+  g_assert_cmpstr (str, ==, "You greeted me with 'Hey'. Thanks!");
+  e_variant_unref (result);
+
+  /* Check that we can completely recover the returned error */
+  result = e_dbus_proxy_invoke_method_sync (proxy,
+                                            "HelloWorld",
+                                            e_variant_new ("(s)", "Yo"),
+                                            -1,
+                                            NULL,
+                                            &error);
+  g_assert_error (error, E_DBUS_ERROR, E_DBUS_ERROR_REMOTE_ERROR);
+  g_assert (e_dbus_error_is_remote_error (error));
+  g_assert (e_dbus_error_is_remote_error (error));
+  g_assert (result == NULL);
+  dbus_error_name = e_dbus_error_get_remote_error (error);
+  g_assert_cmpstr (dbus_error_name, ==, "com.example.TestException");
+  g_free (dbus_error_name);
+  g_assert (e_dbus_error_strip_remote_error (error));
+  g_assert_cmpstr (error->message, ==, "Yo is not a proper greeting");
+  g_clear_error (&error);
+
+  /* Check that we get a timeout if the method handling is taking longer than timeout */
+  error = NULL;
+  result = e_dbus_proxy_invoke_method_sync (proxy,
+                                            "Sleep",
+                                            e_variant_new ("(i)", 500 /* msec */),
+                                            100 /* msec */,
+                                            NULL,
+                                            &error);
+  g_assert_error (error, E_DBUS_ERROR, E_DBUS_ERROR_NO_REPLY);
+  g_assert (e_dbus_error_is_remote_error (error));
+  g_assert (result == NULL);
+  g_clear_error (&error);
+
+  /* Check that proxy-default timeouts work. */
+  g_assert_cmpint (e_dbus_proxy_get_default_timeout (proxy), ==, -1);
+
+  /* the default timeout is 25000 msec so this should work */
+  result = e_dbus_proxy_invoke_method_sync (proxy,
+                                            "Sleep",
+                                            e_variant_new ("(i)", 500 /* msec */),
+                                            -1, /* use proxy default (e.g. -1 -> e.g. 25000 msec) */
+                                            NULL,
+                                            &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_assert_cmpstr (e_variant_get_type_string (result), ==, "()");
+  e_variant_unref (result);
+
+  /* now set the proxy-default timeout to 250 msec and try the 500 msec call - this should FAIL */
+  e_dbus_proxy_set_default_timeout (proxy, 250);
+  g_assert_cmpint (e_dbus_proxy_get_default_timeout (proxy), ==, 250);
+  result = e_dbus_proxy_invoke_method_sync (proxy,
+                                            "Sleep",
+                                            e_variant_new ("(i)", 500 /* msec */),
+                                            -1, /* use proxy default (e.g. 250 msec) */
+                                            NULL,
+                                            &error);
+  g_assert_error (error, E_DBUS_ERROR, E_DBUS_ERROR_NO_REPLY);
+  g_assert (e_dbus_error_is_remote_error (error));
+  g_assert (result == NULL);
+  g_clear_error (&error);
+
+  /* clean up after ourselves */
+  e_dbus_proxy_set_default_timeout (proxy, -1);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that the property aspects of EDBusProxy works */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_properties (EDBusConnection *connection,
+                 const gchar     *name,
+                 const gchar     *name_owner,
+                 EDBusProxy      *proxy)
+{
+  GError *error;
+  EVariant *variant;
+  EVariant *variant2;
+  EVariant *result;
+
+  error = NULL;
+
+  /**
+   * Check that we can read cached properties.
+   *
+   * No need to test all properties - EVariant has already been tested
+   */
+  variant = e_dbus_proxy_get_cached_property (proxy, "y", &error);
+  g_assert_no_error (error);
+  g_assert (variant != NULL);
+  g_assert_cmpint (e_variant_get_byte (variant), ==, 1);
+  e_variant_unref (variant);
+  variant = e_dbus_proxy_get_cached_property (proxy, "o", &error);
+  g_assert_no_error (error);
+  g_assert (variant != NULL);
+  g_assert_cmpstr (e_variant_get_string (variant, NULL), ==, "/some/path");
+  e_variant_unref (variant);
+
+  /**
+   * Now ask the service to change a property and check that #EDBusProxy::g-property-changed
+   * is received. Also check that the cache is updated.
+   */
+  variant2 = e_variant_new_byte (42);
+  result = e_dbus_proxy_invoke_method_sync (proxy,
+                                            "FrobSetProperty",
+                                            e_variant_new ("(sv)",
+                                                           "y",
+                                                           variant2),
+                                            -1,
+                                            NULL,
+                                            &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_assert_cmpstr (e_variant_get_type_string (result), ==, "()");
+  e_variant_unref (result);
+  _g_assert_signal_received (proxy, "g-properties-changed");
+  variant = e_dbus_proxy_get_cached_property (proxy, "y", &error);
+  g_assert_no_error (error);
+  g_assert (variant != NULL);
+  g_assert_cmpint (e_variant_get_byte (variant), ==, 42);
+  e_variant_unref (variant);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Test that the signal aspects of EDBusProxy works */
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+test_proxy_signals_on_signal (EDBusProxy  *proxy,
+                              const gchar *sender_name,
+                              const gchar *signal_name,
+                              EVariant    *parameters,
+                              gpointer     user_data)
+{
+  GString *s = user_data;
+
+  g_assert_cmpstr (signal_name, ==, "TestSignal");
+  g_assert_cmpstr (e_variant_get_type_string (parameters), ==, "(sov)");
+
+  e_variant_print_string (parameters, s, TRUE);
+}
+
+typedef struct
+{
+  GMainLoop *internal_loop;
+  GString *s;
+} TestSignalData;
+
+static void
+test_proxy_signals_on_emit_signal_cb (EDBusProxy   *proxy,
+                                      GAsyncResult *res,
+                                      gpointer      user_data)
+{
+  TestSignalData *data = user_data;
+  GError *error;
+  EVariant *result;
+
+  error = NULL;
+  result = e_dbus_proxy_invoke_method_finish (proxy,
+                                              res,
+                                              &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_assert_cmpstr (e_variant_get_type_string (result), ==, "()");
+  e_variant_unref (result);
+
+  /* check that the signal was recieved before we got the method result */
+  g_assert (strlen (data->s->str) > 0);
+
+  /* break out of the loop */
+  g_main_loop_quit (data->internal_loop);
+}
+
+static void
+test_signals (EDBusConnection *connection,
+              const gchar     *name,
+              const gchar     *name_owner,
+              EDBusProxy      *proxy)
+{
+  GError *error;
+  GString *s;
+  gulong signal_handler_id;
+  TestSignalData data;
+  EVariant *result;
+
+  error = NULL;
+
+  /**
+   * Ask the service to emit a signal and check that we receive it.
+   *
+   * Note that blocking calls don't block in the mainloop so wait for the signal (which
+   * is dispatched before the method reply)
+   */
+  s = g_string_new (NULL);
+  signal_handler_id = g_signal_connect (proxy,
+                                        "g-signal",
+                                        G_CALLBACK (test_proxy_signals_on_signal),
+                                        s);
+
+  result = e_dbus_proxy_invoke_method_sync (proxy,
+                                            "EmitSignal",
+                                            e_variant_new ("(so)",
+                                                           "Accept the next proposition you hear",
+                                                           "/some/path"),
+                                            -1,
+                                            NULL,
+                                            &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_assert_cmpstr (e_variant_get_type_string (result), ==, "()");
+  e_variant_unref (result);
+  /* check that we haven't received the signal just yet */
+  g_assert (strlen (s->str) == 0);
+  /* and now wait for the signal */
+  _g_assert_signal_received (proxy, "g-signal");
+  g_assert_cmpstr (s->str,
+                   ==,
+                   "(\"Accept the next proposition you hear .. in bed!\", objectpath \"/some/path/in/bed\", <\"a variant\">)");
+  g_signal_handler_disconnect (proxy, signal_handler_id);
+  g_string_free (s, TRUE);
+
+  /**
+   * Now do this async to check the signal is received before the method returns.
+   */
+  s = g_string_new (NULL);
+  data.internal_loop = g_main_loop_new (NULL, FALSE);
+  data.s = s;
+  signal_handler_id = g_signal_connect (proxy,
+                                        "g-signal",
+                                        G_CALLBACK (test_proxy_signals_on_signal),
+                                        s);
+  e_dbus_proxy_invoke_method (proxy,
+                              "EmitSignal",
+                              e_variant_new ("(so)",
+                                             "You will make a great programmer",
+                                             "/some/other/path"),
+                              -1,
+                              NULL,
+                              (GAsyncReadyCallback) test_proxy_signals_on_emit_signal_cb,
+                              &data);
+  g_main_loop_run (data.internal_loop);
+  g_main_loop_unref (data.internal_loop);
+  g_assert_cmpstr (s->str,
+                   ==,
+                   "(\"You will make a great programmer .. in bed!\", objectpath \"/some/other/path/in/bed\", <\"a variant\">)");
+  g_signal_handler_disconnect (proxy, signal_handler_id);
+  g_string_free (s, TRUE);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_proxy_appeared (EDBusConnection *connection,
+                   const gchar     *name,
+                   const gchar     *name_owner,
+                   EDBusProxy      *proxy,
+                   gpointer         user_data)
+{
+  test_methods (connection, name, name_owner, proxy);
+  test_properties (connection, name, name_owner, proxy);
+  test_signals (connection, name, name_owner, proxy);
+
+  g_main_loop_quit (loop);
+}
+
+static void
+on_proxy_vanished (EDBusConnection *connection,
+                   const gchar     *name,
+                   gpointer         user_data)
+{
+}
+
+static void
+test_proxy (void)
+{
+  guint watcher_id;
+
+  session_bus_up ();
+
+  /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
+   * until one can connect to the bus but that's not how things work right now
+   */
+  usleep (500 * 1000);
+
+  watcher_id = e_bus_watch_proxy (G_BUS_TYPE_SESSION,
+                                  "com.example.TestService",
+                                  "/com/example/TestObject",
+                                  "com.example.Frob",
+                                  E_TYPE_DBUS_PROXY,
+                                  E_DBUS_PROXY_FLAGS_NONE,
+                                  on_proxy_appeared,
+                                  on_proxy_vanished,
+                                  NULL,
+                                  NULL);
+
+  /* this is safe; testserver will exit once the bus goes away */
+  g_assert (g_spawn_command_line_async ("./testserver.py", NULL));
+
+  g_main_loop_run (loop);
+
+  e_bus_unwatch_proxy (watcher_id);
+
+  /* tear down bus */
+  session_bus_down ();
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  g_type_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  /* all the tests rely on a shared main loop */
+  loop = g_main_loop_new (NULL, FALSE);
+
+  /* all the tests use a session bus with a well-known address that we can bring up and down
+   * using session_bus_up() and session_bus_down().
+   */
+  g_unsetenv ("DISPLAY");
+  g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+  g_test_add_func ("/gdbus/proxy", test_proxy);
+  return g_test_run();
+}
diff --git a/edbus/tests/sessionbus.c b/edbus/tests/sessionbus.c
new file mode 100644
index 0000000..3dde5d2
--- /dev/null
+++ b/edbus/tests/sessionbus.c
@@ -0,0 +1,342 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <stdio.h>
+
+#include "sessionbus.h"
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Utilities for bringing up and tearing down session message bus instances */
+
+static void
+watch_parent (gint fd)
+{
+  GPollFD fds[1];
+  gint num_events;
+  gchar buf[512];
+  gint bytes_read;
+  GArray *buses_to_kill_array;
+
+  fds[0].fd = fd;
+  fds[0].events = G_IO_HUP | G_IO_IN;
+  fds[0].revents = 0;
+
+  buses_to_kill_array = g_array_new (FALSE, TRUE, sizeof (guint));
+
+  do
+    {
+      guint pid;
+      guint n;
+
+      num_events = g_poll (fds, 1, -1);
+      if (num_events == 0)
+        continue;
+
+      if (fds[0].revents == G_IO_HUP)
+        {
+          for (n = 0; n < buses_to_kill_array->len; n++)
+            {
+              pid = g_array_index (buses_to_kill_array, guint, n);
+              g_print ("cleaning up bus with pid %d\n", pid);
+              kill (pid, SIGTERM);
+            }
+          g_array_free (buses_to_kill_array, TRUE);
+          exit (0);
+        }
+
+      //g_debug ("data from parent");
+
+      memset (buf, '\0', sizeof buf);
+    again:
+      bytes_read = read (fds[0].fd, buf, sizeof buf);
+      if (bytes_read < 0 && (errno == EAGAIN || errno == EINTR))
+        goto again;
+
+      if (sscanf (buf, "add %d\n", &pid) == 1)
+        {
+          g_array_append_val (buses_to_kill_array, pid);
+        }
+      else if (sscanf (buf, "remove %d\n", &pid) == 1)
+        {
+          for (n = 0; n < buses_to_kill_array->len; n++)
+            {
+              if (g_array_index (buses_to_kill_array, guint, n) == pid)
+                {
+                  g_array_remove_index (buses_to_kill_array, n);
+                  pid = 0;
+                  break;
+                }
+            }
+          if (pid != 0)
+            {
+              g_warning ("unknown pid %d to remove", pid);
+            }
+        }
+      else
+        {
+          g_warning ("unknown command from parent '%s'", buf);
+        }
+    }
+  while (TRUE);
+
+}
+
+static GHashTable *session_bus_address_to_pid = NULL;
+static gint pipe_fds[2];
+
+const gchar *
+session_bus_up_with_address (const gchar *given_address)
+{
+  gchar *address;
+  int stdout_fd;
+  GError *error;
+  const gchar *argv[] = {"dbus-daemon", "--print-address", "--config-file=foo", NULL};
+  GPid pid;
+  gchar buf[512];
+  ssize_t bytes_read;
+  gchar *config_file_name;
+  gint config_file_fd;
+  GString *config_file_contents;
+
+  address = NULL;
+  error = NULL;
+  config_file_name = NULL;
+  config_file_fd = -1;
+  argv[2] = NULL;
+
+  config_file_fd = g_file_open_tmp ("g-dbus-tests-XXXXXX",
+                                    &config_file_name,
+                                    &error);
+  if (config_file_fd < 0)
+    {
+      g_warning ("Error creating temporary config file: %s", error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  config_file_contents = g_string_new (NULL);
+  g_string_append        (config_file_contents, "<busconfig>\n");
+  g_string_append        (config_file_contents, "  <type>session</type>\n");
+  g_string_append_printf (config_file_contents, "  <listen>%s</listen>\n", given_address);
+  g_string_append        (config_file_contents,
+                          "  <policy context=\"default\">\n"
+                          "    <!-- Allow everything to be sent -->\n"
+                          "    <allow send_destination=\"*\" eavesdrop=\"true\"/>\n"
+                          "    <!-- Allow everything to be received -->\n"
+                          "    <allow eavesdrop=\"true\"/>\n"
+                          "    <!-- Allow anyone to own anything -->\n"
+                          "    <allow own=\"*\"/>\n"
+                          "  </policy>\n");
+  g_string_append        (config_file_contents, "</busconfig>\n");
+
+  if (write (config_file_fd, config_file_contents->str, config_file_contents->len) != (gssize) config_file_contents->len)
+    {
+      g_warning ("Error writing %d bytes to config file: %m", (gint) config_file_contents->len);
+      g_string_free (config_file_contents, TRUE);
+      goto out;
+    }
+  g_string_free (config_file_contents, TRUE);
+
+  argv[2] = g_strdup_printf ("--config-file=%s", config_file_name);
+
+  if (session_bus_address_to_pid == NULL)
+    {
+      /* keep a mapping from session bus address to the pid */
+      session_bus_address_to_pid = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+      /* fork a child to clean up session buses when we are killed */
+      if (pipe (pipe_fds) != 0)
+        {
+          g_warning ("pipe() failed: %m");
+          g_assert_not_reached ();
+        }
+      switch (fork ())
+        {
+        case -1:
+          g_warning ("fork() failed: %m");
+          g_assert_not_reached ();
+          break;
+
+        case 0:
+          /* child */
+          close (pipe_fds[1]);
+          watch_parent (pipe_fds[0]);
+          break;
+
+        default:
+          /* parent */
+          close (pipe_fds[0]);
+          break;
+        }
+
+      //atexit (cleanup_session_buses);
+      /* TODO: need to handle the cases where we crash */
+    }
+  else
+    {
+      /* check if we already have a bus running for this address */
+      if (g_hash_table_lookup (session_bus_address_to_pid, given_address) != NULL)
+        {
+          g_warning ("Already have a bus instance for the given address %s", given_address);
+          goto out;
+        }
+    }
+
+  if (!g_spawn_async_with_pipes (NULL,
+                                 (char **)argv,
+                                 NULL,
+                                 G_SPAWN_SEARCH_PATH,
+                                 NULL,
+                                 NULL,
+                                 &pid,
+                                 NULL,
+                                 &stdout_fd,
+                                 NULL,
+                                 &error))
+    {
+      g_warning ("Error spawning dbus-daemon: %s", error->message);
+      g_error_free (error);
+      goto out;
+    }
+
+  memset (buf, '\0', sizeof buf);
+ again:
+  bytes_read = read (stdout_fd, buf, sizeof buf);
+  if (bytes_read < 0 && (errno == EAGAIN || errno == EINTR))
+    goto again;
+  close (stdout_fd);
+
+  if (bytes_read == 0 || bytes_read == sizeof buf)
+    {
+      g_warning ("Error reading address from dbus daemon, %d bytes read", (gint) bytes_read);
+      kill (SIGTERM, pid);
+      goto out;
+    }
+
+  address = g_strdup (buf);
+  g_strstrip (address);
+
+  /* write the pid to the child so it can kill it when we die */
+  g_snprintf (buf, sizeof buf, "add %d\n", (guint) pid);
+  write (pipe_fds[1], buf, strlen (buf));
+
+  /* start dbus-monitor */
+  if (g_getenv ("E_DBUS_MONITOR") != NULL)
+    {
+      g_spawn_command_line_async ("dbus-monitor --session", NULL);
+      usleep (500 * 1000);
+    }
+
+  g_hash_table_insert (session_bus_address_to_pid, address, GUINT_TO_POINTER (pid));
+
+ out:
+  if (config_file_fd > 0)
+    {
+      if (close (config_file_fd) != 0)
+        {
+          g_warning ("Error closing fd for config file %s: %m", config_file_name);
+        }
+      g_assert (config_file_name != NULL);
+      if (unlink (config_file_name) != 0)
+        {
+          g_warning ("Error unlinking config file %s: %m", config_file_name);
+        }
+    }
+  g_free ((char *)argv[2]);
+  g_free (config_file_name);
+  return address;
+}
+
+void
+session_bus_down_with_address (const gchar *address)
+{
+  gpointer value;
+  GPid pid;
+  gchar buf[512];
+
+  g_assert (address != NULL);
+  g_assert (session_bus_address_to_pid != NULL);
+
+  value = g_hash_table_lookup (session_bus_address_to_pid, address);
+  g_assert (value != NULL);
+
+  pid = GPOINTER_TO_UINT (g_hash_table_lookup (session_bus_address_to_pid, address));
+
+  kill (pid, SIGTERM);
+
+  /* write the pid to the child so it won't kill it when we die */
+  g_snprintf (buf, sizeof buf, "remove %d\n", (guint) pid);
+  write (pipe_fds[1], buf, strlen (buf));
+
+  g_hash_table_remove (session_bus_address_to_pid, address);
+}
+
+static gchar *temporary_address = NULL;
+static gchar *temporary_address_used_by_bus = NULL;
+
+const gchar *
+session_bus_get_temporary_address (void)
+{
+  if (temporary_address == NULL)
+    {
+      /* TODO: maybe use a more random name etc etc */
+      temporary_address = g_strdup_printf ("unix:path=/tmp/g-dbus-tests-pid-%d", getpid ());
+    }
+
+  return temporary_address;
+}
+
+const gchar *
+session_bus_up (void)
+{
+  if (temporary_address_used_by_bus != NULL)
+    {
+      g_warning ("There is already a session bus up");
+      goto out;
+    }
+
+  temporary_address_used_by_bus = g_strdup (session_bus_up_with_address (session_bus_get_temporary_address ()));
+
+ out:
+  return temporary_address_used_by_bus;
+}
+
+void
+session_bus_down (void)
+{
+  if (temporary_address_used_by_bus == NULL)
+    {
+      g_warning ("There is not a session bus up");
+    }
+  else
+    {
+      session_bus_down_with_address (temporary_address_used_by_bus);
+      g_free (temporary_address_used_by_bus);
+      temporary_address_used_by_bus = NULL;
+    }
+}
diff --git a/edbus/tests/sessionbus.h b/edbus/tests/sessionbus.h
new file mode 100644
index 0000000..e9c4e73
--- /dev/null
+++ b/edbus/tests/sessionbus.h
@@ -0,0 +1,38 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __SESSION_BUS_H__
+#define __SESSION_BUS_H__
+
+#include <edbus/edbus.h>
+
+G_BEGIN_DECLS
+
+const gchar *session_bus_up_with_address       (const gchar *given_address);
+void         session_bus_down_with_address     (const gchar *address);
+const gchar *session_bus_get_temporary_address (void);
+const gchar *session_bus_up                    (void);
+void         session_bus_down                  (void);
+
+G_END_DECLS
+
+#endif /* __SESSION_BUS_H__ */
diff --git a/edbus/tests/tests.c b/edbus/tests/tests.c
new file mode 100644
index 0000000..03c1c8e
--- /dev/null
+++ b/edbus/tests/tests.c
@@ -0,0 +1,131 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <edbus/edbus.h>
+#include <unistd.h>
+
+#include "tests.h"
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GMainLoop *loop;
+  gboolean   timed_out;
+} PropertyNotifyData;
+
+static void
+on_property_notify (GObject    *object,
+                    GParamSpec *pspec,
+                    gpointer    user_data)
+{
+  PropertyNotifyData *data = user_data;
+  g_main_loop_quit (data->loop);
+}
+
+static gboolean
+on_property_notify_timeout (gpointer user_data)
+{
+  PropertyNotifyData *data = user_data;
+  data->timed_out = TRUE;
+  g_main_loop_quit (data->loop);
+  return TRUE;
+}
+
+gboolean
+_g_assert_property_notify_run (gpointer     object,
+                               const gchar *property_name)
+{
+  gchar *s;
+  gulong handler_id;
+  guint timeout_id;
+  PropertyNotifyData data;
+
+  data.loop = g_main_loop_new (NULL, FALSE);
+  data.timed_out = FALSE;
+  s = g_strdup_printf ("notify::%s", property_name);
+  handler_id = g_signal_connect (object,
+                                 s,
+                                 G_CALLBACK (on_property_notify),
+                                 &data);
+  g_free (s);
+  timeout_id = g_timeout_add (5 * 1000,
+                              on_property_notify_timeout,
+                              &data);
+  g_main_loop_run (data.loop);
+  g_signal_handler_disconnect (object, handler_id);
+  g_source_remove (timeout_id);
+  g_main_loop_unref (data.loop);
+
+  return data.timed_out;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+  GMainLoop *loop;
+  gboolean   timed_out;
+} SignalReceivedData;
+
+static void
+on_signal_received (gpointer user_data)
+{
+  SignalReceivedData *data = user_data;
+  g_main_loop_quit (data->loop);
+}
+
+static gboolean
+on_signal_received_timeout (gpointer user_data)
+{
+  SignalReceivedData *data = user_data;
+  data->timed_out = TRUE;
+  g_main_loop_quit (data->loop);
+  return TRUE;
+}
+
+gboolean
+_g_assert_signal_received_run (gpointer     object,
+                               const gchar *signal_name)
+{
+  gulong handler_id;
+  guint timeout_id;
+  SignalReceivedData data;
+
+  data.loop = g_main_loop_new (NULL, FALSE);
+  data.timed_out = FALSE;
+  handler_id = g_signal_connect_swapped (object,
+                                         signal_name,
+                                         G_CALLBACK (on_signal_received),
+                                         &data);
+  timeout_id = g_timeout_add (5 * 1000,
+                              on_signal_received_timeout,
+                              &data);
+  g_main_loop_run (data.loop);
+  g_signal_handler_disconnect (object, handler_id);
+  g_source_remove (timeout_id);
+  g_main_loop_unref (data.loop);
+
+  return data.timed_out;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/edbus/tests/tests.h b/edbus/tests/tests.h
new file mode 100644
index 0000000..9ade394
--- /dev/null
+++ b/edbus/tests/tests.h
@@ -0,0 +1,117 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __TESTS_H__
+#define __TESTS_H__
+
+#include <edbus/edbus.h>
+#include "sessionbus.h"
+
+G_BEGIN_DECLS
+
+/* TODO: clean up and move to gtestutils.c
+ *
+ * This is needed because libdbus-1 does not give predictable error messages - e.g. you
+ * get a different error message on connecting to a bus if the socket file is there vs
+ * if the socket file is missing.
+ */
+
+#define _g_assert_error_domain(err, dom)	do { if (!err || (err)->domain != dom) \
+      g_assertion_message_error (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
+                                 #err, err, dom, -1); } while (0)
+
+#define _g_assert_property_notify(object, property_name)                \
+  do                                                                    \
+    {                                                                   \
+      if (!G_IS_OBJECT (object))                                        \
+        {                                                               \
+          g_assertion_message (G_LOG_DOMAIN,                            \
+                               __FILE__,                                \
+                               __LINE__,                                \
+                               G_STRFUNC,                               \
+                               "Not a GObject instance");               \
+        }                                                               \
+      if (g_object_class_find_property (G_OBJECT_GET_CLASS (object),    \
+                                        property_name) == NULL)         \
+        {                                                               \
+          g_assertion_message (G_LOG_DOMAIN,                            \
+                               __FILE__,                                \
+                               __LINE__,                                \
+                               G_STRFUNC,                               \
+                               "Property " property_name " does not "   \
+                               "exist on object");                      \
+        }                                                               \
+      if (_g_assert_property_notify_run (object, property_name))        \
+        {                                                               \
+          g_assertion_message (G_LOG_DOMAIN,                            \
+                               __FILE__,                                \
+                               __LINE__,                                \
+                               G_STRFUNC,                               \
+                               "Timed out waiting for notification "    \
+                               "on property " property_name);           \
+        }                                                               \
+    }                                                                   \
+  while (FALSE)
+
+#define _g_assert_signal_received(object, signal_name)                  \
+  do                                                                    \
+    {                                                                   \
+      if (!G_IS_OBJECT (object))                                        \
+        {                                                               \
+          g_assertion_message (G_LOG_DOMAIN,                            \
+                               __FILE__,                                \
+                               __LINE__,                                \
+                               G_STRFUNC,                               \
+                               "Not a GObject instance");               \
+        }                                                               \
+      if (g_signal_lookup (signal_name,                                 \
+                           G_TYPE_FROM_INSTANCE (object)) == 0)         \
+        {                                                               \
+          g_assertion_message (G_LOG_DOMAIN,                            \
+                               __FILE__,                                \
+                               __LINE__,                                \
+                               G_STRFUNC,                               \
+                               "Signal " signal_name " does not "       \
+                               "exist on object");                      \
+        }                                                               \
+      if (_g_assert_signal_received_run (object, signal_name))          \
+        {                                                               \
+          g_assertion_message (G_LOG_DOMAIN,                            \
+                               __FILE__,                                \
+                               __LINE__,                                \
+                               G_STRFUNC,                               \
+                               "Timed out waiting for signal "          \
+                               signal_name);                            \
+        }                                                               \
+    }                                                                   \
+  while (FALSE)
+
+gboolean _g_assert_property_notify_run (gpointer     object,
+                                        const gchar *property_name);
+
+
+gboolean _g_assert_signal_received_run (gpointer     object,
+                                        const gchar *signal_name);
+
+G_END_DECLS
+
+#endif /* __TESTS_H__ */
diff --git a/edbus/tests/threading.c b/edbus/tests/threading.c
new file mode 100644
index 0000000..82d5d3f
--- /dev/null
+++ b/edbus/tests/threading.c
@@ -0,0 +1,529 @@
+/* GLib testing framework examples and tests
+ *
+ * Copyright (C) 2008-2009 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include <edbus/edbus.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "tests.h"
+
+/* all tests rely on a global connection */
+static EDBusConnection *c = NULL;
+
+/* all tests rely on a shared mainloop */
+static GMainLoop *loop = NULL;
+
+/* ---------------------------------------------------------------------------------------------------- */
+/* Ensure that signal and method replies are delivered in the right thread */
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct {
+  GThread *thread;
+  GMainLoop *thread_loop;
+  guint signal_count;
+} DeliveryData;
+
+static void
+msg_cb_expect_success (EDBusConnection *connection,
+                       GAsyncResult    *res,
+                       gpointer         user_data)
+{
+  DeliveryData *data = user_data;
+  GError *error;
+  EVariant *result;
+
+  error = NULL;
+  result = e_dbus_connection_invoke_method_finish (connection,
+                                                   res,
+                                                   &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  e_variant_unref (result);
+
+  g_assert (g_thread_self () == data->thread);
+
+  g_main_loop_quit (data->thread_loop);
+}
+
+static void
+msg_cb_expect_error_cancelled (EDBusConnection *connection,
+                               GAsyncResult    *res,
+                               gpointer         user_data)
+{
+  DeliveryData *data = user_data;
+  GError *error;
+  EVariant *result;
+
+  error = NULL;
+  result = e_dbus_connection_invoke_method_finish (connection,
+                                                   res,
+                                                   &error);
+  g_assert_error (error, E_DBUS_ERROR, E_DBUS_ERROR_CANCELLED);
+  g_assert (!e_dbus_error_is_remote_error (error));
+  g_error_free (error);
+  g_assert (result == NULL);
+
+  g_assert (g_thread_self () == data->thread);
+
+  g_main_loop_quit (data->thread_loop);
+}
+
+static void
+signal_handler (EDBusConnection *connection,
+                const gchar      *sender_name,
+                const gchar      *object_path,
+                const gchar      *interface_name,
+                const gchar      *signal_name,
+                EVariant         *parameters,
+                gpointer         user_data)
+{
+  DeliveryData *data = user_data;
+
+  g_assert (g_thread_self () == data->thread);
+
+  data->signal_count++;
+
+  g_main_loop_quit (data->thread_loop);
+}
+
+static gpointer
+test_delivery_in_thread_func (gpointer _data)
+{
+  GMainLoop *thread_loop;
+  GMainContext *thread_context;
+  DeliveryData data;
+  GCancellable *ca;
+  guint subscription_id;
+  EDBusConnection *priv_c;
+  GError *error;
+
+  error = NULL;
+
+  thread_context = g_main_context_new ();
+  thread_loop = g_main_loop_new (thread_context, FALSE);
+  g_main_context_push_thread_default (thread_context);
+
+  data.thread = g_thread_self ();
+  data.thread_loop = thread_loop;
+  data.signal_count = 0;
+
+  /* ---------------------------------------------------------------------------------------------------- */
+
+  /**
+   * Check that we get a reply to the GetId() method call.
+   */
+  e_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetId",                 /* method name */
+                                   NULL,
+                                   -1,
+                                   NULL,
+                                   (GAsyncReadyCallback) msg_cb_expect_success,
+                                   &data);
+  g_main_loop_run (thread_loop);
+
+  /**
+   * Check that we never actually send a message if the GCancellable is already cancelled - i.e.
+   * we should get #E_DBUS_ERROR_CANCELLED instead of #E_DBUS_ERROR_FAILED even when the actual
+   * connection is not up.
+   */
+  ca = g_cancellable_new ();
+  g_cancellable_cancel (ca);
+  e_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetId",                 /* method name */
+                                   NULL,
+                                   -1,
+                                   ca,
+                                   (GAsyncReadyCallback) msg_cb_expect_error_cancelled,
+                                   &data);
+  g_main_loop_run (thread_loop);
+  g_object_unref (ca);
+
+  /**
+   * Check that cancellation works when the message is already in flight.
+   */
+  ca = g_cancellable_new ();
+  e_dbus_connection_invoke_method (c,
+                                   "org.freedesktop.DBus",  /* bus_name */
+                                   "/org/freedesktop/DBus", /* object path */
+                                   "org.freedesktop.DBus",  /* interface name */
+                                   "GetId",                 /* method name */
+                                   NULL,
+                                   -1,
+                                   ca,
+                                   (GAsyncReadyCallback) msg_cb_expect_error_cancelled,
+                                   &data);
+  g_cancellable_cancel (ca);
+  g_main_loop_run (thread_loop);
+  g_object_unref (ca);
+
+  /**
+   * Check that signals are delivered to the correct thread.
+   *
+   * First we subscribe to the signal, the we create a a private connection. This should
+   * cause a NameOwnerChanged message. Then we tear it down. This should cause another
+   * NameOwnerChanged message.
+   */
+  subscription_id = e_dbus_connection_signal_subscribe (c,
+                                                        "org.freedesktop.DBus",  /* sender */
+                                                        "org.freedesktop.DBus",  /* interface */
+                                                        "NameOwnerChanged",      /* member */
+                                                        "/org/freedesktop/DBus", /* path */
+                                                        NULL,
+                                                        signal_handler,
+                                                        &data,
+                                                        NULL);
+  g_assert (subscription_id != 0);
+  g_assert (data.signal_count == 0);
+
+  priv_c = e_dbus_connection_bus_get_private_sync (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (priv_c != NULL);
+
+  g_main_loop_run (thread_loop);
+  g_assert (data.signal_count == 1);
+
+  g_object_unref (priv_c);
+
+  g_main_loop_run (thread_loop);
+  g_assert (data.signal_count == 2);
+
+  e_dbus_connection_signal_unsubscribe (c, subscription_id);
+
+  /* ---------------------------------------------------------------------------------------------------- */
+
+  g_main_context_pop_thread_default (thread_context);
+  g_main_loop_unref (thread_loop);
+  g_main_context_unref (thread_context);
+
+  g_main_loop_quit (loop);
+
+  return NULL;
+}
+
+static void
+test_delivery_in_thread (void)
+{
+  GError *error;
+  GThread *thread;
+
+  error = NULL;
+  thread = g_thread_create (test_delivery_in_thread_func,
+                            NULL,
+                            TRUE,
+                            &error);
+  g_assert_no_error (error);
+  g_assert (thread != NULL);
+
+  /* run the event loop - it is needed to dispatch D-Bus messages */
+  g_main_loop_run (loop);
+
+  g_thread_join (thread);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct {
+  EDBusProxy *proxy;
+  gint msec;
+  guint num;
+  gboolean async;
+
+  GMainLoop *thread_loop;
+  GThread *thread;
+
+  gboolean done;
+} SyncThreadData;
+
+static void
+sleep_cb (EDBusProxy   *proxy,
+          GAsyncResult *res,
+          gpointer      user_data)
+{
+  SyncThreadData *data = user_data;
+  GError *error;
+  EVariant *result;
+
+  error = NULL;
+  result = e_dbus_proxy_invoke_method_finish (proxy,
+                                              res,
+                                              &error);
+  g_assert_no_error (error);
+  g_assert (result != NULL);
+  g_assert_cmpstr (e_variant_get_type_string (result), ==, "()");
+  e_variant_unref (result);
+
+  g_assert (data->thread == g_thread_self ());
+
+  g_main_loop_quit (data->thread_loop);
+
+  //g_debug ("async cb (%p)", g_thread_self ());
+}
+
+static gpointer
+test_sleep_in_thread_func (gpointer _data)
+{
+  SyncThreadData *data = _data;
+  GMainContext *thread_context;
+  guint n;
+
+  thread_context = g_main_context_new ();
+  data->thread_loop = g_main_loop_new (thread_context, FALSE);
+  g_main_context_push_thread_default (thread_context);
+
+  data->thread = g_thread_self ();
+
+  for (n = 0; n < data->num; n++)
+    {
+      if (data->async)
+        {
+          //g_debug ("invoking async (%p)", g_thread_self ());
+          e_dbus_proxy_invoke_method (data->proxy,
+                                      "Sleep",
+                                      e_variant_new ("(i)", data->msec),
+                                      -1,
+                                      NULL,
+                                      (GAsyncReadyCallback) sleep_cb,
+                                      data);
+          g_main_loop_run (data->thread_loop);
+          //g_debug ("done invoking async (%p)", g_thread_self ());
+        }
+      else
+        {
+          GError *error;
+          EVariant *result;
+
+          error = NULL;
+          //g_debug ("invoking sync (%p)", g_thread_self ());
+          result = e_dbus_proxy_invoke_method_sync (data->proxy,
+                                                    "Sleep",
+                                                    e_variant_new ("(i)", data->msec),
+                                                    -1,
+                                                    NULL,
+                                                    &error);
+          //g_debug ("done invoking sync (%p)", g_thread_self ());
+          g_assert_no_error (error);
+          g_assert (result != NULL);
+          g_assert_cmpstr (e_variant_get_type_string (result), ==, "()");
+          e_variant_unref (result);
+        }
+    }
+
+  g_main_context_pop_thread_default (thread_context);
+  g_main_loop_unref (data->thread_loop);
+  g_main_context_unref (thread_context);
+
+  data->done = TRUE;
+  g_main_loop_quit (loop);
+
+  return NULL;
+}
+
+static void
+on_proxy_appeared (EDBusConnection *connection,
+                   const gchar     *name,
+                   const gchar     *name_owner,
+                   EDBusProxy      *proxy,
+                   gpointer         user_data)
+{
+  guint n;
+
+  /**
+   * Check that multiple threads can do calls without interferring with
+   * each other. We do this by creating three threads that call the
+   * Sleep() method on the server (which handles it asynchronously, e.g.
+   * it won't block other requests) with different sleep durations and
+   * a number of times. We do this so each set of calls add up to 4000
+   * milliseconds.
+   *
+   * We run this test twice - first with async calls in each thread, then
+   * again with sync calls
+   */
+
+  /* TODO: The sync calls are broken - this is likely a libdbus-1 bug. Need
+   *       to investigate. So only do the async calls for now.
+   */
+  for (n = 0; n < /* 2 */ 1; n++)
+    {
+      gboolean do_async;
+      GThread *thread1;
+      GThread *thread2;
+      GThread *thread3;
+      SyncThreadData data1;
+      SyncThreadData data2;
+      SyncThreadData data3;
+      GError *error;
+      GTimeVal start_time;
+      GTimeVal end_time;
+      guint elapsed_msec;
+
+      error = NULL;
+      do_async = (n == 0);
+
+      g_get_current_time (&start_time);
+
+      data1.proxy = proxy;
+      data1.msec = 40;
+      data1.num = 100;
+      data1.async = do_async;
+      data1.done = FALSE;
+      thread1 = g_thread_create (test_sleep_in_thread_func,
+                                 &data1,
+                                 TRUE,
+                                 &error);
+      g_assert_no_error (error);
+      g_assert (thread1 != NULL);
+
+      data2.proxy = proxy;
+      data2.msec = 20;
+      data2.num = 200;
+      data2.async = do_async;
+      data2.done = FALSE;
+      thread2 = g_thread_create (test_sleep_in_thread_func,
+                                 &data2,
+                                 TRUE,
+                                 &error);
+      g_assert_no_error (error);
+      g_assert (thread2 != NULL);
+
+      data3.proxy = proxy;
+      data3.msec = 100;
+      data3.num = 40;
+      data3.async = do_async;
+      data3.done = FALSE;
+      thread3 = g_thread_create (test_sleep_in_thread_func,
+                                 &data3,
+                                 TRUE,
+                                 &error);
+      g_assert_no_error (error);
+      g_assert (thread3 != NULL);
+
+      /* we handle messages in the main loop - threads will quit it when they are done */
+      while (!(data1.done && data2.done && data3.done))
+        g_main_loop_run (loop);
+
+      g_thread_join (thread1);
+      g_thread_join (thread2);
+      g_thread_join (thread3);
+
+      g_get_current_time (&end_time);
+
+      elapsed_msec = ((end_time.tv_sec * G_USEC_PER_SEC + end_time.tv_usec) -
+                      (start_time.tv_sec * G_USEC_PER_SEC + start_time.tv_usec)) / 1000;
+
+      //g_debug ("Elapsed time for %s = %d msec", n == 0 ? "async" : "sync", elapsed_msec);
+
+      /* elapsed_msec should be 4000 msec + change for overhead */
+      g_assert_cmpint (elapsed_msec, >=, 4000);
+      g_assert_cmpint (elapsed_msec,  <, 5000);
+    }
+
+  g_main_loop_quit (loop);
+}
+
+static void
+on_proxy_vanished (EDBusConnection *connection,
+                   const gchar     *name,
+                   gpointer         user_data)
+{
+}
+
+static void
+test_method_calls_in_thread (void)
+{
+  guint watcher_id;
+
+  watcher_id = e_bus_watch_proxy (G_BUS_TYPE_SESSION,
+                                  "com.example.TestService",
+                                  "/com/example/TestObject",
+                                  "com.example.Frob",
+                                  E_TYPE_DBUS_PROXY,
+                                  E_DBUS_PROXY_FLAGS_NONE,
+                                  on_proxy_appeared,
+                                  on_proxy_vanished,
+                                  NULL,
+                                  NULL);
+
+  g_main_loop_run (loop);
+
+  e_bus_unwatch_proxy (watcher_id);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+int
+main (int   argc,
+      char *argv[])
+{
+  GError *error;
+  gint ret;
+
+  g_type_init ();
+  g_thread_init (NULL);
+  e_dbus_threads_init ();
+  g_test_init (&argc, &argv, NULL);
+
+  /* all the tests rely on a shared main loop */
+  loop = g_main_loop_new (NULL, FALSE);
+
+  /* all the tests use a session bus with a well-known address that we can bring up and down
+   * using session_bus_up() and session_bus_down().
+   */
+  g_unsetenv ("DISPLAY");
+  g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
+
+  session_bus_up ();
+
+  /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return
+   * until one can connect to the bus but that's not how things work right now
+   */
+  usleep (500 * 1000);
+
+  /* this is safe; testserver will exit once the bus goes away */
+  g_assert (g_spawn_command_line_async ("./testserver.py", NULL));
+
+  /* wait for the service to come up */
+  usleep (500 * 1000);
+
+  /* Create the connection in the main thread */
+  error = NULL;
+  c = e_dbus_connection_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
+  g_assert_no_error (error);
+  g_assert (c != NULL);
+
+  g_test_add_func ("/gdbus/delivery-in-thread", test_delivery_in_thread);
+  g_test_add_func ("/gdbus/method-calls-in-thread", test_method_calls_in_thread);
+
+  ret = g_test_run();
+
+  g_object_unref (c);
+
+  /* tear down bus */
+  session_bus_down ();
+
+  return ret;
+}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]