text: /* GLib testing framework examples and tests
   *
   * Copyright (C) 2008-2010 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.1 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, see <http://www.gnu.org/licenses/>.
   *
   * Author: David Zeuthen <davidz@redhat.com>
   */
  
  #include <gio/gio.h>
  #include <unistd.h>
  #include <string.h>
  
  #include <sys/types.h>
  
  #include "gdbus-tests.h"
  
  /* all tests rely on a shared mainloop */
  static GMainLoop *loop = NULL;
  
  #if 0
  G_GNUC_UNUSED static void
  _log (const gchar *format, ...)
  {
    GTimeVal now;
    time_t now_time;
    struct tm *now_tm;
    gchar time_buf[128];
    gchar *str;
    va_list var_args;
  
    va_start (var_args, format);
    str = g_strdup_vprintf (format, var_args);
    va_end (var_args);
  
    g_get_current_time (&now);
    now_time = (time_t) now.tv_sec;
    now_tm = localtime (&now_time);
    strftime (time_buf, sizeof time_buf, "%H:%M:%S", now_tm);
  
    g_printerr ("%s.%06d: %s\n",
             time_buf, (gint) now.tv_usec / 1000,
             str);
    g_free (str);
  }
  #else
  #define _log(...)
  #endif
  
  static gboolean
  test_connection_quit_mainloop (gpointer user_data)
  {
    volatile gboolean *quit_mainloop_fired = user_data;
    _log ("quit_mainloop_fired");
    *quit_mainloop_fired = TRUE;
    g_main_loop_quit (loop);
    return TRUE;
  }
  
  /* ---------------------------------------------------------------------------------------------------- */
  /* Connection life-cycle testing */
  /* ---------------------------------------------------------------------------------------------------- */
  
  static const GDBusInterfaceInfo boo_interface_info =
  {
    -1,
    "org.example.Boo",
    (GDBusMethodInfo **) NULL,
    (GDBusSignalInfo **) NULL,
    (GDBusPropertyInfo **) NULL,
    NULL,
  };
  
  static const GDBusInterfaceVTable boo_vtable =
  {
    NULL, /* _method_call */
    NULL, /* _get_property */
    NULL  /* _set_property */
  };
  
  static GDBusMessage *
  some_filter_func (GDBusConnection *connection,
                    GDBusMessage    *message,
                    gboolean         incoming,
                    gpointer         user_data)
  {
    return message;
  }
  
  static void
  on_name_owner_changed (GDBusConnection *connection,
                         const gchar     *sender_name,
                         const gchar     *object_path,
                         const gchar     *interface_name,
                         const gchar     *signal_name,
                         GVariant        *parameters,
                         gpointer         user_data)
  {
  }
  
  static void
  a_gdestroynotify_that_sets_a_gboolean_to_true_and_quits_loop (gpointer user_data)
  {
    volatile gboolean *val = user_data;
    *val = TRUE;
    _log ("destroynotify fired for %p", val);
    g_main_loop_quit (loop);
  }
  
  static void
  test_connection_bus_failure (void)
  {
    GDBusConnection *c;
    GError *error = NULL;
  
    /*
     * Check for correct behavior when no bus is present
     *
     */
    c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
    g_assert (error != NULL);
    g_assert (!g_dbus_error_is_remote_error (error));
    g_assert (c == NULL);
    g_error_free (error);
  }
  
  static void
  test_connection_life_cycle (void)
  {
    gboolean ret;
    GDBusConnection *c;
    GDBusConnection *c2;
    GError *error;
    volatile gboolean on_signal_registration_freed_called;
    volatile gboolean on_filter_freed_called;
    volatile gboolean on_register_object_freed_called;
    volatile gboolean quit_mainloop_fired;
    guint quit_mainloop_id;
    guint registration_id;
  
    error = NULL;
  
    /*
     *  Check for correct behavior when a bus is present
     */
    session_bus_up ();
    /* case 1 */
    error = NULL;
    c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
    g_assert_no_error (error);
    g_assert (c != NULL);
    g_assert (!g_dbus_connection_is_closed (c));
  
    /*
     * Check that singleton handling work
     */
    error = NULL;
    c2 = g_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 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &error);
    g_assert_no_error (error);
    g_assert (c2 != NULL);
    g_assert (c != c2);
    g_object_unref (c2);
  
    c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &error);
    g_assert_no_error (error);
    g_assert (c2 != NULL);
    g_assert (!g_dbus_connection_is_closed (c2));
    ret = g_dbus_connection_close_sync (c2, NULL, &error);
    g_assert_no_error (error);
    g_assert (ret);
    _g_assert_signal_received (c2, "closed");
    g_assert (g_dbus_connection_is_closed (c2));
    ret = g_dbus_connection_close_sync (c2, NULL, &error);
    g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED);
    g_error_free (error);
    g_assert (!ret);
    g_object_unref (c2);
  
    /*
     * Check that the finalization code works
     *
     * (and that the GDestroyNotify for filters and objects and signal
     * registrations are run as expected)
     */
    error = NULL;
    c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &error);
    g_assert_no_error (error);
    g_assert (c2 != NULL);
    /* signal registration */
    on_signal_registration_freed_called = FALSE;
    g_dbus_connection_signal_subscribe (c2,
                                        "org.freedesktop.DBus", /* bus name */
                                        "org.freedesktop.DBus", /* interface */
                                        "NameOwnerChanged",     /* member */
                                        "/org/freesktop/DBus",  /* path */
                                        NULL,                   /* arg0 */
                                        G_DBUS_SIGNAL_FLAGS_NONE,
                                        on_name_owner_changed,
                                        (gpointer) &on_signal_registration_freed_called,
                                        a_gdestroynotify_that_sets_a_gboolean_to_true_and_quits_loop);
    /* filter func */
    on_filter_freed_called = FALSE;
    g_dbus_connection_add_filter (c2,
                                  some_filter_func,
                                  (gpointer) &on_filter_freed_called,
                                  a_gdestroynotify_that_sets_a_gboolean_to_true_and_quits_loop);
    /* object registration */
    on_register_object_freed_called = FALSE;
    error = NULL;
    registration_id = g_dbus_connection_register_object (c2,
                                                         "/foo",
                                                         (GDBusInterfaceInfo *) &boo_interface_info,
                                                         &boo_vtable,
                                                         (gpointer) &on_register_object_freed_called,
                                                         a_gdestroynotify_that_sets_a_gboolean_to_true_and_quits_loop,
                                                         &error);
    g_assert_no_error (error);
    g_assert (registration_id > 0);
    /* ok, finalize the connection and check that all the GDestroyNotify functions are invoked as expected */
    g_object_unref (c2);
    quit_mainloop_fired = FALSE;
    quit_mainloop_id = g_timeout_add (30000, test_connection_quit_mainloop, (gpointer) &quit_mainloop_fired);
    _log ("destroynotifies for\n"
          " register_object %p\n"
          " filter          %p\n"
          " signal          %p",
          &on_register_object_freed_called,
          &on_filter_freed_called,
          &on_signal_registration_freed_called);
    while (TRUE)
      {
        if (on_signal_registration_freed_called &&
            on_filter_freed_called &&
            on_register_object_freed_called)
          break;
        if (quit_mainloop_fired)
          break;
        _log ("entering loop");
        g_main_loop_run (loop);
        _log ("exiting loop");
      }
    g_source_remove (quit_mainloop_id);
    g_assert (on_signal_registration_freed_called);
    g_assert (on_filter_freed_called);
    g_assert (on_register_object_freed_called);
    g_assert (!quit_mainloop_fired);
  
    /*
     *  Check for correct behavior when the bus goes away
     *
     */
    g_assert (!g_dbus_connection_is_closed (c));
    g_dbus_connection_set_exit_on_close (c, FALSE);
    session_bus_stop ();
    _g_assert_signal_received (c, "closed");
    g_assert (g_dbus_connection_is_closed (c));
    g_object_unref (c);
  
    session_bus_down ();
  }
  
  /* ---------------------------------------------------------------------------------------------------- */
  /* Test that sending and receiving messages work as expected */
  /* ---------------------------------------------------------------------------------------------------- */
  
  static void
  msg_cb_expect_error_disconnected (GDBusConnection *connection,
                                    GAsyncResult    *res,
                                    gpointer         user_data)
  {
    GError *error;
    GVariant *result;
  
    /* Make sure gdbusconnection isn't holding @connection's lock. (#747349) */
    g_dbus_connection_get_last_serial (connection);
  
    error = NULL;
    result = g_dbus_connection_call_finish (connection,
                                            res,
                                            &error);
    g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED);
    g_assert (!g_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 (GDBusConnection *connection,
                                      GAsyncResult    *res,
                                      gpointer         user_data)
  {
    GError *error;
    GVariant *result;
  
    /* Make sure gdbusconnection isn't holding @connection's lock. (#747349) */
    g_dbus_connection_get_last_serial (connection);
  
    error = NULL;
    result = g_dbus_connection_call_finish (connection,
                                            res,
                                            &error);
    g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD);
    g_assert (g_dbus_error_is_remote_error (error));
    g_error_free (error);
    g_assert (result == NULL);
  
    g_main_loop_quit (loop);
  }
  
  static void
  msg_cb_expect_success (GDBusConnection *connection,
                         GAsyncResult    *res,
                         gpointer         user_data)
  {
    GError *error;
    GVariant *result;
  
    /* Make sure gdbusconnection isn't holding @connection's lock. (#747349) */
    g_dbus_connection_get_last_serial (connection);
  
    error = NULL;
    result = g_dbus_connection_call_finish (connection,
                                            res,
                                            &error);
    g_assert_no_error (error);
    g_assert (result != NULL);
    g_variant_unref (result);
  
    g_main_loop_quit (loop);
  }
  
  static void
  msg_cb_expect_error_cancelled (GDBusConnection *connection,
                                 GAsyncResult    *res,
                                 gpointer         user_data)
  {
    GError *error;
    GVariant *result;
  
    /* Make sure gdbusconnection isn't holding @connection's lock. (#747349) */
    g_dbus_connection_get_last_serial (connection);
  
    error = NULL;
    result = g_dbus_connection_call_finish (connection,
                                            res,
                                            &error);
    g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
    g_assert (!g_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_cancelled_2 (GDBusConnection *connection,
                                   GAsyncResult    *res,
                                   gpointer         user_data)
  {
    GError *error;
    GVariant *result;
  
    /* Make sure gdbusconnection isn't holding @connection's lock. (#747349) */
    g_dbus_connection_get_last_serial (connection);
  
    error = NULL;
    result = g_dbus_connection_call_finish (connection,
                                            res,
                                            &error);
    g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED);
    g_assert (!g_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)
  {
    GDBusConnection *c;
    GCancellable *ca;
  
    session_bus_up ();
  
    /* First, get an unopened connection */
    c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
    g_assert (c != NULL);
    g_assert (!g_dbus_connection_is_closed (c));
  
    /*
     * Check that we never actually send a message if the GCancellable
     * is already cancelled - i.e.  we should get #G_IO_ERROR_CANCELLED
     * when the actual connection is not up.
     */
    ca = g_cancellable_new ();
    g_cancellable_cancel (ca);
    g_dbus_connection_call (c,
                            "org.freedesktop.DBus",  /* bus_name */
                            "/org/freedesktop/DBus", /* object path */
                            "org.freedesktop.DBus",  /* interface name */
                            "GetId",                 /* method name */
                            NULL, NULL,
                            G_DBUS_CALL_FLAGS_NONE,
                            -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.
     */
    g_dbus_connection_call (c,
                            "org.freedesktop.DBus",  /* bus_name */
                            "/org/freedesktop/DBus", /* object path */
                            "org.freedesktop.DBus",  /* interface name */
                            "GetId",                 /* method name */
                            NULL, NULL,
                            G_DBUS_CALL_FLAGS_NONE,
                            -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.
     */
    g_dbus_connection_call (c,
                            "org.freedesktop.DBus",  /* bus_name */
                            "/org/freedesktop/DBus", /* object path */
                            "org.freedesktop.DBus",  /* interface name */
                            "NonExistantMethod",     /* method name */
                            NULL, NULL,
                            G_DBUS_CALL_FLAGS_NONE,
                            -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 ();
    g_dbus_connection_call (c,
                            "org.freedesktop.DBus",  /* bus_name */
                            "/org/freedesktop/DBus", /* object path */
                            "org.freedesktop.DBus",  /* interface name */
                            "GetId",                 /* method name */
                            NULL, NULL,
                            G_DBUS_CALL_FLAGS_NONE,
                            -1,
                            ca,
                            (GAsyncReadyCallback) msg_cb_expect_error_cancelled_2,
                            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.
     */
    g_dbus_connection_set_exit_on_close (c, FALSE);
    session_bus_stop ();
    _g_assert_signal_received (c, "closed");
    g_assert (g_dbus_connection_is_closed (c));
  
    g_dbus_connection_call (c,
                            "org.freedesktop.DBus",  /* bus_name */
                            "/org/freedesktop/DBus", /* object path */
                            "org.freedesktop.DBus",  /* interface name */
                            "GetId",                 /* method name */
                            NULL, NULL,
                            G_DBUS_CALL_FLAGS_NONE,
                            -1,
                            NULL,
                            (GAsyncReadyCallback) msg_cb_expect_error_disconnected,
                            NULL);
    g_main_loop_run (loop);
  
    g_object_unref (c);
  
    session_bus_down ();
  }
  
  /* ---------------------------------------------------------------------------------------------------- */
  /* Connection signal tests */
  /* ---------------------------------------------------------------------------------------------------- */
  
  static void
  test_connection_signal_handler (GDBusConnection  *connection,
                                  const gchar      *sender_name,
                                  const gchar      *object_path,
                                  const gchar      *interface_name,
                                  const gchar      *signal_name,
                                  GVariant         *parameters,
                                  gpointer         user_data)
  {
    gint *counter = user_data;
    *counter += 1;
  
    /*g_debug ("in test_connection_signal_handler (sender=%s path=%s interface=%s member=%s)",
             sender_name,
             object_path,
             interface_name,
             signal_name);*/
  
    g_main_loop_quit (loop);
  }
  
  static void
  test_connection_signals (void)
  {
    GDBusConnection *c1;
    GDBusConnection *c2;
    GDBusConnection *c3;
    guint s1;
    guint s1b;
    guint s2;
    guint s3;
    gint count_s1;
    gint count_s1b;
    gint count_s2;
    gint count_name_owner_changed;
    GError *error;
    gboolean ret;
    GVariant *result;
    gboolean quit_mainloop_fired;
    guint quit_mainloop_id;
  
    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 ("G_DBUS_MONITOR") == NULL)
      {
        c1 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL);
        g_assert (c1 != NULL);
        g_assert (!g_dbus_connection_is_closed (c1));
        g_object_unref (c1);
      }
    c1 = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
    g_assert (c1 != NULL);
    g_assert (!g_dbus_connection_is_closed (c1));
    g_assert_cmpstr (g_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 = g_dbus_connection_signal_subscribe (c1,
                                             ":1.2",
                                             "org.gtk.GDBus.ExampleInterface",
                                             "Foo",
                                             "/org/gtk/GDBus/ExampleInterface",
                                             NULL,
                                             G_DBUS_SIGNAL_FLAGS_NONE,
                                             test_connection_signal_handler,
                                             &count_s1,
                                             NULL);
    s2 = g_dbus_connection_signal_subscribe (c1,
                                             NULL, /* match any sender */
                                             "org.gtk.GDBus.ExampleInterface",
                                             "Foo",
                                             "/org/gtk/GDBus/ExampleInterface",
                                             NULL,
                                             G_DBUS_SIGNAL_FLAGS_NONE,
                                             test_connection_signal_handler,
                                             &count_s2,
                                             NULL);
    s3 = g_dbus_connection_signal_subscribe (c1,
                                             "org.freedesktop.DBus",  /* sender */
                                             "org.freedesktop.DBus",  /* interface */
                                             "NameOwnerChanged",      /* member */
                                             "/org/freedesktop/DBus", /* path */
                                             NULL,
                                             G_DBUS_SIGNAL_FLAGS_NONE,
                                             test_connection_signal_handler,
                                             &count_name_owner_changed,
                                             NULL);
    /* Note that s1b is *just like* s1 - this is to catch a bug where N
     * subscriptions of the same rule causes N calls to each of the N
     * subscriptions instead of just 1 call to each of the N subscriptions.
     */
    s1b = g_dbus_connection_signal_subscribe (c1,
                                              ":1.2",
                                              "org.gtk.GDBus.ExampleInterface",
                                              "Foo",
                                              "/org/gtk/GDBus/ExampleInterface",
                                              NULL,
                                              G_DBUS_SIGNAL_FLAGS_NONE,
                                              test_connection_signal_handler,
                                              &count_s1b,
                                              NULL);
    g_assert (s1 != 0);
    g_assert (s1b != 0);
    g_assert (s2 != 0);
    g_assert (s3 != 0);
  
    count_s1 = 0;
    count_s1b = 0;
    count_s2 = 0;
    count_name_owner_changed = 0;
  
    /*
     * Make c2 emit "Foo" - we should catch it twice
     *
     * Note that there is no way to be sure that the signal subscriptions
     * on c1 are effective yet - for all we know, the AddMatch() messages
     * could sit waiting in a buffer somewhere between this process and
     * the message bus. And emitting signals on c2 (a completely other
     * socket!) will not necessarily change this.
     *
     * To ensure this is not the case, do a synchronous call on c1.
     */
    result = g_dbus_connection_call_sync (c1,
                                          "org.freedesktop.DBus",  /* bus name */
                                          "/org/freedesktop/DBus", /* object path */
                                          "org.freedesktop.DBus",  /* interface name */
                                          "GetId",                 /* method name */
                                          NULL,                    /* parameters */
                                          NULL,                    /* return type */
                                          G_DBUS_CALL_FLAGS_NONE,
                                          -1,
                                          NULL,
                                          &error);
    g_assert_no_error (error);
    g_assert (result != NULL);
    g_variant_unref (result);
  
    /*
     * Bring up two other connections
     */
    c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL);
    g_assert (c2 != NULL);
    g_assert (!g_dbus_connection_is_closed (c2));
    g_assert_cmpstr (g_dbus_connection_get_unique_name (c2), ==, ":1.2");
    c3 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL);
    g_assert (c3 != NULL);
    g_assert (!g_dbus_connection_is_closed (c3));
    g_assert_cmpstr (g_dbus_connection_get_unique_name (c3), ==, ":1.3");
  
    /* now, emit the signal on c2 */
    ret = g_dbus_connection_emit_signal (c2,
                                         NULL, /* destination bus name */
                                         "/org/gtk/GDBus/ExampleInterface",
                                         "org.gtk.GDBus.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 = g_dbus_connection_emit_signal (c3,
                                         NULL, /* destination bus name */
                                         "/org/gtk/GDBus/ExampleInterface",
                                         "org.gtk.GDBus.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);
  
    /*
     * Also to check the total amount of NameOwnerChanged signals - use a 5 second ceiling
     * to avoid spinning forever
     */
    quit_mainloop_fired = FALSE;
    quit_mainloop_id = g_timeout_add (30000, test_connection_quit_mainloop, &quit_mainloop_fired);
    while (count_name_owner_changed < 2 && !quit_mainloop_fired)
      g_main_loop_run (loop);
    g_source_remove (quit_mainloop_id);
    g_assert_cmpint (count_s1, ==, 1);
    g_assert_cmpint (count_s2, ==, 2);
    g_assert_cmpint (count_name_owner_changed, ==, 2);
  
    g_dbus_connection_signal_unsubscribe (c1, s1);
    g_dbus_connection_signal_unsubscribe (c1, s2);
    g_dbus_connection_signal_unsubscribe (c1, s3);
    g_dbus_connection_signal_unsubscribe (c1, s1b);
  
    g_object_unref (c1);
    g_object_unref (c2);
    g_object_unref (c3);
  
    session_bus_down ();
  }
  
  static void
  test_match_rule (GDBusConnection  *connection,
                   GDBusSignalFlags  flags,
                   gchar            *arg0_rule,
                   gchar            *arg0,
                   gboolean          should_match)
  {
    guint subscription_ids[2];
    gint emissions = 0;
    gint matches = 0;
    GError *error = NULL;
  
    subscription_ids[0] = g_dbus_connection_signal_subscribe (connection,
                                                              NULL, "org.gtk.ExampleInterface", "Foo", "/",
                                                              NULL,
                                                              G_DBUS_SIGNAL_FLAGS_NONE,
                                                              test_connection_signal_handler,
                                                              &emissions, NULL);
    subscription_ids[1] = g_dbus_connection_signal_subscribe (connection,
                                                              NULL, "org.gtk.ExampleInterface", "Foo", "/",
                                                              arg0_rule,
                                                              flags,
                                                              test_connection_signal_handler,
                                                              &matches, NULL);
    g_assert_cmpint (subscription_ids[0], !=, 0);
    g_assert_cmpint (subscription_ids[1], !=, 0);
  
    g_dbus_connection_emit_signal (connection,
                                   NULL, "/", "org.gtk.ExampleInterface",
                                   "Foo", g_variant_new ("(s)", arg0),
                                   &error);
    g_assert_no_error (error);
  
    /* synchronously ping a non-existent method to make sure the signals are dispatched */
    g_dbus_connection_call_sync (connection, "org.gtk.ExampleInterface", "/", "org.gtk.ExampleInterface",
                                 "Bar", g_variant_new ("()"), G_VARIANT_TYPE_UNIT, G_DBUS_CALL_FLAGS_NONE,
                                 -1, NULL, NULL);
  
    while (g_main_context_iteration (NULL, FALSE))
      ;
  
    g_assert_cmpint (emissions, ==, 1);
    g_assert_cmpint (matches, ==, should_match ? 1 : 0);
  
    g_dbus_connection_signal_unsubscribe (connection, subscription_ids[0]);
    g_dbus_connection_signal_unsubscribe (connection, subscription_ids[1]);
  }
  
  static void
  test_connection_signal_match_rules (void)
  {
    GDBusConnection *con;
  
    session_bus_up ();
    con = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
  
    test_match_rule (con, G_DBUS_SIGNAL_FLAGS_NONE, "foo", "foo", TRUE);
    test_match_rule (con, G_DBUS_SIGNAL_FLAGS_NONE, "foo", "bar", FALSE);
  
    test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, "org.gtk", "", FALSE);
    test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, "org.gtk", "org", FALSE);
    test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, "org.gtk", "org.gtk", TRUE);
    test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, "org.gtk", "org.gtk.Example", TRUE);
    test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_NAMESPACE, "org.gtk", "org.gtk+", FALSE);
  
    test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH, "/", "/", TRUE);
    test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH, "/", "", FALSE);
    test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH, "/org/gtk/Example", "/org/gtk/Example", TRUE);
    test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH, "/org/gtk/", "/org/gtk/Example", TRUE);
    test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH, "/org/gtk/Example", "/org/gtk/", TRUE);
    test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH, "/org/gtk/Example", "/org/gtk", FALSE);
    test_match_rule (con, G_DBUS_SIGNAL_FLAGS_MATCH_ARG0_PATH, "/org/gtk+", "/org/gtk", FALSE);
  
    g_object_unref (con);
    session_bus_down ();
  }
  
  /* ---------------------------------------------------------------------------------------------------- */
  
  typedef struct
  {
    guint num_handled;
    guint num_outgoing;
    guint32 serial;
  } FilterData;
  
  static GDBusMessage *
  filter_func (GDBusConnection *connection,
               GDBusMessage    *message,
               gboolean         incoming,
               gpointer         user_data)
  {
    FilterData *data = user_data;
    guint32 reply_serial;
  
    if (incoming)
      {
        reply_serial = g_dbus_message_get_reply_serial (message);
        if (reply_serial == data->serial)
          data->num_handled += 1;
      }
    else
      {
        data->num_outgoing += 1;
      }
  
    return message;
  }
  
  
  typedef struct
  {
    gboolean alter_incoming;
    gboolean alter_outgoing;
  } FilterEffects;
  
  static GDBusMessage *
  other_filter_func (GDBusConnection *connection,
                     GDBusMessage    *message,
                     gboolean         incoming,
                     gpointer         user_data)
  {
    FilterEffects *effects = user_data;
    GDBusMessage *ret;
    gboolean alter;
  
    if (incoming)
      alter = effects->alter_incoming;
    else
      alter = effects->alter_outgoing;
  
    if (alter)
      {
        GDBusMessage *copy;
        GVariant *body;
        gchar *s;
        gchar *s2;
  
        copy = g_dbus_message_copy (message, NULL);
        g_object_unref (message);
  
        body = g_dbus_message_get_body (copy);
        g_variant_get (body, "(s)", &s);
        s2 = g_strdup_printf ("MOD: %s", s);
        g_dbus_message_set_body (copy, g_variant_new ("(s)", s2));
        g_free (s2);
        g_free (s);
  
        ret = copy;
      }
    else
      {
        ret = message;
      }
  
    return ret;
  }
  
  static void
  test_connection_filter_name_owner_changed_signal_handler (GDBusConnection  *connection,
                                                            const gchar      *sender_name,
                                                            const gchar      *object_path,
                                                            const gchar      *interface_name,
                                                            const gchar      *signal_name,
                                                            GVariant         *parameters,
                                                            gpointer         user_data)
  {
    const gchar *name;
    const gchar *old_owner;
    const gchar *new_owner;
  
    g_variant_get (parameters,
                   "(&s&s&s)",
                   &name,
                   &old_owner,
                   &new_owner);
  
    if (g_strcmp0 (name, "com.example.TestService") == 0 && strlen (new_owner) > 0)
      {
        g_main_loop_quit (loop);
      }
  }
  
  static gboolean
  test_connection_filter_on_timeout (gpointer user_data)
  {
    g_printerr ("Timeout waiting 30 sec on service\n");
    g_assert_not_reached ();
    return FALSE;
  }
  
  static void
  test_connection_filter (void)
  {
    GDBusConnection *c;
    FilterData data;
    GDBusMessage *m;
    GDBusMessage *m2;
    GDBusMessage *r;
    GError *error;
    guint filter_id;
    guint timeout_mainloop_id;
    guint signal_handler_id;
    FilterEffects effects;
    GVariant *result;
    const gchar *s;
  
    memset (&data, '\0', sizeof (FilterData));
  
    session_bus_up ();
  
    error = NULL;
    c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
    g_assert_no_error (error);
    g_assert (c != NULL);
  
    filter_id = g_dbus_connection_add_filter (c,
                                              filter_func,
                                              &data,
                                              NULL);
  
    m = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */
                                        "/org/freedesktop/DBus", /* path */
                                        "org.freedesktop.DBus", /* interface */
                                        "GetNameOwner");
    g_dbus_message_set_body (m, g_variant_new ("(s)", "org.freedesktop.DBus"));
    error = NULL;
    g_dbus_connection_send_message (c, m, G_DBUS_SEND_MESSAGE_FLAGS_NONE, &data.serial, &error);
    g_assert_no_error (error);
  
    while (data.num_handled == 0)
      g_thread_yield ();
  
    m2 = g_dbus_message_copy (m, &error);
    g_assert_no_error (error);
    g_dbus_connection_send_message (c, m2, G_DBUS_SEND_MESSAGE_FLAGS_NONE, &data.serial, &error);
    g_object_unref (m2);
    g_assert_no_error (error);
  
    while (data.num_handled == 1)
      g_thread_yield ();
  
    m2 = g_dbus_message_copy (m, &error);
    g_assert_no_error (error);
    g_dbus_message_set_serial (m2, data.serial);
    /* lock the message to test PRESERVE_SERIAL flag. */
    g_dbus_message_lock (m2);
    g_dbus_connection_send_message (c, m2, G_DBUS_SEND_MESSAGE_FLAGS_PRESERVE_SERIAL, &data.serial, &error);
    g_object_unref (m2);
    g_assert_no_error (error);
  
    while (data.num_handled == 2)
      g_thread_yield ();
  
    m2 = g_dbus_message_copy (m, &error);
    g_assert_no_error (error);
    r = g_dbus_connection_send_message_with_reply_sync (c,
                                                        m2,
                                                        G_DBUS_SEND_MESSAGE_FLAGS_NONE,
                                                        -1,
                                                        &data.serial,
                                                        NULL, /* GCancellable */
                                                        &error);
    g_object_unref (m2);
    g_assert_no_error (error);
    g_assert (r != NULL);
    g_object_unref (r);
    g_assert_cmpint (data.num_handled, ==, 4);
  
    g_dbus_connection_remove_filter (c, filter_id);
  
    m2 = g_dbus_message_copy (m, &error);
    g_assert_no_error (error);
    r = g_dbus_connection_send_message_with_reply_sync (c,
                                                        m2,
                                                        G_DBUS_SEND_MESSAGE_FLAGS_NONE,
                                                        -1,
                                                        &data.serial,
                                                        NULL, /* GCancellable */
                                                        &error);
    g_object_unref (m2);
    g_assert_no_error (error);
    g_assert (r != NULL);
    g_object_unref (r);
    g_assert_cmpint (data.num_handled, ==, 4);
    g_assert_cmpint (data.num_outgoing, ==, 4);
  
    /* wait for service to be available */
    signal_handler_id = g_dbus_connection_signal_subscribe (c,
                                                            "org.freedesktop.DBus", /* sender */
                                                            "org.freedesktop.DBus",
                                                            "NameOwnerChanged",
                                                            "/org/freedesktop/DBus",
                                                            NULL, /* arg0 */
                                                            G_DBUS_SIGNAL_FLAGS_NONE,
                                                            test_connection_filter_name_owner_changed_signal_handler,
                                                            NULL,
                                                            NULL);
    g_assert_cmpint (signal_handler_id, !=, 0);
  
    /* this is safe; testserver will exit once the bus goes away */
    g_assert (g_spawn_command_line_async (g_test_get_filename (G_TEST_BUILT, "gdbus-testserver", NULL), NULL));
  
    timeout_mainloop_id = g_timeout_add (30000, test_connection_filter_on_timeout, NULL);
    g_main_loop_run (loop);
    g_source_remove (timeout_mainloop_id);
    g_dbus_connection_signal_unsubscribe (c, signal_handler_id);
  
    /* now test some combinations... */
    filter_id = g_dbus_connection_add_filter (c,
                                              other_filter_func,
                                              &effects,
                                              NULL);
    /* -- */
    effects.alter_incoming = FALSE;
    effects.alter_outgoing = FALSE;
    error = NULL;
    result = g_dbus_connection_call_sync (c,
                                          "com.example.TestService",      /* bus name */
                                          "/com/example/TestObject",      /* object path */
                                          "com.example.Frob",             /* interface name */
                                          "HelloWorld",                   /* method name */
                                          g_variant_new ("(s)", "Cat"),   /* parameters */
                                          G_VARIANT_TYPE ("(s)"),         /* return type */
                                          G_DBUS_CALL_FLAGS_NONE,
                                          -1,
                                          NULL,
                                          &error);
    g_assert_no_error (error);
    g_variant_get (result, "(&s)", &s);
    g_assert_cmpstr (s, ==, "You greeted me with 'Cat'. Thanks!");
    g_variant_unref (result);
    /* -- */
    effects.alter_incoming = TRUE;
    effects.alter_outgoing = TRUE;
    error = NULL;
    result = g_dbus_connection_call_sync (c,
                                          "com.example.TestService",      /* bus name */
                                          "/com/example/TestObject",      /* object path */
                                          "com.example.Frob",             /* interface name */
                                          "HelloWorld",                   /* method name */
                                          g_variant_new ("(s)", "Cat"),   /* parameters */
                                          G_VARIANT_TYPE ("(s)"),         /* return type */
                                          G_DBUS_CALL_FLAGS_NONE,
                                          -1,
                                          NULL,
                                          &error);
    g_assert_no_error (error);
    g_variant_get (result, "(&s)", &s);
    g_assert_cmpstr (s, ==, "MOD: You greeted me with 'MOD: Cat'. Thanks!");
    g_variant_unref (result);
  
  
    g_dbus_connection_remove_filter (c, filter_id);
  
    g_object_unref (c);
    g_object_unref (m);
  
    session_bus_down ();
  }
  
  /* ---------------------------------------------------------------------------------------------------- */
  
  #define NUM_THREADS 50
  
  static void
  send_bogus_message (GDBusConnection *c, guint32 *out_serial)
  {
    GDBusMessage *m;
    GError *error;
  
    m = g_dbus_message_new_method_call ("org.freedesktop.DBus", /* name */
                                        "/org/freedesktop/DBus", /* path */
                                        "org.freedesktop.DBus", /* interface */
                                        "GetNameOwner");
    g_dbus_message_set_body (m, g_variant_new ("(s)", "org.freedesktop.DBus"));
    error = NULL;
    g_dbus_connection_send_message (c, m, G_DBUS_SEND_MESSAGE_FLAGS_NONE, out_serial, &error);
    g_assert_no_error (error);
    g_object_unref (m);
  }
  
  #define SLEEP_USEC (100 * 1000)
  
  static gpointer
  serials_thread_func (GDBusConnection *c)
  {
    guint32 message_serial;
    guint i;
  
    /* No calls on this thread yet */
    g_assert_cmpint (g_dbus_connection_get_last_serial(c), ==, 0);
  
    /* Send a bogus message and store its serial */
    message_serial = 0;
    send_bogus_message (c, &message_serial);
  
    /* Give it some time to actually send the message out. 10 seconds
     * should be plenty, even on slow machines. */
    for (i = 0; i < 10 * G_USEC_PER_SEC / SLEEP_USEC; i++)
      {
        if (g_dbus_connection_get_last_serial(c) != 0)
          break;
  
        g_usleep (SLEEP_USEC);
      }
  
    g_assert_cmpint (g_dbus_connection_get_last_serial(c), !=, 0);
    g_assert_cmpint (g_dbus_connection_get_last_serial(c), ==, message_serial);
  
    return NULL;
  }
  
  static void
  test_connection_serials (void)
  {
    GDBusConnection *c;
    GError *error;
    GThread *pool[NUM_THREADS];
    int i;
  
    session_bus_up ();
  
    error = NULL;
    c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
    g_assert_no_error (error);
    g_assert (c != NULL);
  
    /* Status after initialization */
    g_assert_cmpint (g_dbus_connection_get_last_serial (c), ==, 1);
  
    /* Send a bogus message */
    send_bogus_message (c, NULL);
    g_assert_cmpint (g_dbus_connection_get_last_serial (c), ==, 2);
  
    /* Start the threads */
    for (i = 0; i < NUM_THREADS; i++)
      pool[i] = g_thread_new (NULL, (GThreadFunc) serials_thread_func, c);
  
    /* Wait until threads are finished */
    for (i = 0; i < NUM_THREADS; i++)
        g_thread_join (pool[i]);
  
    /* No calls in between on this thread, should be the last value */
    g_assert_cmpint (g_dbus_connection_get_last_serial (c), ==, 2);
  
    send_bogus_message (c, NULL);
  
    /* All above calls + calls in threads */
    g_assert_cmpint (g_dbus_connection_get_last_serial (c), ==, 3 + NUM_THREADS);
  
    g_object_unref (c);
  
    session_bus_down ();
  }
  
  /* ---------------------------------------------------------------------------------------------------- */
  
  static void
  test_connection_basic (void)
  {
    GDBusConnection *connection;
    GError *error;
    GDBusCapabilityFlags flags;
    gchar *guid;
    gchar *name;
    gboolean closed;
    gboolean exit_on_close;
    GIOStream *stream;
    GCredentials *credentials;
  
    session_bus_up ();
  
    error = NULL;
    connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
    g_assert_no_error (error);
    g_assert (connection != NULL);
  
    flags = g_dbus_connection_get_capabilities (connection);
    g_assert (flags == G_DBUS_CAPABILITY_FLAGS_NONE ||
              flags == G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING);
  
    credentials = g_dbus_connection_get_peer_credentials (connection);
    g_assert (credentials == NULL);
  
    g_object_get (connection,
                  "stream", &stream,
                  "guid", &guid,
                  "unique-name", &name,
                  "closed", &closed,
                  "exit-on-close", &exit_on_close,
                  "capabilities", &flags,
                  NULL);
  
    g_assert (G_IS_IO_STREAM (stream));
    g_assert (g_dbus_is_guid (guid));
    g_assert (g_dbus_is_unique_name (name));
    g_assert (!closed);
    g_assert (exit_on_close);
    g_assert (flags == G_DBUS_CAPABILITY_FLAGS_NONE ||
              flags == G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING);
    g_object_unref (stream);
    g_free (name);
    g_free (guid);
  
    g_object_unref (connection);
  
    session_bus_down ();
  }
  
  /* ---------------------------------------------------------------------------------------------------- */
  
  int
  main (int   argc,
        char *argv[])
  {
    int ret;
    g_test_init (&argc, &argv, NULL);
  
    /* all the tests rely on a shared main loop */
    loop = g_main_loop_new (NULL, FALSE);
  
    g_test_dbus_unset ();
  
    /* gdbus cleanup is pretty racy due to worker threads, so always do this test first */
    g_test_add_func ("/gdbus/connection/bus-failure", test_connection_bus_failure);
  
    g_test_add_func ("/gdbus/connection/basic", test_connection_basic);
    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);
    g_test_add_func ("/gdbus/connection/signal-match-rules", test_connection_signal_match_rules);
    g_test_add_func ("/gdbus/connection/filter", test_connection_filter);
    g_test_add_func ("/gdbus/connection/serials", test_connection_serials);
    ret = g_test_run();
  
    g_main_loop_unref (loop);
    return ret;
  }
  