Add a regression test for being a new-style monitor
authorSimon McVittie <simon.mcvittie@collabora.co.uk>
Mon, 2 Feb 2015 20:02:56 +0000 (20:02 +0000)
committerSimon McVittie <simon.mcvittie@collabora.co.uk>
Wed, 4 Feb 2015 17:15:24 +0000 (17:15 +0000)
This includes most of the situations I could think of:

* method call on dbus-daemon and response
* NameOwnerChanged
* NameAcquired, NameLost (although I'm not 100% sure these should
  get captured, since they're redundant with NameOwnerChanged)
* unicast message is allowed through
* unicast message is rejected by no-sending or no-receiving policy
* broadcast is allowed through
* broadcast is rejected by no-sending policy (the error reply
  is also captured)
* broadcast is rejected by no-receiving policy (there is no error
  reply)
* message causing service activation, and the message telling systemd
  to do the actual activation
* systemd reporting that activation failed

It does not cover:

* sending a message to dbus-daemon, then provoking a reply, then
  dbus-daemon does not allow itself to send the reply due to its
  own security policy

This is such an obscure corner case that I'm not even convinced it's
testable without dropping down into lower-level socket manipulation:
dbus-daemon's replies are always assumed to be requested replies,
and replies contain so little other metadata that I think we can
only forbid them by forbidding all method replies. If we do that,
the reply to Hello() won't arrive and the client-side connection will
not become active.

Bug: https://bugs.freedesktop.org/show_bug.cgi?id=46787
Reviewed-by: Philip Withnall <philip.withnall@collabora.co.uk>
test/Makefile.am
test/data/valid-config-files/forbidding.conf.in [new file with mode: 0644]
test/monitor.c [new file with mode: 0644]

index 8b84c7d222bfc777590c2cf72964d219f08b6d50..6d721856d83733cd3307fad8f4b4e17d61149646 100644 (file)
@@ -156,6 +156,7 @@ installable_tests += \
        test-corrupt \
        test-dbus-daemon \
        test-dbus-daemon-eavesdrop \
+       test-monitor \
        test-loopback \
        test-marshal \
        test-refs \
@@ -245,6 +246,15 @@ test_marshal_LDADD = \
     $(GLIB_LIBS) \
     $(NULL)
 
+test_monitor_SOURCES = \
+    monitor.c \
+    $(NULL)
+test_monitor_CPPFLAGS = $(testutils_shared_if_possible_cppflags)
+test_monitor_LDADD = \
+    $(testutils_shared_if_possible_libs) \
+    $(GLIB_LIBS) \
+    $(NULL)
+
 test_syntax_SOURCES = syntax.c
 test_syntax_LDADD = \
     $(top_builddir)/dbus/libdbus-1.la \
@@ -295,6 +305,7 @@ in_data = \
        data/valid-config-files/debug-allow-all-sha1.conf.in \
        data/valid-config-files/debug-allow-all.conf.in \
        data/valid-config-files/finite-timeout.conf.in \
+       data/valid-config-files/forbidding.conf.in \
        data/valid-config-files/incoming-limit.conf.in \
        data/valid-config-files/multi-user.conf.in \
        data/valid-config-files/systemd-activation.conf.in \
diff --git a/test/data/valid-config-files/forbidding.conf.in b/test/data/valid-config-files/forbidding.conf.in
new file mode 100644 (file)
index 0000000..6a674f8
--- /dev/null
@@ -0,0 +1,18 @@
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+  <!-- Our well-known bus type, don't change this -->
+  <type>session</type>
+  <listen>@TEST_LISTEN@</listen>
+
+  <policy context="default">
+    <!-- Allow everything -->
+    <allow send_destination="*"/>
+    <allow receive_sender="*"/>
+    <allow own="*"/>
+
+    <!-- Exception: some messages are forbidden -->
+    <deny send_interface="com.example.CannotSend"/>
+    <deny receive_interface="com.example.CannotReceive"/>
+  </policy>
+</busconfig>
diff --git a/test/monitor.c b/test/monitor.c
new file mode 100644 (file)
index 0000000..8f71da5
--- /dev/null
@@ -0,0 +1,1497 @@
+/* Integration tests for monitor-mode D-Bus connections
+ *
+ * Copyright © 2010-2011 Nokia Corporation
+ * Copyright © 2015 Collabora Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include "test-utils-glib.h"
+
+typedef struct {
+    const char *config_file;
+    const char * const *match_rules;
+    gboolean care_about_our_names;
+} Config;
+
+typedef struct {
+    const Config *config;
+    TestMainContext *ctx;
+    DBusError e;
+    GError *ge;
+
+    gchar *address;
+    GPid daemon_pid;
+
+    DBusConnection *monitor;
+    DBusConnection *sender;
+    DBusConnection *recipient;
+
+    GQueue monitored;
+
+    const char *monitor_name;
+    const char *sender_name;
+    const char *recipient_name;
+
+    DBusConnection *systemd;
+    const char *systemd_name;
+    DBusMessage *systemd_message;
+    DBusConnection *activated;
+    const char *activated_name;
+    DBusMessage *activated_message;
+} Fixture;
+
+static const char * const no_match_rules[] = {
+    NULL
+};
+
+static const char * const wildcard_match_rules[] = {
+    "",
+    NULL,
+    FALSE
+};
+
+static const char * const eavesdrop_match_rules[] = {
+    "eavesdrop=true",
+    NULL,
+    FALSE
+};
+
+static const char * const no_eavesdrop_match_rules[] = {
+    "eavesdrop=false",
+    NULL,
+    FALSE
+};
+
+static const char * const selective_match_rules[] = {
+    "interface='com.example.Interesting'",
+    "interface='com.example.Fun'",
+    NULL,
+    FALSE
+};
+
+static Config forbidding_config = {
+    "valid-config-files/forbidding.conf",
+    NULL,
+    FALSE
+};
+
+static Config wildcard_config = {
+    NULL,
+    wildcard_match_rules,
+    FALSE
+};
+
+static Config selective_config = {
+    NULL,
+    selective_match_rules,
+    FALSE
+};
+
+static Config no_rules_config = {
+    NULL,
+    no_match_rules,
+    FALSE
+};
+
+static Config eavesdrop_config = {
+    NULL,
+    eavesdrop_match_rules,
+    FALSE
+};
+
+static Config no_eavesdrop_config = {
+    NULL,
+    no_eavesdrop_match_rules,
+    FALSE
+};
+
+static Config fake_systemd_config = {
+    "valid-config-files/systemd-activation.conf",
+    NULL,
+    FALSE
+};
+
+static Config side_effects_config = {
+    NULL,
+    NULL,
+    TRUE
+};
+
+static inline const char *
+not_null2 (const char *x,
+    const char *fallback)
+{
+  if (x == NULL)
+    return fallback;
+
+  return x;
+}
+
+static inline const char *
+not_null (const char *x)
+{
+  return not_null2 (x, "(null)");
+}
+
+#define log_message(m) _log_message (m, __FILE__, __LINE__)
+
+G_GNUC_UNUSED
+static void
+_log_message (DBusMessage *m,
+    const char *file,
+    int line)
+{
+  g_message ("%s:%d: message type %d (%s)", file, line,
+      dbus_message_get_type (m),
+      dbus_message_type_to_string (dbus_message_get_type (m)));
+  g_message ("\tfrom: %s",
+      not_null2 (dbus_message_get_sender (m), "(dbus-daemon)"));
+  g_message ("\tto: %s",
+      not_null2 (dbus_message_get_destination (m), "(broadcast)"));
+  g_message ("\tpath: %s",
+      not_null (dbus_message_get_path (m)));
+  g_message ("\tinterface: %s",
+      not_null (dbus_message_get_interface (m)));
+  g_message ("\tmember: %s",
+      not_null (dbus_message_get_member (m)));
+  g_message ("\tsignature: %s",
+      not_null (dbus_message_get_signature (m)));
+  g_message ("\terror name: %s",
+      not_null (dbus_message_get_error_name (m)));
+
+  if (strcmp ("s", dbus_message_get_signature (m)) == 0)
+    {
+      DBusError e = DBUS_ERROR_INIT;
+      const char *s;
+
+      dbus_message_get_args (m, &e,
+            DBUS_TYPE_STRING, &s,
+            DBUS_TYPE_INVALID);
+      test_assert_no_error (&e);
+      g_message ("\tstring payload: %s", s);
+    }
+}
+
+/* these are macros so they get the right line number */
+
+#define assert_hello(m) \
+do { \
+  g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \
+      ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_METHOD_CALL)); \
+  g_assert_cmpstr (dbus_message_get_destination (m), ==, DBUS_SERVICE_DBUS); \
+  g_assert_cmpstr (dbus_message_get_path (m), ==, DBUS_PATH_DBUS); \
+  g_assert_cmpstr (dbus_message_get_interface (m), ==, DBUS_INTERFACE_DBUS); \
+  g_assert_cmpstr (dbus_message_get_member (m), ==, "Hello"); \
+  g_assert_cmpstr (dbus_message_get_signature (m), ==, ""); \
+  g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \
+  g_assert_cmpint (dbus_message_get_reply_serial (m), ==, 0); \
+} while (0)
+
+#define assert_hello_reply(m) \
+do { \
+  DBusError _e = DBUS_ERROR_INIT; \
+  const char *_s; \
+    \
+  g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \
+      ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_METHOD_RETURN)); \
+  g_assert_cmpstr (dbus_message_get_sender (m), ==, DBUS_SERVICE_DBUS); \
+  g_assert_cmpstr (dbus_message_get_path (m), ==, NULL); \
+  g_assert_cmpstr (dbus_message_get_interface (m), ==, NULL); \
+  g_assert_cmpstr (dbus_message_get_member (m), ==, NULL); \
+  g_assert_cmpstr (dbus_message_get_signature (m), ==, "s"); \
+  g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \
+  g_assert_cmpint (dbus_message_get_reply_serial (m), !=, 0); \
+    \
+  dbus_message_get_args (m, &_e, \
+        DBUS_TYPE_STRING, &_s, \
+        DBUS_TYPE_INVALID); \
+  test_assert_no_error (&_e); \
+  g_assert_cmpstr (dbus_message_get_destination (m), ==, _s); \
+} while (0)
+
+#define assert_name_acquired(m) \
+do { \
+  DBusError _e = DBUS_ERROR_INIT; \
+  const char *_s; \
+    \
+  g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \
+      ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_SIGNAL)); \
+  g_assert_cmpstr (dbus_message_get_sender (m), ==, DBUS_SERVICE_DBUS); \
+  g_assert_cmpstr (dbus_message_get_path (m), ==, DBUS_PATH_DBUS); \
+  g_assert_cmpstr (dbus_message_get_interface (m), ==, DBUS_INTERFACE_DBUS); \
+  g_assert_cmpstr (dbus_message_get_member (m), ==, "NameAcquired"); \
+  g_assert_cmpstr (dbus_message_get_signature (m), ==, "s"); \
+  g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \
+  g_assert_cmpint (dbus_message_get_reply_serial (m), ==, 0); \
+    \
+  dbus_message_get_args (m, &_e, \
+        DBUS_TYPE_STRING, &_s, \
+        DBUS_TYPE_INVALID); \
+  test_assert_no_error (&_e); \
+  g_assert_cmpstr (dbus_message_get_destination (m), ==, _s); \
+} while (0)
+
+#define assert_method_call(m, sender, \
+    destination, path, iface, method, signature) \
+do { \
+  g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \
+      ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_METHOD_CALL)); \
+  g_assert_cmpstr (dbus_message_get_sender (m), ==, sender); \
+  g_assert_cmpstr (dbus_message_get_destination (m), ==, destination); \
+  g_assert_cmpstr (dbus_message_get_path (m), ==, path); \
+  g_assert_cmpstr (dbus_message_get_interface (m), ==, iface); \
+  g_assert_cmpstr (dbus_message_get_member (m), ==, method); \
+  g_assert_cmpstr (dbus_message_get_signature (m), ==, signature); \
+  g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \
+  g_assert_cmpint (dbus_message_get_reply_serial (m), ==, 0); \
+} while (0)
+
+#define assert_signal(m, \
+    sender, path, iface, member, signature, \
+    destination) \
+do { \
+  g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \
+      ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_SIGNAL)); \
+  g_assert_cmpstr (dbus_message_get_sender (m), ==, sender); \
+  g_assert_cmpstr (dbus_message_get_destination (m), ==, destination); \
+  g_assert_cmpstr (dbus_message_get_path (m), ==, path); \
+  g_assert_cmpstr (dbus_message_get_interface (m), ==, iface); \
+  g_assert_cmpstr (dbus_message_get_member (m), ==, member); \
+  g_assert_cmpstr (dbus_message_get_signature (m), ==, signature); \
+  g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \
+  g_assert_cmpint (dbus_message_get_reply_serial (m), ==, 0); \
+} while (0)
+
+#define assert_method_reply(m, sender, destination, signature) \
+do { \
+  g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \
+      ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_METHOD_RETURN)); \
+  g_assert_cmpstr (dbus_message_get_sender (m), ==, sender); \
+  g_assert_cmpstr (dbus_message_get_destination (m), ==, destination); \
+  g_assert_cmpstr (dbus_message_get_path (m), ==, NULL); \
+  g_assert_cmpstr (dbus_message_get_interface (m), ==, NULL); \
+  g_assert_cmpstr (dbus_message_get_member (m), ==, NULL); \
+  g_assert_cmpstr (dbus_message_get_signature (m), ==, signature); \
+  g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \
+  g_assert_cmpint (dbus_message_get_reply_serial (m), !=, 0); \
+} while (0)
+
+#define assert_error_reply(m, sender, destination, error_name) \
+do { \
+  g_assert_cmpstr (dbus_message_type_to_string (dbus_message_get_type (m)), \
+      ==, dbus_message_type_to_string (DBUS_MESSAGE_TYPE_ERROR)); \
+  g_assert_cmpstr (dbus_message_get_sender (m), ==, sender); \
+  g_assert_cmpstr (dbus_message_get_destination (m), ==, destination); \
+  g_assert_cmpstr (dbus_message_get_error_name (m), ==, error_name); \
+  g_assert_cmpstr (dbus_message_get_path (m), ==, NULL); \
+  g_assert_cmpstr (dbus_message_get_interface (m), ==, NULL); \
+  g_assert_cmpstr (dbus_message_get_member (m), ==, NULL); \
+  g_assert_cmpstr (dbus_message_get_signature (m), ==, "s"); \
+  g_assert_cmpint (dbus_message_get_serial (m), !=, 0); \
+  g_assert_cmpint (dbus_message_get_reply_serial (m), !=, 0); \
+} while (0)
+
+/* This is called after processing pending replies to our own method
+ * calls, but before anything else.
+ */
+static DBusHandlerResult
+monitor_filter (DBusConnection *connection,
+    DBusMessage *message,
+    void *user_data)
+{
+  Fixture *f = user_data;
+
+  g_assert_cmpstr (dbus_message_get_interface (message), !=,
+      "com.example.Tedious");
+
+  /* we are not interested in the monitor getting NameAcquired or NameLost
+   * for most tests */
+  if (f->config == NULL || !f->config->care_about_our_names)
+    {
+      if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
+            "NameAcquired") ||
+          dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
+            "NameLost"))
+        {
+          DBusError e = DBUS_ERROR_INIT;
+          const char *s;
+
+          dbus_message_get_args (message, &e,
+                DBUS_TYPE_STRING, &s,
+                DBUS_TYPE_INVALID);
+          test_assert_no_error (&e);
+
+          if (strcmp (s, f->monitor_name) == 0)
+            {
+              /* ignore */
+              return DBUS_HANDLER_RESULT_HANDLED;
+            }
+        }
+    }
+
+  g_queue_push_tail (&f->monitored, dbus_message_ref (message));
+
+  return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static DBusHandlerResult
+recipient_filter (DBusConnection *connection,
+    DBusMessage *message,
+    void *user_data)
+{
+  g_assert_cmpstr (dbus_message_get_interface (message), !=,
+      "com.example.CannotSend");
+  g_assert_cmpstr (dbus_message_get_interface (message), !=,
+      "com.example.CannotReceive");
+
+  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusHandlerResult
+systemd_filter (DBusConnection *connection,
+    DBusMessage *message,
+    void *user_data)
+{
+  Fixture *f = user_data;
+
+  if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
+        "NameAcquired") ||
+      dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
+        "NameLost"))
+    {
+      return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+
+  g_assert (f->systemd_message == NULL);
+  f->systemd_message = dbus_message_ref (message);
+
+  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusHandlerResult
+activated_filter (DBusConnection *connection,
+    DBusMessage *message,
+    void *user_data)
+{
+  Fixture *f = user_data;
+
+  if (dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
+        "NameAcquired") ||
+      dbus_message_is_signal (message, DBUS_INTERFACE_DBUS,
+        "NameLost"))
+    {
+      return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    }
+
+  g_assert (f->activated_message == NULL);
+  f->activated_message = dbus_message_ref (message);
+
+  return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static void
+setup (Fixture *f,
+    gconstpointer context)
+{
+  f->config = context;
+
+  f->ctx = test_main_context_get ();
+
+  f->ge = NULL;
+  dbus_error_init (&f->e);
+
+  f->address = test_get_dbus_daemon (f->config ? f->config->config_file : NULL,
+      TEST_USER_ME, &f->daemon_pid);
+
+  if (f->address == NULL)
+    return;
+
+  f->monitor = test_connect_to_bus (f->ctx, f->address);
+  f->monitor_name = dbus_bus_get_unique_name (f->monitor);
+  f->sender = test_connect_to_bus (f->ctx, f->address);
+  f->sender_name = dbus_bus_get_unique_name (f->sender);
+  f->recipient = test_connect_to_bus (f->ctx, f->address);
+  f->recipient_name = dbus_bus_get_unique_name (f->recipient);
+
+  if (!dbus_connection_add_filter (f->monitor, monitor_filter, f, NULL))
+    g_error ("OOM");
+
+  if (!dbus_connection_add_filter (f->recipient, recipient_filter, f, NULL))
+    g_error ("OOM");
+}
+
+static void
+become_monitor (Fixture *f)
+{
+  DBusMessage *m;
+  DBusPendingCall *pc;
+  dbus_bool_t ok;
+  DBusMessageIter appender, array_appender;
+  const char * const *match_rules;
+  int i;
+  dbus_uint32_t zero = 0;
+
+  if (f->config != NULL && f->config->match_rules != NULL)
+    match_rules = f->config->match_rules;
+  else
+    match_rules = wildcard_match_rules;
+
+  m = dbus_message_new_method_call (DBUS_SERVICE_DBUS,
+      DBUS_PATH_DBUS, DBUS_INTERFACE_MONITORING, "BecomeMonitor");
+
+  if (m == NULL)
+    g_error ("OOM");
+
+  dbus_message_iter_init_append (m, &appender);
+
+  if (!dbus_message_iter_open_container (&appender, DBUS_TYPE_ARRAY, "s",
+        &array_appender))
+    g_error ("OOM");
+
+  for (i = 0; match_rules[i] != NULL; i++)
+    {
+      if (!dbus_message_iter_append_basic (&array_appender, DBUS_TYPE_STRING,
+            &match_rules[i]))
+        g_error ("OOM");
+    }
+
+  if (!dbus_message_iter_close_container (&appender, &array_appender) ||
+      !dbus_message_iter_append_basic (&appender, DBUS_TYPE_UINT32, &zero))
+    g_error ("OOM");
+
+  if (!dbus_connection_send_with_reply (f->monitor, m, &pc,
+        DBUS_TIMEOUT_USE_DEFAULT) ||
+      pc == NULL)
+    g_error ("OOM");
+
+  dbus_message_unref (m);
+  m = NULL;
+
+  if (dbus_pending_call_get_completed (pc))
+    test_pending_call_store_reply (pc, &m);
+  else if (!dbus_pending_call_set_notify (pc, test_pending_call_store_reply,
+        &m, NULL))
+    g_error ("OOM");
+
+  while (m == NULL)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  ok = dbus_message_get_args (m, &f->e,
+      DBUS_TYPE_INVALID);
+  test_assert_no_error (&f->e);
+  g_assert (ok);
+
+  dbus_pending_call_unref (pc);
+  dbus_message_unref (m);
+  m = NULL;
+}
+
+/*
+ * Test the side-effects of becoming a monitor.
+ */
+static void
+test_become_monitor (Fixture *f,
+    gconstpointer context)
+{
+  DBusMessage *m;
+  int ret;
+  dbus_bool_t got_unique = FALSE, got_a = FALSE, got_b = FALSE, got_c = FALSE;
+  dbus_bool_t lost_unique = FALSE, lost_a = FALSE, lost_b = FALSE, lost_c = FALSE;
+
+  if (f->address == NULL)
+    return;
+
+  ret = dbus_bus_request_name (f->monitor, "com.example.A",
+      DBUS_NAME_FLAG_DO_NOT_QUEUE, &f->e);
+  test_assert_no_error (&f->e);
+  g_assert_cmpint (ret, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
+
+  ret = dbus_bus_request_name (f->monitor, "com.example.B",
+      DBUS_NAME_FLAG_DO_NOT_QUEUE, &f->e);
+  test_assert_no_error (&f->e);
+  g_assert_cmpint (ret, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
+
+  ret = dbus_bus_request_name (f->monitor, "com.example.C",
+      DBUS_NAME_FLAG_DO_NOT_QUEUE, &f->e);
+  test_assert_no_error (&f->e);
+  g_assert_cmpint (ret, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
+
+  while (!got_unique || !got_a || !got_b || !got_c)
+    {
+      test_main_context_iterate (f->ctx, TRUE);
+
+      while ((m = g_queue_pop_head (&f->monitored)) != NULL)
+        {
+          if (dbus_message_is_signal (m, DBUS_INTERFACE_DBUS,
+                "NameAcquired"))
+            {
+              const char *name;
+              dbus_bool_t ok = dbus_message_get_args (m, &f->e,
+                  DBUS_TYPE_STRING, &name,
+                  DBUS_TYPE_INVALID);
+
+              g_assert_cmpstr (dbus_message_get_path (m), ==,
+                  DBUS_PATH_DBUS);
+
+              test_assert_no_error (&f->e);
+              g_assert (ok);
+
+              if (g_str_equal (name, f->monitor_name))
+                {
+                  g_assert (!got_unique);
+                  got_unique = TRUE;
+                }
+              else if (g_str_equal (name, "com.example.A"))
+                {
+                  g_assert (!got_a);
+                  got_a = TRUE;
+                }
+              else if (g_str_equal (name, "com.example.B"))
+                {
+                  g_assert (!got_b);
+                  got_b = TRUE;
+                }
+              else
+                {
+                  g_assert_cmpstr (name, ==, "com.example.C");
+                  g_assert (!got_c);
+                  got_c = TRUE;
+                }
+            }
+          else
+            {
+              g_error ("unexpected message %s.%s",
+                  dbus_message_get_interface (m),
+                  dbus_message_get_member (m));
+            }
+
+          dbus_message_unref (m);
+        }
+    }
+
+  become_monitor (f);
+
+  while (!lost_unique || !lost_a || !lost_b || !lost_c)
+    {
+      test_main_context_iterate (f->ctx, TRUE);
+
+      while ((m = g_queue_pop_head (&f->monitored)) != NULL)
+        {
+          if (dbus_message_is_signal (m, DBUS_INTERFACE_DBUS,
+                "NameLost"))
+            {
+              const char *name;
+              dbus_bool_t ok = dbus_message_get_args (m, &f->e,
+                  DBUS_TYPE_STRING, &name,
+                  DBUS_TYPE_INVALID);
+
+              test_assert_no_error (&f->e);
+              g_assert (ok);
+
+              if (g_str_equal (name, f->monitor_name))
+                {
+                  g_assert (!lost_unique);
+                  lost_unique = TRUE;
+                }
+              else if (g_str_equal (name, "com.example.A"))
+                {
+                  g_assert (!lost_a);
+                  lost_a = TRUE;
+                }
+              else if (g_str_equal (name, "com.example.B"))
+                {
+                  g_assert (!lost_b);
+                  lost_b = TRUE;
+                }
+              else
+                {
+                  g_assert_cmpstr (name, ==, "com.example.C");
+                  g_assert (!lost_c);
+                  lost_c = TRUE;
+                }
+            }
+          else
+            {
+              g_error ("unexpected message %s.%s",
+                  dbus_message_get_interface (m),
+                  dbus_message_get_member (m));
+            }
+
+          dbus_message_unref (m);
+        }
+    }
+
+  /* Calling methods is forbidden; we get disconnected. */
+  dbus_bus_add_match (f->monitor, "", &f->e);
+  g_assert_cmpstr (f->e.name, ==, DBUS_ERROR_NO_REPLY);
+  g_assert (!dbus_connection_get_is_connected (f->monitor));
+
+  while (TRUE)
+    {
+      test_main_context_iterate (f->ctx, TRUE);
+
+      /* When we iterate all the connection's messages, we see ourselves
+       * losing all our names, then we're disconnected. */
+      while ((m = g_queue_pop_head (&f->monitored)) != NULL)
+        {
+          if (dbus_message_is_signal (m, DBUS_INTERFACE_LOCAL, "Disconnected"))
+            {
+              dbus_message_unref (m);
+              goto disconnected;
+            }
+          else
+            {
+              g_error ("unexpected message %s.%s",
+                  dbus_message_get_interface (m),
+                  dbus_message_get_member (m));
+            }
+
+          dbus_message_unref (m);
+        }
+    }
+
+disconnected:
+
+  g_assert (lost_a);
+  g_assert (lost_b);
+  g_assert (lost_c);
+}
+
+static void
+test_broadcast (Fixture *f,
+    gconstpointer context)
+{
+  DBusMessage *m;
+
+  if (f->address == NULL)
+    return;
+
+  dbus_bus_add_match (f->recipient, "type='signal'", &f->e);
+  test_assert_no_error (&f->e);
+
+  become_monitor (f);
+
+  m = dbus_message_new_signal ("/foo", "com.example.bar", "BroadcastSignal1");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  m = dbus_message_new_signal ("/foo", "com.example.bar", "BroadcastSignal2");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  m = dbus_message_new_signal ("/foo", "com.example.bar", "BroadcastSignal3");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  while (g_queue_get_length (&f->monitored) < 3)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.bar",
+      "BroadcastSignal1", "", NULL);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.bar",
+      "BroadcastSignal2", "", NULL);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.bar",
+      "BroadcastSignal3", "", NULL);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  g_assert (m == NULL);
+}
+
+static void
+test_forbidden_broadcast (Fixture *f,
+    gconstpointer context)
+{
+  DBusMessage *m;
+
+  if (f->address == NULL)
+    return;
+
+  dbus_bus_add_match (f->recipient, "type='signal'", &f->e);
+  test_assert_no_error (&f->e);
+
+  become_monitor (f);
+
+  m = dbus_message_new_signal ("/foo", "com.example.CannotSend",
+      "BroadcastSignal1");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  m = dbus_message_new_signal ("/foo", "com.example.CannotReceive",
+      "BroadcastSignal2");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  m = dbus_message_new_signal ("/foo", "com.example.CannotSend",
+      "BroadcastSignal3");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  while (g_queue_get_length (&f->monitored) < 6)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.CannotSend",
+      "BroadcastSignal1", "", NULL);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_error_reply (m, DBUS_SERVICE_DBUS, f->sender_name,
+      DBUS_ERROR_ACCESS_DENIED);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.CannotReceive",
+      "BroadcastSignal2", "", NULL);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_error_reply (m, DBUS_SERVICE_DBUS, f->sender_name,
+      DBUS_ERROR_ACCESS_DENIED);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo", "com.example.CannotSend",
+      "BroadcastSignal3", "", NULL);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_error_reply (m, DBUS_SERVICE_DBUS, f->sender_name,
+      DBUS_ERROR_ACCESS_DENIED);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  g_assert (m == NULL);
+}
+
+static void
+test_unicast_signal (Fixture *f,
+    gconstpointer context)
+{
+  DBusMessage *m;
+
+  if (f->address == NULL)
+    return;
+
+  become_monitor (f);
+
+  m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal1");
+  if (!dbus_message_set_destination (m, f->recipient_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal2");
+  if (!dbus_message_set_destination (m, f->recipient_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal3");
+  if (!dbus_message_set_destination (m, f->recipient_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  while (g_queue_get_length (&f->monitored) < 3)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo",
+      "com.example.bar", "UnicastSignal1", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo",
+      "com.example.bar", "UnicastSignal2", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo",
+      "com.example.bar", "UnicastSignal3", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  g_assert (m == NULL);
+}
+
+static void
+test_forbidden (Fixture *f,
+    gconstpointer context)
+{
+  DBusMessage *m;
+
+  if (f->address == NULL)
+    return;
+
+  become_monitor (f);
+
+  m = dbus_message_new_signal ("/foo", "com.example.CannotSend",
+      "UnicastSignal1");
+  if (!dbus_message_set_destination (m, f->recipient_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  m = dbus_message_new_signal ("/foo", "com.example.CannotReceive",
+      "UnicastSignal2");
+  if (!dbus_message_set_destination (m, f->recipient_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  m = dbus_message_new_signal ("/foo", "com.example.CannotSend",
+      "UnicastSignal3");
+  if (!dbus_message_set_destination (m, f->recipient_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  while (g_queue_get_length (&f->monitored) < 6)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo",
+      "com.example.CannotSend", "UnicastSignal1", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_error_reply (m, DBUS_SERVICE_DBUS, f->sender_name,
+      DBUS_ERROR_ACCESS_DENIED);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo",
+      "com.example.CannotReceive", "UnicastSignal2", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_error_reply (m, DBUS_SERVICE_DBUS, f->sender_name,
+      DBUS_ERROR_ACCESS_DENIED);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo",
+      "com.example.CannotSend", "UnicastSignal3", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_error_reply (m, DBUS_SERVICE_DBUS, f->sender_name,
+      DBUS_ERROR_ACCESS_DENIED);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  g_assert (m == NULL);
+}
+
+static void
+test_method_call (Fixture *f,
+    gconstpointer context)
+{
+  DBusMessage *m;
+
+  if (f->address == NULL)
+    return;
+
+  become_monitor (f);
+
+  m = dbus_message_new_method_call (f->recipient_name, "/foo", "com.example.bar",
+      "Call1");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  while (g_queue_get_length (&f->monitored) < 2)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_method_call (m, f->sender_name, f->recipient_name, "/foo",
+      "com.example.bar", "Call1", "");
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_error_reply (m, f->recipient_name, f->sender_name,
+      DBUS_ERROR_UNKNOWN_METHOD);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  g_assert (m == NULL);
+}
+
+static void
+test_forbidden_method_call (Fixture *f,
+    gconstpointer context)
+{
+  DBusMessage *m;
+
+  if (f->address == NULL)
+    return;
+
+  become_monitor (f);
+
+  m = dbus_message_new_method_call (f->recipient_name, "/foo",
+      "com.example.CannotSend", "Call1");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  while (g_queue_get_length (&f->monitored) < 2)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_method_call (m, f->sender_name, f->recipient_name, "/foo",
+      "com.example.CannotSend", "Call1", "");
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_error_reply (m, DBUS_SERVICE_DBUS, f->sender_name,
+      DBUS_ERROR_ACCESS_DENIED);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  g_assert (m == NULL);
+
+  m = dbus_message_new_method_call (f->recipient_name, "/foo",
+      "com.example.CannotReceive", "Call2");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  while (g_queue_get_length (&f->monitored) < 2)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_method_call (m, f->sender_name, f->recipient_name, "/foo",
+      "com.example.CannotReceive", "Call2", "");
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_error_reply (m, DBUS_SERVICE_DBUS, f->sender_name,
+      DBUS_ERROR_ACCESS_DENIED);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  g_assert (m == NULL);
+}
+
+static void
+test_dbus_daemon (Fixture *f,
+    gconstpointer context)
+{
+  DBusMessage *m;
+  int res;
+
+  if (f->address == NULL)
+    return;
+
+  become_monitor (f);
+
+  res = dbus_bus_request_name (f->sender, "com.example.Sender",
+      DBUS_NAME_FLAG_DO_NOT_QUEUE, &f->e);
+  test_assert_no_error (&f->e);
+  g_assert_cmpint (res, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
+
+  res = dbus_bus_release_name (f->sender, "com.example.Sender", &f->e);
+  test_assert_no_error (&f->e);
+  g_assert_cmpint (res, ==, DBUS_RELEASE_NAME_REPLY_RELEASED);
+
+  while (g_queue_get_length (&f->monitored) < 8)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_method_call (m, f->sender_name, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
+      DBUS_INTERFACE_DBUS, "RequestName", "su");
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
+      "NameOwnerChanged", "sss", NULL);
+  dbus_message_unref (m);
+
+  /* FIXME: should we get this? */
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
+      "NameAcquired", "s", f->sender_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_method_reply (m, DBUS_SERVICE_DBUS, f->sender_name, "u");
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_method_call (m, f->sender_name, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
+      DBUS_INTERFACE_DBUS, "ReleaseName", "s");
+  dbus_message_unref (m);
+
+  /* FIXME: should we get this? */
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
+      "NameLost", "s", f->sender_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
+      "NameOwnerChanged", "sss", NULL);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_method_reply (m, DBUS_SERVICE_DBUS, f->sender_name, "u");
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  g_assert (m == NULL);
+}
+
+static void
+test_selective (Fixture *f,
+    gconstpointer context)
+{
+  DBusMessage *m;
+
+  if (f->address == NULL)
+    return;
+
+  /* Match rules added before becoming a monitor should be cleared:
+   * if they weren't, this test would get Interesting twice, then Tedious,
+   * and only see Fun after that. */
+  dbus_bus_add_match (f->monitor,
+      "eavesdrop='true',interface='com.example.Interesting'", &f->e);
+  test_assert_no_error (&f->e);
+  dbus_bus_add_match (f->monitor,
+      "eavesdrop='true',interface='com.example.Tedious'", &f->e);
+  test_assert_no_error (&f->e);
+
+  become_monitor (f);
+
+  m = dbus_message_new_signal ("/foo", "com.example.Interesting",
+      "UnicastSignal1");
+  if (!dbus_message_set_destination (m, f->recipient_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  m = dbus_message_new_signal ("/foo", "com.example.Tedious",
+      "UnicastSignal2");
+  if (!dbus_message_set_destination (m, f->recipient_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  m = dbus_message_new_signal ("/foo", "com.example.Fun",
+      "UnicastSignal3");
+  if (!dbus_message_set_destination (m, f->recipient_name))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  while (g_queue_get_length (&f->monitored) < 2)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  /* We get the interesting signal and the fun signal, but not the tedious
+   * signal. */
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo",
+      "com.example.Interesting", "UnicastSignal1", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo",
+      "com.example.Fun", "UnicastSignal3", "", f->recipient_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  g_assert (m == NULL);
+}
+
+static void
+expect_new_connection (Fixture *f)
+{
+  DBusMessage *m;
+
+  while (g_queue_get_length (&f->monitored) < 4)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_hello (m);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_hello_reply (m);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
+      "NameOwnerChanged", "sss", NULL);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_name_acquired (m);
+  dbus_message_unref (m);
+}
+
+static void
+take_well_known_name (Fixture *f,
+    DBusConnection *connection,
+    const char *name)
+{
+  int ret;
+
+  ret = dbus_bus_request_name (connection, name,
+      DBUS_NAME_FLAG_DO_NOT_QUEUE, &f->e);
+  test_assert_no_error (&f->e);
+  g_assert_cmpint (ret, ==, DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER);
+}
+
+static void
+expect_take_well_known_name (Fixture *f,
+    DBusConnection *connection,
+    const char *name)
+{
+  DBusMessage *m;
+  const char *connection_name = dbus_bus_get_unique_name (connection);
+
+  while (g_queue_get_length (&f->monitored) < 4)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_method_call (m, connection_name, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
+      DBUS_INTERFACE_DBUS, "RequestName", "su");
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
+      "NameOwnerChanged", "sss", NULL);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS,
+      "NameAcquired", "s", connection_name);
+  dbus_message_unref (m);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_method_reply (m, DBUS_SERVICE_DBUS, connection_name, "u");
+  dbus_message_unref (m);
+}
+
+static void
+test_activation (Fixture *f,
+    gconstpointer context)
+{
+  DBusMessage *m;
+
+  if (f->address == NULL)
+    return;
+
+  become_monitor (f);
+
+  /* The sender sends a message to an activatable service. */
+  m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal1");
+  if (!dbus_message_set_destination (m, "com.example.SystemdActivatable1"))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  /* We observe the activation request, and the message that caused it,
+   * before systemd has even joined the bus. */
+  while (g_queue_get_length (&f->monitored) < 2)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
+      "org.freedesktop.systemd1.Activator", "ActivationRequest", "s",
+      "org.freedesktop.systemd1");
+  dbus_message_unref (m);
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo",
+      "com.example.bar", "UnicastSignal1", "",
+      "com.example.SystemdActivatable1");
+  dbus_message_unref (m);
+
+  /* The fake systemd connects to the bus. */
+  f->systemd = test_connect_to_bus (f->ctx, f->address);
+  if (!dbus_connection_add_filter (f->systemd, systemd_filter, f, NULL))
+    g_error ("OOM");
+  f->systemd_name = dbus_bus_get_unique_name (f->systemd);
+
+  expect_new_connection (f);
+  take_well_known_name (f, f->systemd, "org.freedesktop.systemd1");
+  expect_take_well_known_name (f, f->systemd, "org.freedesktop.systemd1");
+
+  /* It gets its activation request. */
+  while (f->systemd_message == NULL)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = f->systemd_message;
+  f->systemd_message = NULL;
+  assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
+      "org.freedesktop.systemd1.Activator", "ActivationRequest", "s",
+      "org.freedesktop.systemd1");
+  dbus_message_unref (m);
+
+  /* systemd starts the activatable service. */
+  f->activated = test_connect_to_bus (f->ctx, f->address);
+  if (!dbus_connection_add_filter (f->activated, activated_filter,
+        f, NULL))
+    g_error ("OOM");
+  f->activated_name = dbus_bus_get_unique_name (f->activated);
+
+  expect_new_connection (f);
+  take_well_known_name (f, f->activated, "com.example.SystemdActivatable1");
+  expect_take_well_known_name (f, f->activated,
+      "com.example.SystemdActivatable1");
+
+  /* The message is delivered to the activatable service. */
+  while (f->activated_message == NULL)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = f->activated_message;
+  f->activated_message = NULL;
+  assert_signal (m, f->sender_name, "/foo",
+      "com.example.bar", "UnicastSignal1", "",
+      "com.example.SystemdActivatable1");
+  dbus_message_unref (m);
+
+  /* The sender sends a message to a different activatable service. */
+  m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal2");
+  if (!dbus_message_set_destination (m, "com.example.SystemdActivatable2"))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  /* This time systemd is already ready for it. */
+  while (g_queue_get_length (&f->monitored) < 2 ||
+      f->systemd_message == NULL)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = f->systemd_message;
+  f->systemd_message = NULL;
+  assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
+      "org.freedesktop.systemd1.Activator", "ActivationRequest", "s",
+      "org.freedesktop.systemd1");
+  dbus_message_unref (m);
+
+  /* The monitor sees the activation request and the signal that
+   * prompted it.*/
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
+      "org.freedesktop.systemd1.Activator", "ActivationRequest", "s",
+      "org.freedesktop.systemd1");
+  dbus_message_unref (m);
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo",
+      "com.example.bar", "UnicastSignal2", "",
+      "com.example.SystemdActivatable2");
+  dbus_message_unref (m);
+
+  /* The activatable service takes its name. Here I'm faking it by using
+   * an existing connection. */
+  take_well_known_name (f, f->activated, "com.example.SystemdActivatable2");
+
+  /* The message is delivered to the activatable service.
+   * Implementation detail: the monitor sees this happen before it even
+   * sees that the name request happened, which is pretty odd. */
+  while (f->activated_message == NULL)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = f->activated_message;
+  f->activated_message = NULL;
+  assert_signal (m, f->sender_name, "/foo",
+      "com.example.bar", "UnicastSignal2", "",
+      "com.example.SystemdActivatable2");
+  dbus_message_unref (m);
+
+  expect_take_well_known_name (f, f->activated,
+      "com.example.SystemdActivatable2");
+
+  /* A third activation. */
+  m = dbus_message_new_signal ("/foo", "com.example.bar", "UnicastSignal3");
+  if (!dbus_message_set_destination (m, "com.example.SystemdActivatable3"))
+    g_error ("OOM");
+  dbus_connection_send (f->sender, m, NULL);
+  dbus_message_unref (m);
+
+  /* Once again, we see the activation request and the reason. */
+  while (g_queue_get_length (&f->monitored) < 2)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
+      "org.freedesktop.systemd1.Activator", "ActivationRequest", "s",
+      "org.freedesktop.systemd1");
+  dbus_message_unref (m);
+  m = g_queue_pop_head (&f->monitored);
+  assert_signal (m, f->sender_name, "/foo",
+      "com.example.bar", "UnicastSignal3", "",
+      "com.example.SystemdActivatable3");
+  dbus_message_unref (m);
+
+  /* systemd gets the request too. */
+  while (f->systemd_message == NULL)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = f->systemd_message;
+  f->systemd_message = NULL;
+  assert_signal (m, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS,
+      "org.freedesktop.systemd1.Activator", "ActivationRequest", "s",
+      "org.freedesktop.systemd1");
+  dbus_message_unref (m);
+
+  /* This time activation fails */
+  m = dbus_message_new_signal ("/org/freedesktop/systemd1",
+      "org.freedesktop.systemd1.Activator", "ActivationFailure");
+
+  do
+    {
+      const char *unit = "dbus-com.example.SystemdActivatable3.service";
+      const char *error_name = "com.example.Nope";
+      const char *error_message = "Computer says no";
+
+      if (!dbus_message_append_args (m,
+            DBUS_TYPE_STRING, &unit,
+            DBUS_TYPE_STRING, &error_name,
+            DBUS_TYPE_STRING, &error_message,
+            DBUS_TYPE_INVALID))
+        g_error ("OOM");
+    }
+  while (0);
+
+  if (!dbus_message_set_destination (m, "org.freedesktop.DBus"))
+    g_error ("OOM");
+  dbus_connection_send (f->systemd, m, NULL);
+  dbus_message_unref (m);
+
+  /* The monitor sees activation fail */
+
+  /* Once again, we see the activation request and the reason. */
+  while (g_queue_get_length (&f->monitored) < 1)
+    test_main_context_iterate (f->ctx, TRUE);
+
+  m = g_queue_pop_head (&f->monitored);
+  assert_error_reply (m, DBUS_SERVICE_DBUS, f->sender_name,
+      "com.example.Nope");
+  dbus_message_unref (m);
+}
+
+static void
+teardown (Fixture *f,
+    gconstpointer context G_GNUC_UNUSED)
+{
+  dbus_error_free (&f->e);
+  g_clear_error (&f->ge);
+
+  if (f->monitor != NULL)
+    {
+      dbus_connection_remove_filter (f->monitor, monitor_filter, f);
+      dbus_connection_close (f->monitor);
+      dbus_connection_unref (f->monitor);
+      f->monitor = NULL;
+    }
+
+  if (f->sender != NULL)
+    {
+      dbus_connection_close (f->sender);
+      dbus_connection_unref (f->sender);
+      f->sender = NULL;
+    }
+
+  if (f->recipient != NULL)
+    {
+      dbus_connection_remove_filter (f->recipient, recipient_filter, f);
+      dbus_connection_close (f->recipient);
+      dbus_connection_unref (f->recipient);
+      f->recipient = NULL;
+    }
+
+  if (f->systemd != NULL)
+    {
+      dbus_connection_remove_filter (f->systemd, systemd_filter, f);
+      dbus_connection_close (f->systemd);
+      dbus_connection_unref (f->systemd);
+      f->systemd = NULL;
+    }
+
+  if (f->activated != NULL)
+    {
+      dbus_connection_remove_filter (f->activated, activated_filter, f);
+      dbus_connection_close (f->activated);
+      dbus_connection_unref (f->activated);
+      f->activated = NULL;
+    }
+
+  test_kill_pid (f->daemon_pid);
+  g_spawn_close_pid (f->daemon_pid);
+
+  test_main_context_unref (f->ctx);
+
+  g_queue_foreach (&f->monitored, (GFunc) dbus_message_unref, NULL);
+  g_queue_clear (&f->monitored);
+
+  g_free (f->address);
+}
+
+int
+main (int argc,
+    char **argv)
+{
+  g_test_init (&argc, &argv, NULL);
+  g_test_bug_base ("https://bugs.freedesktop.org/show_bug.cgi?id=");
+
+  g_test_add ("/monitor/become", Fixture, &side_effects_config,
+      setup, test_become_monitor, teardown);
+  g_test_add ("/monitor/broadcast", Fixture, NULL,
+      setup, test_broadcast, teardown);
+  g_test_add ("/monitor/forbidden-broadcast", Fixture, &forbidding_config,
+      setup, test_forbidden_broadcast, teardown);
+  g_test_add ("/monitor/unicast-signal", Fixture, NULL,
+      setup, test_unicast_signal, teardown);
+  g_test_add ("/monitor/forbidden", Fixture, &forbidding_config,
+      setup, test_forbidden, teardown);
+  g_test_add ("/monitor/method-call", Fixture, NULL,
+      setup, test_method_call, teardown);
+  g_test_add ("/monitor/forbidden-method", Fixture, &forbidding_config,
+      setup, test_forbidden_method_call, teardown);
+  g_test_add ("/monitor/dbus-daemon", Fixture, NULL,
+      setup, test_dbus_daemon, teardown);
+  g_test_add ("/monitor/selective", Fixture, &selective_config,
+      setup, test_selective, teardown);
+  g_test_add ("/monitor/wildcard", Fixture, &wildcard_config,
+      setup, test_unicast_signal, teardown);
+  g_test_add ("/monitor/no-rule", Fixture, &no_rules_config,
+      setup, test_unicast_signal, teardown);
+  g_test_add ("/monitor/eavesdrop", Fixture, &eavesdrop_config,
+      setup, test_unicast_signal, teardown);
+  g_test_add ("/monitor/no-eavesdrop", Fixture, &no_eavesdrop_config,
+      setup, test_unicast_signal, teardown);
+  g_test_add ("/monitor/activation", Fixture, &fake_systemd_config,
+      setup, test_activation, teardown);
+
+  return g_test_run ();
+}