/* * Copyright 2024 Collabora Ltd. * SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include "gdbus-tests.h" #define DBUS_SERVICE_DBUS "org.freedesktop.DBus" #define DBUS_PATH_DBUS "/org/freedesktop/DBus" #define DBUS_INTERFACE_DBUS DBUS_SERVICE_DBUS /* A signal that each connection emits to indicate that it has finished * emitting other signals */ #define FINISHED_PATH "/org/gtk/Test/Finished" #define FINISHED_INTERFACE "org.gtk.Test.Finished" #define FINISHED_SIGNAL "Finished" /* A signal emitted during testing */ #define EXAMPLE_PATH "/org/gtk/GDBus/ExampleInterface" #define EXAMPLE_INTERFACE "org.gtk.GDBus.ExampleInterface" #define FOO_SIGNAL "Foo" /* Log @s in a debug message. */ static inline const char * nonnull (const char *s, const char *if_null) { return (s == NULL) ? if_null : s; } typedef enum { TEST_CONN_NONE, TEST_CONN_FIRST, /* A connection that subscribes to signals */ TEST_CONN_SUBSCRIBER = TEST_CONN_FIRST, /* A mockup of a legitimate service */ TEST_CONN_SERVICE, /* A mockup of a second legitimate service */ TEST_CONN_SERVICE2, /* A connection that tries to trick @subscriber into processing its signals * as if they came from @service */ TEST_CONN_ATTACKER, NUM_TEST_CONNS } TestConn; static const char * const test_conn_descriptions[NUM_TEST_CONNS] = { "(unused)", "subscriber", "service", "service 2", "attacker" }; typedef enum { SUBSCRIPTION_MODE_CONN, SUBSCRIPTION_MODE_PROXY, SUBSCRIPTION_MODE_PARALLEL } SubscriptionMode; typedef struct { GDBusProxy *received_by_proxy; TestConn sender; char *path; char *iface; char *member; GVariant *parameters; char *arg0; guint32 step; } ReceivedMessage; static void received_message_free (ReceivedMessage *self) { g_clear_object (&self->received_by_proxy); g_free (self->path); g_free (self->iface); g_free (self->member); g_clear_pointer (&self->parameters, g_variant_unref); g_free (self->arg0); g_free (self); } typedef struct { TestConn sender; TestConn unicast_to; const char *path; const char *iface; const char *member; const char *arg0; guint received_by_conn; guint received_by_proxy; } TestEmitSignal; typedef struct { TestConn sender; const char *path; const char *iface; const char *member; const char *arg0; GDBusSignalFlags flags; } TestSubscribe; typedef enum { TEST_ACTION_NONE = 0, TEST_ACTION_SUBSCRIBE, TEST_ACTION_EMIT_SIGNAL, } TestAction; typedef struct { TestAction action; union { TestEmitSignal signal; TestSubscribe subscribe; } u; } TestStep; /* Arbitrary, extend as necessary to accommodate the longest test */ #define MAX_TEST_STEPS 10 typedef struct { const char *description; TestStep steps[MAX_TEST_STEPS]; } TestPlan; static const TestPlan plan_simple = { .description = "A broadcast is only received after subscribing to it", .steps = { { /* We don't receive a signal if we haven't subscribed yet */ .action = TEST_ACTION_EMIT_SIGNAL, .u.signal = { .sender = TEST_CONN_SERVICE, .path = EXAMPLE_PATH, .iface = EXAMPLE_INTERFACE, .member = FOO_SIGNAL, .received_by_conn = 0, .received_by_proxy = 0 }, }, { .action = TEST_ACTION_SUBSCRIBE, .u.subscribe = { .path = EXAMPLE_PATH, .iface = EXAMPLE_INTERFACE, }, }, { /* Now it works */ .action = TEST_ACTION_EMIT_SIGNAL, .u.signal = { .sender = TEST_CONN_SERVICE, .path = EXAMPLE_PATH, .iface = EXAMPLE_INTERFACE, .member = FOO_SIGNAL, .received_by_conn = 1, /* The proxy can't be used in this case, because it needs * a bus name to subscribe to */ .received_by_proxy = 0 }, }, }, }; static const TestPlan plan_broadcast_from_anyone = { .description = "A subscription with NULL sender accepts broadcast and unicast", .steps = { { /* Subscriber wants to receive signals from anyone */ .action = TEST_ACTION_SUBSCRIBE, .u.subscribe = { .path = EXAMPLE_PATH, .iface = EXAMPLE_INTERFACE, }, }, { /* First service sends a broadcast */ .action = TEST_ACTION_EMIT_SIGNAL, .u.signal = { .sender = TEST_CONN_SERVICE, .path = EXAMPLE_PATH, .iface = EXAMPLE_INTERFACE, .member = FOO_SIGNAL, .received_by_conn = 1, .received_by_proxy = 0 }, }, { /* Second service also sends a broadcast */ .action = TEST_ACTION_EMIT_SIGNAL, .u.signal = { .sender = TEST_CONN_SERVICE2, .path = EXAMPLE_PATH, .iface = EXAMPLE_INTERFACE, .member = FOO_SIGNAL, .received_by_conn = 1, .received_by_proxy = 0 }, }, { /* First service sends a unicast signal */ .action = TEST_ACTION_EMIT_SIGNAL, .u.signal = { .sender = TEST_CONN_SERVICE, .unicast_to = TEST_CONN_SUBSCRIBER, .path = EXAMPLE_PATH, .iface = EXAMPLE_INTERFACE, .member = FOO_SIGNAL, .received_by_conn = 1, .received_by_proxy = 0 }, }, { /* Second service also sends a unicast signal */ .action = TEST_ACTION_EMIT_SIGNAL, .u.signal = { .sender = TEST_CONN_SERVICE2, .unicast_to = TEST_CONN_SUBSCRIBER, .path = EXAMPLE_PATH, .iface = EXAMPLE_INTERFACE, .member = FOO_SIGNAL, .received_by_conn = 1, .received_by_proxy = 0 }, }, }, }; static const TestPlan plan_match_twice = { .description = "A message matching more than one subscription is received " "once per subscription", .steps = { { .action = TEST_ACTION_SUBSCRIBE, .u.subscribe = { .sender = TEST_CONN_SERVICE, .path = EXAMPLE_PATH, .iface = EXAMPLE_INTERFACE, }, }, { .action = TEST_ACTION_SUBSCRIBE, .u.subscribe = { .path = EXAMPLE_PATH, }, }, { .action = TEST_ACTION_SUBSCRIBE, .u.subscribe = { .iface = EXAMPLE_INTERFACE, }, }, { .action = TEST_ACTION_SUBSCRIBE, .u.subscribe = { .sender = TEST_CONN_SERVICE, .path = EXAMPLE_PATH, .iface = EXAMPLE_INTERFACE, }, }, { .action = TEST_ACTION_EMIT_SIGNAL, .u.signal = { .sender = TEST_CONN_SERVICE, .path = EXAMPLE_PATH, .iface = EXAMPLE_INTERFACE, .member = FOO_SIGNAL, .received_by_conn = 4, /* Only the first and last work with GDBusProxy */ .received_by_proxy = 2 }, }, }, }; static const TestPlan plan_limit_by_unique_name = { .description = "A subscription via a unique name only accepts messages " "sent by that same unique name", .steps = { { /* Subscriber wants to receive signals from service */ .action = TEST_ACTION_SUBSCRIBE, .u.subscribe = { .sender = TEST_CONN_SERVICE, .path = EXAMPLE_PATH, .iface = EXAMPLE_INTERFACE, }, }, { /* Attacker wants to trick subscriber into thinking that service * sent a signal */ .action = TEST_ACTION_EMIT_SIGNAL, .u.signal = { .sender = TEST_CONN_ATTACKER, .path = EXAMPLE_PATH, .iface = EXAMPLE_INTERFACE, .member = FOO_SIGNAL, .received_by_conn = 0, .received_by_proxy = 0 }, }, { /* Attacker tries harder, by sending a signal unicast directly to * the subscriber */ .action = TEST_ACTION_EMIT_SIGNAL, .u.signal = { .sender = TEST_CONN_ATTACKER, .unicast_to = TEST_CONN_SUBSCRIBER, .path = EXAMPLE_PATH, .iface = EXAMPLE_INTERFACE, .member = FOO_SIGNAL, .received_by_conn = 0, .received_by_proxy = 0 }, }, { /* When the real service sends a signal, it should still get through */ .action = TEST_ACTION_EMIT_SIGNAL, .u.signal = { .sender = TEST_CONN_SERVICE, .path = EXAMPLE_PATH, .iface = EXAMPLE_INTERFACE, .member = FOO_SIGNAL, .received_by_conn = 1, .received_by_proxy = 1 }, }, }, }; typedef struct { const TestPlan *plan; SubscriptionMode mode; GError *error; /* (element-type ReceivedMessage) */ GPtrArray *received; /* conns[TEST_CONN_NONE] is unused and remains NULL */ GDBusConnection *conns[NUM_TEST_CONNS]; /* Proxies on conns[TEST_CONN_SUBSCRIBER] */ GPtrArray *proxies; /* unique_names[TEST_CONN_NONE] is unused and remains NULL */ const char *unique_names[NUM_TEST_CONNS]; /* finished[TEST_CONN_NONE] is unused and remains FALSE */ gboolean finished[NUM_TEST_CONNS]; /* Remains 0 for any step that is not a subscription */ guint subscriptions[MAX_TEST_STEPS]; /* Number of times the signal from step n was received */ guint received_by_conn[MAX_TEST_STEPS]; /* Number of times the signal from step n was received */ guint received_by_proxy[MAX_TEST_STEPS]; guint finished_subscription; } Fixture; /* Wait for asynchronous messages from @conn to have been processed * by the message bus, as a sequence point so that we can make * "happens before" and "happens after" assertions relative to this. * The easiest way to achieve this is to call a message bus method that has * no arguments and wait for it to return: because the message bus processes * messages in-order, anything we sent before this must have been processed * by the time this call arrives. */ static void connection_wait_for_bus (GDBusConnection *conn) { GError *error = NULL; GVariant *call_result; call_result = g_dbus_connection_call_sync (conn, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetId", NULL, /* arguments */ NULL, /* result type */ G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); g_assert_no_error (error); g_assert_nonnull (call_result); g_variant_unref (call_result); } /* * Called when the subscriber receives a message from any connection * announcing that it has emitted all the signals that it plans to emit. */ static void subscriber_finished_cb (GDBusConnection *conn, const char *sender_name, const char *path, const char *iface, const char *member, GVariant *parameters, void *user_data) { Fixture *f = user_data; GDBusConnection *subscriber = f->conns[TEST_CONN_SUBSCRIBER]; guint i; g_assert_true (conn == subscriber); for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++) { if (g_str_equal (sender_name, f->unique_names[i])) { g_assert_false (f->finished[i]); f->finished[i] = TRUE; g_test_message ("Received Finished signal from %s %s", test_conn_descriptions[i], sender_name); return; } } g_error ("Received Finished signal from unknown sender %s", sender_name); } /* * Called when we receive a signal, either via the GDBusProxy (proxy != NULL) * or via the GDBusConnection (proxy == NULL). */ static void fixture_received_signal (Fixture *f, GDBusProxy *proxy, const char *sender_name, const char *path, const char *iface, const char *member, GVariant *parameters) { guint i; ReceivedMessage *received; /* Ignore the Finished signal if it matches a wildcard subscription */ if (g_str_equal (member, FINISHED_SIGNAL)) return; received = g_new0 (ReceivedMessage, 1); if (proxy != NULL) received->received_by_proxy = g_object_ref (proxy); else received->received_by_proxy = NULL; received->path = g_strdup (path); received->iface = g_strdup (iface); received->member = g_strdup (member); received->parameters = g_variant_ref (parameters); for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++) { if (g_str_equal (sender_name, f->unique_names[i])) { received->sender = i; g_assert_false (f->finished[i]); break; } } g_assert_cmpint (received->sender, !=, TEST_CONN_NONE); g_test_message ("Signal received from %s %s via %s", test_conn_descriptions[received->sender], sender_name, proxy != NULL ? "proxy" : "connection"); g_test_message ("\tPath: %s", path); g_test_message ("\tInterface: %s", iface); g_test_message ("\tMember: %s", member); if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(su)"))) { g_variant_get (parameters, "(su)", &received->arg0, &received->step); g_test_message ("\tString argument 0: %s", received->arg0); g_test_message ("\tSent in step: %u", received->step); } else { g_assert_cmpstr (g_variant_get_type_string (parameters), ==, "(uu)"); g_variant_get (parameters, "(uu)", NULL, &received->step); g_test_message ("\tArgument 0: (not a string)"); g_test_message ("\tSent in step: %u", received->step); } g_ptr_array_add (f->received, g_steal_pointer (&received)); } static void proxy_signal_cb (GDBusProxy *proxy, const char *sender_name, const char *member, GVariant *parameters, void *user_data) { Fixture *f = user_data; fixture_received_signal (f, proxy, sender_name, g_dbus_proxy_get_object_path (proxy), g_dbus_proxy_get_interface_name (proxy), member, parameters); } static void subscribed_signal_cb (GDBusConnection *conn, const char *sender_name, const char *path, const char *iface, const char *member, GVariant *parameters, void *user_data) { Fixture *f = user_data; GDBusConnection *subscriber = f->conns[TEST_CONN_SUBSCRIBER]; g_assert_true (conn == subscriber); fixture_received_signal (f, NULL, sender_name, path, iface, member, parameters); } static void fixture_subscribe (Fixture *f, const TestSubscribe *subscribe, guint step_number) { GDBusConnection *subscriber = f->conns[TEST_CONN_SUBSCRIBER]; const char *sender; if (subscribe->sender != TEST_CONN_NONE) { sender = f->unique_names[subscribe->sender]; g_test_message ("\tSender: %s %s", test_conn_descriptions[subscribe->sender], sender); } else { sender = NULL; g_test_message ("\tSender: (any)"); } g_test_message ("\tPath: %s", nonnull (subscribe->path, "(any)")); g_test_message ("\tInterface: %s", nonnull (subscribe->iface, "(any)")); g_test_message ("\tMember: %s", nonnull (subscribe->member, "(any)")); g_test_message ("\tString argument 0: %s", nonnull (subscribe->arg0, "(any)")); g_test_message ("\tFlags: %x", subscribe->flags); if (f->mode != SUBSCRIPTION_MODE_PROXY) { /* CONN or PARALLEL */ guint id; g_test_message ("\tSubscribing via connection"); id = g_dbus_connection_signal_subscribe (subscriber, sender, subscribe->iface, subscribe->member, subscribe->path, subscribe->arg0, subscribe->flags, subscribed_signal_cb, f, NULL); g_assert_cmpuint (id, !=, 0); f->subscriptions[step_number] = id; } if (f->mode != SUBSCRIPTION_MODE_CONN) { /* PROXY or PARALLEL */ if (sender == NULL) { g_test_message ("\tCannot subscribe via proxy: no bus name"); } else if (subscribe->path == NULL) { g_test_message ("\tCannot subscribe via proxy: no path"); } else if (subscribe->iface == NULL) { g_test_message ("\tCannot subscribe via proxy: no interface"); } else { GDBusProxy *proxy; g_test_message ("\tSubscribing via proxy"); proxy = g_dbus_proxy_new_sync (subscriber, (G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START), NULL, /* GDBusInterfaceInfo */ sender, subscribe->path, subscribe->iface, NULL, /* GCancellable */ &f->error); g_assert_no_error (f->error); g_assert_nonnull (proxy); g_signal_connect (proxy, "g-signal", G_CALLBACK (proxy_signal_cb), f); g_ptr_array_add (f->proxies, g_steal_pointer (&proxy)); } } /* As in setup(), we need to wait for AddMatch to happen. */ g_test_message ("Waiting for AddMatch to be processed"); connection_wait_for_bus (subscriber); } static void fixture_emit_signal (Fixture *f, const TestEmitSignal *signal, guint step_number) { GVariant *body; const char *destination; gboolean ok; g_test_message ("\tSender: %s", test_conn_descriptions[signal->sender]); if (signal->unicast_to != TEST_CONN_NONE) { destination = f->unique_names[signal->unicast_to]; g_test_message ("\tDestination: %s %s", test_conn_descriptions[signal->unicast_to], destination); } else { destination = NULL; g_test_message ("\tDestination: (broadcast)"); } g_assert_nonnull (signal->path); g_test_message ("\tPath: %s", signal->path); g_assert_nonnull (signal->iface); g_test_message ("\tInterface: %s", signal->iface); g_assert_nonnull (signal->member); g_test_message ("\tMember: %s", signal->member); /* If arg0 is non-NULL, put it in the message's argument 0. * Otherwise put something that will not match any arg0. * Either way, put the sequence number in argument 1 so we can * correlate sent messages with received messages later. */ if (signal->arg0 != NULL) { g_test_message ("\tString argument 0: %s", signal->arg0); /* floating */ body = g_variant_new ("(su)", signal->arg0, (guint32) step_number); } else { g_test_message ("\tArgument 0: (not a string)"); body = g_variant_new ("(uu)", (guint32) 0, (guint32) step_number); } ok = g_dbus_connection_emit_signal (f->conns[signal->sender], destination, signal->path, signal->iface, signal->member, /* steals floating reference */ g_steal_pointer (&body), &f->error); g_assert_no_error (f->error); g_assert_true (ok); /* Emitting the signal is asynchronous, so if we want subsequent steps * to be guaranteed to happen after the signal from the message bus's * perspective, we have to do a round-trip to the message bus to sync up. */ g_test_message ("Waiting for signal to reach message bus"); connection_wait_for_bus (f->conns[signal->sender]); } static void fixture_run_plan (Fixture *f, const TestPlan *plan, SubscriptionMode mode) { guint i; G_STATIC_ASSERT (G_N_ELEMENTS (plan->steps) == G_N_ELEMENTS (f->subscriptions)); G_STATIC_ASSERT (G_N_ELEMENTS (plan->steps) == G_N_ELEMENTS (f->received_by_conn)); G_STATIC_ASSERT (G_N_ELEMENTS (plan->steps) == G_N_ELEMENTS (f->received_by_proxy)); f->mode = mode; f->plan = plan; g_test_summary (plan->description); for (i = 0; i < G_N_ELEMENTS (plan->steps); i++) { const TestStep *step = &plan->steps[i]; switch (step->action) { case TEST_ACTION_SUBSCRIBE: g_test_message ("Step %u: adding subscription", i); fixture_subscribe (f, &step->u.subscribe, i); break; case TEST_ACTION_EMIT_SIGNAL: g_test_message ("Step %u: emitting signal", i); fixture_emit_signal (f, &step->u.signal, i); break; case TEST_ACTION_NONE: /* Padding to fill the rest of the array, do nothing */ break; default: g_return_if_reached (); } } /* Now that we have done everything we wanted to do, emit Finished * from each connection. */ for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++) { gboolean ok; ok = g_dbus_connection_emit_signal (f->conns[i], NULL, FINISHED_PATH, FINISHED_INTERFACE, FINISHED_SIGNAL, NULL, &f->error); g_assert_no_error (f->error); g_assert_true (ok); } /* Wait until we have seen the Finished signal from each sender */ while (TRUE) { gboolean all_finished = TRUE; for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++) all_finished = all_finished && f->finished[i]; if (all_finished) break; g_main_context_iteration (NULL, TRUE); } /* Assert that the correct things happened before each Finished signal */ for (i = 0; i < f->received->len; i++) { const ReceivedMessage *received = g_ptr_array_index (f->received, i); g_assert_cmpuint (received->step, <, G_N_ELEMENTS (f->received_by_conn)); g_assert_cmpuint (received->step, <, G_N_ELEMENTS (f->received_by_proxy)); g_assert_cmpint (plan->steps[received->step].action, ==, TEST_ACTION_EMIT_SIGNAL); if (received->received_by_proxy != NULL) f->received_by_proxy[received->step] += 1; else f->received_by_conn[received->step] += 1; } for (i = 0; i < G_N_ELEMENTS (plan->steps); i++) { const TestStep *step = &plan->steps[i]; if (step->action == TEST_ACTION_EMIT_SIGNAL) { const TestEmitSignal *signal = &plan->steps[i].u.signal; if (mode != SUBSCRIPTION_MODE_PROXY) { g_test_message ("Signal from step %u was received %u times by " "GDBusConnection, expected %u", i, f->received_by_conn[i], signal->received_by_conn); g_assert_cmpuint (f->received_by_conn[i], ==, signal->received_by_conn); } else { g_assert_cmpuint (f->received_by_conn[i], ==, 0); } if (mode != SUBSCRIPTION_MODE_CONN) { g_test_message ("Signal from step %u was received %u times by " "GDBusProxy, expected %u", i, f->received_by_proxy[i], signal->received_by_proxy); g_assert_cmpuint (f->received_by_proxy[i], ==, signal->received_by_proxy); } else { g_assert_cmpuint (f->received_by_proxy[i], ==, 0); } } } } static void setup (Fixture *f, G_GNUC_UNUSED const void *context) { GDBusConnection *subscriber; guint i; session_bus_up (); f->proxies = g_ptr_array_new_full (MAX_TEST_STEPS, g_object_unref); f->received = g_ptr_array_new_full (MAX_TEST_STEPS, (GDestroyNotify) received_message_free); for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++) { f->conns[i] = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, &f->error); g_assert_no_error (f->error); g_assert_nonnull (f->conns[i]); f->unique_names[i] = g_dbus_connection_get_unique_name (f->conns[i]); g_assert_nonnull (f->unique_names[i]); g_test_message ("%s is %s", test_conn_descriptions[i], f->unique_names[i]); } subscriber = f->conns[TEST_CONN_SUBSCRIBER]; /* Used to wait for all connections to finish sending whatever they * wanted to send */ f->finished_subscription = g_dbus_connection_signal_subscribe (subscriber, NULL, FINISHED_INTERFACE, FINISHED_SIGNAL, FINISHED_PATH, NULL, G_DBUS_SIGNAL_FLAGS_NONE, subscriber_finished_cb, f, NULL); /* AddMatch is sent asynchronously, so we don't know how * soon it will be processed. Before emitting signals, we * need to wait for the message bus to get as far as processing * AddMatch. */ g_test_message ("Waiting for AddMatch to be processed"); connection_wait_for_bus (subscriber); } static void test_conn_subscribe (Fixture *f, const void *context) { fixture_run_plan (f, context, SUBSCRIPTION_MODE_CONN); } static void test_proxy_subscribe (Fixture *f, const void *context) { fixture_run_plan (f, context, SUBSCRIPTION_MODE_PROXY); } static void test_parallel_subscribe (Fixture *f, const void *context) { fixture_run_plan (f, context, SUBSCRIPTION_MODE_PARALLEL); } static void teardown (Fixture *f, G_GNUC_UNUSED const void *context) { GDBusConnection *subscriber = f->conns[TEST_CONN_SUBSCRIBER]; guint i; g_ptr_array_unref (f->proxies); if (f->finished_subscription != 0) g_dbus_connection_signal_unsubscribe (subscriber, f->finished_subscription); for (i = 0; i < G_N_ELEMENTS (f->subscriptions); i++) { if (f->subscriptions[i] != 0) g_dbus_connection_signal_unsubscribe (subscriber, f->subscriptions[i]); } g_ptr_array_unref (f->received); for (i = TEST_CONN_FIRST; i < G_N_ELEMENTS (f->conns); i++) g_clear_object (&f->conns[i]); g_clear_error (&f->error); session_bus_down (); } int main (int argc, char *argv[]) { g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); g_test_dbus_unset (); #define ADD_SUBSCRIBE_TEST(name) \ do { \ g_test_add ("/gdbus/subscribe/conn/" #name, \ Fixture, &plan_ ## name, \ setup, test_conn_subscribe, teardown); \ g_test_add ("/gdbus/subscribe/proxy/" #name, \ Fixture, &plan_ ## name, \ setup, test_proxy_subscribe, teardown); \ g_test_add ("/gdbus/subscribe/parallel/" #name, \ Fixture, &plan_ ## name, \ setup, test_parallel_subscribe, teardown); \ } while (0) ADD_SUBSCRIBE_TEST (simple); ADD_SUBSCRIBE_TEST (broadcast_from_anyone); ADD_SUBSCRIBE_TEST (match_twice); ADD_SUBSCRIBE_TEST (limit_by_unique_name); return g_test_run(); }