Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright © 2018 Endless Mobile, Inc.
4 : : *
5 : : * This library is free software; you can redistribute it and/or
6 : : * modify it under the terms of the GNU Lesser General Public
7 : : * License as published by the Free Software Foundation; either
8 : : * version 2.1 of the License, or (at your option) any later version.
9 : : *
10 : : * This library is distributed in the hope that it will be useful,
11 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : : * Lesser General Public License for more details.
14 : : *
15 : : * You should have received a copy of the GNU Lesser General Public
16 : : * License along with this library; if not, write to the Free Software
17 : : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 : : *
19 : : * Authors:
20 : : * - Philip Withnall <withnall@endlessm.com>
21 : : */
22 : :
23 : : #include "config.h"
24 : :
25 : : #include <gio/gio.h>
26 : : #include <glib.h>
27 : : #include <glib-object.h>
28 : : #include <libglib-testing/dbus-queue.h>
29 : :
30 : :
31 : : /**
32 : : * SECTION:dbus-queue
33 : : * @short_description: D-Bus service mock implementation
34 : : * @stability: Unstable
35 : : * @include: libglib-testing/dbus-queue.h
36 : : *
37 : : * #GtDBusQueue is an object which allows a D-Bus service to be mocked and
38 : : * implemented to return a variety of results from method calls with a high
39 : : * degree of flexibility. The mock service is driven from within the same
40 : : * process (and potentially the same #GMainContext) as the code under test.
41 : : *
42 : : * This allows a D-Bus service to be mocked without needing to generate and
43 : : * implement a full implementation of its interfaces.
44 : : *
45 : : * A single #GtDBusQueue instance can be used to mock one or more D-Bus
46 : : * services, depending on whether it’s desirable to process the queues of method
47 : : * calls to those services in order or independently from each other. Each
48 : : * #GtDBusQueue has a queue of method calls received by the services it is
49 : : * mocking, which are ordered the same as they were received off the bus. It is
50 : : * intended that the test harness which is using #GtDBusQueue should pop
51 : : * messages off the queue, and either check they are as expected and return a
52 : : * static reply, or construct a reply dynamically based on their contents.
53 : : *
54 : : * Messages can be popped off the queue using
55 : : * gt_dbus_queue_assert_pop_message(), gt_dbus_queue_pop_message() or
56 : : * gt_dbus_queue_try_pop_message(). The former two block until a message can be
57 : : * popped, iterating the thread-default #GMainContext while they block. The
58 : : * latter returns %FALSE immediately if the queue is empty.
59 : : *
60 : : * Popping and handling messages is typically done in the #GtDBusQueue server
61 : : * thread using gt_dbus_queue_set_server_func(). This will work whether the code
62 : : * under test is synchronous or asynchronous. If the code under test is
63 : : * asynchronous, popping and handling messages can instead be done in the main
64 : : * test thread, but this has no particular advantages.
65 : : *
66 : : * By default, a #GtDBusQueue will not assert that its message queue is empty
67 : : * on destruction unless the `assert_queue_empty` argument is passed to
68 : : * gt_dbus_queue_disconnect(). If that argument is %FALSE, it is highly
69 : : * recommended that gt_dbus_queue_assert_no_messages() is called before a
70 : : * #GtDBusQueue is destroyed, or after a particular unit test is completed.
71 : : *
72 : : * Conversely, a #GtDBusQueue will not ensure that the thread default
73 : : * #GMainContext for the thread where it’s constructed is empty when the
74 : : * #GtDBusQueue is finalised. That is the responsibility of the caller who
75 : : * constructed the #GMainContext.
76 : : *
77 : : * ## Usage Example with GLib Testing Framework
78 : : *
79 : : * |[<!-- language="C" -->
80 : : * typedef struct
81 : : * {
82 : : * GtDBusQueue *queue; // (owned)
83 : : * uid_t valid_uid;
84 : : * } BusFixture;
85 : : *
86 : : * static void
87 : : * bus_set_up (BusFixture *fixture,
88 : : * gconstpointer test_data)
89 : : * {
90 : : * g_autoptr(GError) local_error = NULL;
91 : : * g_autofree gchar *object_path = NULL;
92 : : *
93 : : * fixture->valid_uid = 500; // arbitrarily chosen
94 : : * fixture->queue = gt_dbus_queue_new ();
95 : : *
96 : : * gt_dbus_queue_connect (fixture->queue, &local_error);
97 : : * g_assert_no_error (local_error);
98 : : *
99 : : * gt_dbus_queue_own_name (fixture->queue, "org.freedesktop.Accounts");
100 : : *
101 : : * object_path = g_strdup_printf ("/org/freedesktop/Accounts/User%u", fixture->valid_uid);
102 : : * gt_dbus_queue_export_object (fixture->queue,
103 : : * object_path,
104 : : * (GDBusInterfaceInfo *) &app_filter_interface_info,
105 : : * &local_error);
106 : : * g_assert_no_error (local_error);
107 : : *
108 : : * gt_dbus_queue_export_object (fixture->queue,
109 : : * "/org/freedesktop/Accounts",
110 : : * (GDBusInterfaceInfo *) &accounts_interface_info,
111 : : * &local_error);
112 : : * g_assert_no_error (local_error);
113 : : * }
114 : : *
115 : : * static void
116 : : * bus_tear_down (BusFixture *fixture,
117 : : * gconstpointer test_data)
118 : : * {
119 : : * gt_dbus_queue_disconnect (fixture->queue, TRUE);
120 : : * g_clear_pointer (&fixture->queue, gt_dbus_queue_free);
121 : : * }
122 : : *
123 : : * // Helper #GAsyncReadyCallback which returns the #GAsyncResult in its @user_data.
124 : : * static void
125 : : * async_result_cb (GObject *obj,
126 : : * GAsyncResult *result,
127 : : * gpointer user_data)
128 : : * {
129 : : * GAsyncResult **result_out = (GAsyncResult **) user_data;
130 : : *
131 : : * g_assert_null (*result_out);
132 : : * *result_out = g_object_ref (result);
133 : : * }
134 : : *
135 : : * // Test that getting an #EpcAppFilter from the mock D-Bus service works. The
136 : : * // @test_data is a boolean value indicating whether to do the call
137 : : * // synchronously (%FALSE) or asynchronously (%TRUE).
138 : : * //
139 : : * // The mock D-Bus replies are generated in get_app_filter_server_cb(), which is
140 : : * // used for both synchronous and asynchronous calls.
141 : : * static void get_app_filter_server_cb (GtDBusQueue *queue,
142 : : * gpointer user_data);
143 : : *
144 : : * static void
145 : : * test_app_filter_bus_get (BusFixture *fixture,
146 : : * gconstpointer test_data)
147 : : * {
148 : : * g_autoptr(EpcAppFilter) app_filter = NULL;
149 : : * g_autoptr(GError) local_error = NULL;
150 : : * gboolean test_async = GPOINTER_TO_UINT (test_data);
151 : : *
152 : : * gt_dbus_queue_set_server_func (fixture->queue, get_app_filter_server_cb, fixture);
153 : : *
154 : : * if (test_async)
155 : : * {
156 : : * g_autoptr(GAsyncResult) result = NULL;
157 : : *
158 : : * epc_get_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue),
159 : : * fixture->valid_uid,
160 : : * FALSE, NULL, async_result_cb, &result);
161 : : *
162 : : * while (result == NULL)
163 : : * g_main_context_iteration (NULL, TRUE);
164 : : * app_filter = epc_get_app_filter_finish (result, &local_error);
165 : : * }
166 : : * else
167 : : * {
168 : : * app_filter = epc_get_app_filter (gt_dbus_queue_get_client_connection (fixture->queue),
169 : : * fixture->valid_uid,
170 : : * FALSE, NULL, &local_error);
171 : : * }
172 : : *
173 : : * g_assert_no_error (local_error);
174 : : * g_assert_nonnull (app_filter);
175 : : *
176 : : * // Check the app filter properties.
177 : : * g_assert_cmpuint (epc_app_filter_get_user_id (app_filter), ==, fixture->valid_uid);
178 : : * g_assert_false (epc_app_filter_is_flatpak_app_allowed (app_filter, "org.gnome.Builder"));
179 : : * g_assert_true (epc_app_filter_is_flatpak_app_allowed (app_filter, "org.gnome.Chess"));
180 : : * }
181 : : *
182 : : * // This is run in a worker thread.
183 : : * static void
184 : : * get_app_filter_server_cb (GtDBusQueue *queue,
185 : : * gpointer user_data)
186 : : * {
187 : : * BusFixture *fixture = user_data;
188 : : * g_autoptr(GDBusMethodInvocation) invocation1 = NULL;
189 : : * g_autoptr(GDBusMethodInvocation) invocation2 = NULL;
190 : : * g_autofree gchar *object_path = NULL;
191 : : * g_autofree gchar *reply1 = NULL;
192 : : *
193 : : * // Handle the FindUserById() call.
194 : : * gint64 user_id;
195 : : * invocation1 =
196 : : * gt_dbus_queue_assert_pop_message (queue,
197 : : * "/org/freedesktop/Accounts",
198 : : * "org.freedesktop.Accounts",
199 : : * "FindUserById", "(x)", &user_id);
200 : : * g_assert_cmpint (user_id, ==, fixture->valid_uid);
201 : : *
202 : : * object_path = g_strdup_printf ("/org/freedesktop/Accounts/User%u", (uid_t) user_id);
203 : : * reply1 = g_strdup_printf ("(@o '%s',)", object_path);
204 : : * g_dbus_method_invocation_return_value (invocation1, g_variant_new_parsed (reply1));
205 : : *
206 : : * // Handle the Properties.GetAll() call and return some arbitrary, valid values
207 : : * // for the given user.
208 : : * const gchar *property_interface;
209 : : * invocation2 =
210 : : * gt_dbus_queue_assert_pop_message (queue,
211 : : * object_path,
212 : : * "org.freedesktop.DBus.Properties",
213 : : * "GetAll", "(&s)", &property_interface);
214 : : * g_assert_cmpstr (property_interface, ==, "com.endlessm.ParentalControls.AppFilter");
215 : : *
216 : : * const gchar *reply2 =
217 : : * "({"
218 : : * "'allow-user-installation': <true>,"
219 : : * "'allow-system-installation': <false>,"
220 : : * "'app-filter': <(false, ['app/org.gnome.Builder/x86_64/stable'])>,"
221 : : * "'oars-filter': <('oars-1.1', { 'violence-bloodshed': 'mild' })>"
222 : : * "},)";
223 : : * g_dbus_method_invocation_return_value (invocation2, g_variant_new_parsed (reply2));
224 : : * }
225 : : * ]|
226 : : *
227 : : * Since: 0.1.0
228 : : */
229 : :
230 : : /**
231 : : * GtDBusQueue:
232 : : *
233 : : * An object which allows a D-Bus service to be mocked and implemented to return
234 : : * a variety of results from method calls with a high degree of flexibility. The
235 : : * mock service is driven from within the same process (and potentially the same
236 : : * #GMainContext) as the code under test.
237 : : *
238 : : * This allows a D-Bus service to be mocked without needing to generate and
239 : : * implement a full implementation of its interfaces.
240 : : *
241 : : * Since: 0.1.0
242 : : */
243 : : struct _GtDBusQueue
244 : : {
245 : : GTestDBus *bus; /* (owned) */
246 : :
247 : : GThread *server_thread; /* (owned) */
248 : : guint server_filter_id;
249 : : GtDBusQueueServerFunc server_func; /* (nullable) (atomic) */
250 : : gpointer server_func_data; /* (unowned) (nullable) (atomic) */
251 : : gboolean quitting; /* (atomic) */
252 : :
253 : : GMainContext *server_context; /* (owned) */
254 : : GDBusConnection *server_connection; /* (owned) */
255 : :
256 : : GMutex lock; /* (owned) */
257 : : GArray *name_ids; /* (owned) (element-type guint) (locked-by lock) */
258 : : GArray *object_ids; /* (owned) (element-type guint) (locked-by lock) */
259 : :
260 : : GAsyncQueue *server_message_queue; /* (owned) (element-type GDBusMethodInvocation) */
261 : :
262 : : GMainContext *client_context; /* (owned) */
263 : : GDBusConnection *client_connection; /* (owned) */
264 : : };
265 : :
266 : : static gpointer gt_dbus_queue_server_thread_cb (gpointer user_data);
267 : : static void gt_dbus_queue_method_call (GDBusConnection *connection,
268 : : const gchar *sender,
269 : : const gchar *object_path,
270 : : const gchar *interface_name,
271 : : const gchar *method_name,
272 : : GVariant *parameters,
273 : : GDBusMethodInvocation *invocation,
274 : : gpointer user_data);
275 : :
276 : : static const GDBusInterfaceVTable gt_dbus_queue_vtable =
277 : : {
278 : : .method_call = gt_dbus_queue_method_call,
279 : : .get_property = NULL, /* handled manually */
280 : : .set_property = NULL, /* handled manually */
281 : : };
282 : :
283 : : /**
284 : : * gt_dbus_queue_new:
285 : : *
286 : : * Create a new #GtDBusQueue. Start it using gt_dbus_queue_connect(), own a name
287 : : * using gt_dbus_queue_own_name() and register objects using
288 : : * gt_dbus_queue_export_object(). Start a particular test run using
289 : : * gt_dbus_queue_set_server_func().
290 : : *
291 : : * Returns: (transfer full): a new #GtDBusQueue
292 : : * Since: 0.1.0
293 : : */
294 : : GtDBusQueue *
295 : 5 : gt_dbus_queue_new (void)
296 : : {
297 : 10 : g_autoptr(GtDBusQueue) queue = g_new0 (GtDBusQueue, 1);
298 : :
299 : 5 : queue->server_context = g_main_context_new ();
300 : 5 : queue->client_context = g_main_context_ref_thread_default ();
301 : :
302 : 5 : queue->bus = g_test_dbus_new (G_TEST_DBUS_NONE);
303 : 5 : queue->server_message_queue = g_async_queue_new_full ((GDestroyNotify) g_object_unref);
304 : 5 : queue->name_ids = g_array_new (FALSE, FALSE, sizeof (guint));
305 : 5 : queue->object_ids = g_array_new (FALSE, FALSE, sizeof (guint));
306 : 5 : g_mutex_init (&queue->lock);
307 : :
308 : 5 : return g_steal_pointer (&queue);
309 : : }
310 : :
311 : : /**
312 : : * gt_dbus_queue_free:
313 : : * @self: (transfer full): a #GtDBusQueue
314 : : *
315 : : * Free a #GtDBusQueue. This will call gt_dbus_queue_disconnect() if it hasn’t
316 : : * been called already, and will assert that there are no messages left in the
317 : : * server message queue.
318 : : *
319 : : * If you wish to free the #GtDBusQueue regardless of whether there are messages
320 : : * left in the server message queue, call gt_dbus_queue_disconnect() explicitly
321 : : * before this function, and pass %FALSE as its second argument.
322 : : *
323 : : * Since: 0.1.0
324 : : */
325 : : void
326 : 4 : gt_dbus_queue_free (GtDBusQueue *self)
327 : : {
328 [ - + ]: 4 : g_return_if_fail (self != NULL);
329 : :
330 : : /* Typically we’d expect the test harness to call this explicitly, but we can
331 : : * just as easily do it implicitly. Give them the strictest assertion
332 : : * behaviour though. */
333 [ - + ]: 4 : if (self->server_thread != NULL)
334 : 0 : gt_dbus_queue_disconnect (self, TRUE);
335 : :
336 : : /* We can access @object_ids and @name_ids unlocked, since the thread has been
337 : : * shut down. */
338 [ + - ]: 4 : if (self->object_ids != NULL)
339 [ - + ]: 4 : g_assert (self->object_ids->len == 0);
340 [ + - ]: 4 : g_clear_pointer (&self->object_ids, g_array_unref);
341 : :
342 [ + - ]: 4 : if (self->name_ids != NULL)
343 [ - + ]: 4 : g_assert (self->name_ids->len == 0);
344 [ + - ]: 4 : g_clear_pointer (&self->name_ids, g_array_unref);
345 : :
346 [ + - ]: 4 : if (self->server_message_queue != NULL)
347 [ - + ]: 4 : g_assert (g_async_queue_try_pop (self->server_message_queue) == NULL);
348 [ + - ]: 4 : g_clear_pointer (&self->server_message_queue, g_async_queue_unref);
349 : :
350 [ + - ]: 4 : g_clear_object (&self->bus);
351 : :
352 : : /* Note: We can’t assert that the @client_context is empty because we didn’t
353 : : * construct it. */
354 [ + - ]: 4 : if (self->server_context != NULL)
355 [ - + ]: 4 : g_assert (!g_main_context_iteration (self->server_context, FALSE));
356 [ + - ]: 4 : g_clear_pointer (&self->server_context, g_main_context_unref);
357 : :
358 : 4 : g_mutex_clear (&self->lock);
359 : :
360 : 4 : g_free (self);
361 : : }
362 : :
363 : : /**
364 : : * gt_dbus_queue_get_client_connection:
365 : : * @self: a #GtDBusQueue
366 : : *
367 : : * Get the client #GDBusConnection which should be passed to the code under test
368 : : * as its connection to a bus. This will be %NULL if gt_dbus_queue_connect() has
369 : : * not been called yet, or if gt_dbus_queue_disconnect() has been called.
370 : : *
371 : : * Returns: (nullable) (transfer none): the client’s bus connection
372 : : * Since: 0.1.0
373 : : */
374 : : GDBusConnection *
375 : 2 : gt_dbus_queue_get_client_connection (GtDBusQueue *self)
376 : : {
377 [ - + ]: 2 : g_return_val_if_fail (self != NULL, NULL);
378 : :
379 : 2 : return self->client_connection;
380 : : }
381 : :
382 : : /* Run on all messages seen (incoming or outgoing) by the server thread. Do some
383 : : * debug output from it. We do this here, rather than in any of the message
384 : : * handling functions, since it sees messages which the client might have
385 : : * forgotten to export objects for.
386 : : *
387 : : * Called in a random message handling thread. */
388 : : static GDBusMessage *
389 : 22 : gt_dbus_queue_server_filter_cb (GDBusConnection *connection,
390 : : GDBusMessage *message,
391 : : gboolean incoming,
392 : : gpointer user_data)
393 : : {
394 : 22 : g_autofree gchar *formatted = g_dbus_message_print (message, 2);
395 : :
396 [ + + ]: 22 : g_debug ("%s: Server %s Code Under Test\n%s",
397 : : G_STRFUNC, incoming ? "←" : "→", formatted);
398 : :
399 : : /* We could add a debugging feature here where it detects incoming method
400 : : * calls to object paths which are not exported and emits an obvious debug
401 : : * message, since it’s probably an omission in the unit test (or a typo in an
402 : : * existing object registration).
403 : : *
404 : : * On the other hand, to do so would be tricky (this function is called in a
405 : : * random thread), would not catch similar problems with well-known name
406 : : * ownership, and would only be useful in a small number of cases during
407 : : * test development. So it’s left unimplemented for now. */
408 : :
409 : 22 : return message;
410 : : }
411 : :
412 : : /**
413 : : * gt_dbus_queue_connect:
414 : : * @self: a #GtDBusQueue
415 : : * @error: return location for a #GError, or %NULL
416 : : *
417 : : * Create a private bus, mock D-Bus service, and a client #GDBusConnection to be
418 : : * used by the code under test. Once this function has been called, the test
419 : : * harness may call gt_dbus_queue_own_name() and gt_dbus_queue_export_object()
420 : : * and then run the code under test.
421 : : *
422 : : * This must be called from the thread which constructed the #GtDBusQueue.
423 : : *
424 : : * Returns: %TRUE on success, %FALSE otherwise
425 : : * Since: 0.1.0
426 : : */
427 : : gboolean
428 : 3 : gt_dbus_queue_connect (GtDBusQueue *self,
429 : : GError **error)
430 : : {
431 [ - + ]: 3 : g_return_val_if_fail (self != NULL, FALSE);
432 [ - + ]: 3 : g_return_val_if_fail (self->server_thread == NULL, FALSE);
433 [ + - - + ]: 3 : g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
434 : :
435 : 3 : g_main_context_push_thread_default (self->client_context);
436 : 3 : g_test_dbus_up (self->bus);
437 : :
438 : 2 : self->client_connection =
439 : 2 : g_dbus_connection_new_for_address_sync (g_test_dbus_get_bus_address (self->bus),
440 : : G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
441 : : G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
442 : : NULL,
443 : : NULL,
444 : : error);
445 : 2 : g_main_context_pop_thread_default (self->client_context);
446 : :
447 [ - + ]: 2 : if (self->client_connection == NULL)
448 : 0 : return FALSE;
449 : :
450 : 2 : g_main_context_push_thread_default (self->server_context);
451 : 2 : self->server_connection =
452 : 2 : g_dbus_connection_new_for_address_sync (g_test_dbus_get_bus_address (self->bus),
453 : : G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
454 : : G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
455 : : NULL,
456 : : NULL,
457 : : error);
458 : 2 : g_main_context_pop_thread_default (self->server_context);
459 : :
460 [ - + ]: 2 : if (self->server_connection == NULL)
461 : 0 : return FALSE;
462 : :
463 : 2 : self->server_filter_id = g_dbus_connection_add_filter (self->server_connection,
464 : : gt_dbus_queue_server_filter_cb,
465 : : NULL, NULL);
466 : :
467 : 2 : self->server_thread = g_thread_new ("GtDBusQueue server",
468 : : gt_dbus_queue_server_thread_cb,
469 : : self);
470 : :
471 : 2 : return TRUE;
472 : : }
473 : :
474 : : /**
475 : : * gt_dbus_queue_disconnect:
476 : : * @self: a #GtDBusQueue
477 : : * @assert_queue_empty: %TRUE to assert that the server message queue is empty
478 : : * before disconnecting, %FALSE to do nothing
479 : : *
480 : : * Disconnect the mock D-Bus service and client #GDBusConnection, and shut down
481 : : * the private bus.
482 : : *
483 : : * This must be called from the thread which constructed the #GtDBusQueue.
484 : : *
485 : : * Since: 0.1.0
486 : : */
487 : : void
488 : 2 : gt_dbus_queue_disconnect (GtDBusQueue *self,
489 : : gboolean assert_queue_empty)
490 : : {
491 [ - + ]: 2 : g_return_if_fail (self != NULL);
492 [ - + ]: 2 : g_return_if_fail (self->server_thread != NULL);
493 : :
494 [ + - ]: 2 : if (assert_queue_empty)
495 [ - + ]: 2 : gt_dbus_queue_assert_no_messages (self);
496 : :
497 [ + - ]: 2 : if (self->client_connection != NULL)
498 : 2 : g_dbus_connection_close_sync (self->client_connection, NULL, NULL);
499 [ + - ]: 2 : g_clear_object (&self->client_connection);
500 : :
501 : 2 : g_mutex_lock (&self->lock);
502 : :
503 [ + + ]: 4 : for (gsize i = 0; i < self->name_ids->len; i++)
504 : : {
505 : 2 : guint id = g_array_index (self->name_ids, guint, i);
506 : 2 : g_bus_unown_name (id);
507 : : }
508 : 2 : g_array_set_size (self->name_ids, 0);
509 : :
510 [ + + ]: 6 : for (gsize i = 0; i < self->object_ids->len; i++)
511 : : {
512 : 4 : guint id = g_array_index (self->object_ids, guint, i);
513 : 4 : g_dbus_connection_unregister_object (self->server_connection, id);
514 : : }
515 : 2 : g_array_set_size (self->object_ids, 0);
516 : :
517 : 2 : g_mutex_unlock (&self->lock);
518 : :
519 [ + - ]: 2 : if (self->server_filter_id != 0)
520 : : {
521 : 2 : g_dbus_connection_remove_filter (self->server_connection, self->server_filter_id);
522 : 2 : self->server_filter_id = 0;
523 : : }
524 : :
525 [ + - ]: 2 : if (self->server_connection != NULL)
526 : 2 : g_dbus_connection_close_sync (self->server_connection, NULL, NULL);
527 [ + - ]: 2 : g_clear_object (&self->server_connection);
528 : :
529 : 2 : g_test_dbus_down (self->bus);
530 : :
531 : : /* Pack up the server thread. */
532 : 2 : g_atomic_int_set (&self->quitting, TRUE);
533 : 2 : g_main_context_wakeup (self->server_context);
534 : 2 : g_thread_join (g_steal_pointer (&self->server_thread));
535 : : }
536 : :
537 : : typedef struct
538 : : {
539 : : GtDBusQueue *queue; /* (unowned) */
540 : : const gchar *name; /* (unowned) */
541 : : gint id; /* (atomic) */
542 : : } OwnNameData;
543 : :
544 : : static gboolean
545 : 2 : own_name_cb (gpointer user_data)
546 : : {
547 : 2 : OwnNameData *data = user_data;
548 : 2 : GtDBusQueue *queue = data->queue;
549 : : guint id;
550 : :
551 [ - + ]: 2 : g_assert (g_main_context_get_thread_default () == queue->server_context);
552 : :
553 : 2 : g_debug ("%s: Owning ‘%s’", G_STRFUNC, data->name);
554 : 2 : id = g_bus_own_name_on_connection (queue->server_connection,
555 : : data->name,
556 : : G_BUS_NAME_OWNER_FLAGS_NONE,
557 : : NULL, NULL, NULL, NULL);
558 : :
559 : 2 : g_atomic_int_set (&data->id, id);
560 : :
561 : 2 : return G_SOURCE_REMOVE;
562 : : }
563 : :
564 : : /**
565 : : * gt_dbus_queue_own_name:
566 : : * @self: a #GtDBusQueue
567 : : * @name: the well-known D-Bus name to own
568 : : *
569 : : * Make the mock D-Bus service acquire the given @name on the private bus, so
570 : : * that code under test can address the mock service using @name. This behaves
571 : : * similarly to g_bus_own_name().
572 : : *
573 : : * This may be called from any thread after gt_dbus_queue_connect() has been
574 : : * called.
575 : : *
576 : : * Returns: ID for the name ownership, which may be passed to
577 : : * gt_dbus_queue_unown_name() to release it in future; guaranteed to be
578 : : * non-zero
579 : : * Since: 0.1.0
580 : : */
581 : : guint
582 : 2 : gt_dbus_queue_own_name (GtDBusQueue *self,
583 : : const gchar *name)
584 : : {
585 : 2 : OwnNameData data = { NULL, };
586 : : guint id;
587 : :
588 [ - + ]: 2 : g_return_val_if_fail (self != NULL, 0);
589 [ - + ]: 2 : g_return_val_if_fail (self->server_thread != NULL, 0);
590 [ + - + - ]: 2 : g_return_val_if_fail (g_dbus_is_name (name) && !g_dbus_is_unique_name (name), 0);
591 : :
592 : : /* The name has to be acquired from the server thread, so invoke a callback
593 : : * there to do that, and block on a result. No need for locking: @id is
594 : : * accessed atomically, and the other members are not written after they’re
595 : : * initially set. */
596 : 2 : data.queue = self;
597 : 2 : data.name = name;
598 : 2 : data.id = 0;
599 : :
600 : 2 : g_main_context_invoke_full (self->server_context,
601 : : G_PRIORITY_DEFAULT,
602 : : own_name_cb,
603 : : &data,
604 : : NULL);
605 : :
606 [ + + ]: 65796 : while ((id = g_atomic_int_get (&data.id)) == 0);
607 : :
608 : 2 : g_mutex_lock (&self->lock);
609 : 2 : g_array_append_val (self->name_ids, id);
610 : 2 : g_mutex_unlock (&self->lock);
611 : :
612 : 2 : return id;
613 : : }
614 : :
615 : : /**
616 : : * gt_dbus_queue_unown_name:
617 : : * @self: a #GtDBusQueue
618 : : * @id: the name ID returned by gt_dbus_queue_own_name()
619 : : *
620 : : * Make the mock D-Bus service release a name on the private bus previously
621 : : * acquired using gt_dbus_queue_own_name(). This behaves similarly to
622 : : * g_bus_unown_name().
623 : : *
624 : : * This may be called from any thread after gt_dbus_queue_connect() has been
625 : : * called.
626 : : *
627 : : * Since: 0.1.0
628 : : */
629 : : void
630 : 0 : gt_dbus_queue_unown_name (GtDBusQueue *self,
631 : : guint id)
632 : : {
633 [ # # ]: 0 : g_return_if_fail (self != NULL);
634 [ # # ]: 0 : g_return_if_fail (self->server_thread != NULL);
635 [ # # ]: 0 : g_return_if_fail (id != 0);
636 : :
637 : 0 : g_mutex_lock (&self->lock);
638 [ # # ]: 0 : for (gsize i = 0; i < self->name_ids->len; i++)
639 : : {
640 : 0 : guint found_id = g_array_index (self->name_ids, guint, i);
641 : :
642 [ # # ]: 0 : if (found_id == id)
643 : : {
644 : 0 : g_array_remove_index_fast (self->name_ids, i);
645 : 0 : g_mutex_unlock (&self->lock);
646 : :
647 : : /* This is thread safe by itself. */
648 : 0 : g_bus_unown_name (id);
649 : :
650 : 0 : return;
651 : : }
652 : : }
653 : :
654 : 0 : g_mutex_unlock (&self->lock);
655 : :
656 : : /* @id wasn’t found. */
657 : 0 : g_assert_not_reached ();
658 : : }
659 : :
660 : : typedef struct
661 : : {
662 : : /* Protects everything in this struct. */
663 : : GMutex lock;
664 : : GCond cond;
665 : :
666 : : GtDBusQueue *queue; /* (unowned) */
667 : :
668 : : const gchar *object_path; /* (unowned) */
669 : : const GDBusInterfaceInfo *interface_info; /* (unowned) */
670 : :
671 : : guint id;
672 : : GError *error; /* (nullable) (owned) */
673 : : } ExportObjectData;
674 : :
675 : : static gboolean
676 : 4 : export_object_cb (gpointer user_data)
677 : : {
678 : 4 : ExportObjectData *data = user_data;
679 : 4 : GtDBusQueue *queue = data->queue;
680 : :
681 [ - + ]: 4 : g_assert (g_main_context_get_thread_default () == queue->server_context);
682 : :
683 : 4 : g_mutex_lock (&data->lock);
684 : :
685 : 4 : g_debug ("%s: Exporting ‘%s’", G_STRFUNC, data->object_path);
686 : 8 : data->id = g_dbus_connection_register_object (queue->server_connection,
687 : : data->object_path,
688 : 4 : data->interface_info,
689 : : >_dbus_queue_vtable,
690 : : queue,
691 : : NULL,
692 : : &data->error);
693 : :
694 : 4 : g_cond_signal (&data->cond);
695 : 4 : g_mutex_unlock (&data->lock);
696 : :
697 : 4 : return G_SOURCE_REMOVE;
698 : : }
699 : :
700 : : /**
701 : : * gt_dbus_queue_export_object:
702 : : * @self: a #GtDBusQueue
703 : : * @object_path: the path to export an object on
704 : : * @interface_info: (transfer none): definition of the interface to export
705 : : * @error: return location for a #GError, or %NULL
706 : : *
707 : : * Make the mock D-Bus service export an interface matching @interface_info at
708 : : * the given @object_path, so that code under test can call methods at that
709 : : * @object_path. This behaves similarly to g_dbus_connection_register_object().
710 : : *
711 : : * This may be called from any thread after gt_dbus_queue_connect() has been
712 : : * called.
713 : : *
714 : : * Returns: ID for the exported object, which may be passed to
715 : : * gt_dbus_queue_unexport_object() to release it in future; guaranteed to be
716 : : * non-zero
717 : : * Since: 0.1.0
718 : : */
719 : : guint
720 : 4 : gt_dbus_queue_export_object (GtDBusQueue *self,
721 : : const gchar *object_path,
722 : : GDBusInterfaceInfo *interface_info,
723 : : GError **error)
724 : : {
725 : 4 : ExportObjectData data = { NULL, };
726 : 4 : g_autoptr(GError) local_error = NULL;
727 : : guint id;
728 : :
729 [ - + ]: 4 : g_return_val_if_fail (self != NULL, 0);
730 [ - + ]: 4 : g_return_val_if_fail (self->server_thread != NULL, 0);
731 [ + - + - ]: 4 : g_return_val_if_fail (object_path != NULL && g_variant_is_object_path (object_path), 0);
732 [ - + ]: 4 : g_return_val_if_fail (interface_info != NULL, 0);
733 [ + - - + ]: 4 : g_return_val_if_fail (error == NULL || *error == NULL, 0);
734 : :
735 : : /* The object has to be exported from the server thread, so invoke a callback
736 : : * there to do that, and block on a result. */
737 : 4 : g_mutex_init (&data.lock);
738 : 4 : g_cond_init (&data.cond);
739 : 4 : data.queue = self;
740 : 4 : data.object_path = object_path;
741 : 4 : data.interface_info = interface_info;
742 : 4 : data.id = 0;
743 : 4 : data.error = NULL;
744 : :
745 : 4 : g_main_context_invoke_full (self->server_context,
746 : : G_PRIORITY_DEFAULT,
747 : : export_object_cb,
748 : : &data,
749 : : NULL);
750 : :
751 : 4 : g_mutex_lock (&data.lock);
752 : :
753 [ + + + - ]: 8 : while (data.id == 0 && data.error == NULL)
754 : 4 : g_cond_wait (&data.cond, &data.lock);
755 : :
756 : 4 : local_error = g_steal_pointer (&data.error);
757 : 4 : id = data.id;
758 : :
759 : 4 : g_mutex_unlock (&data.lock);
760 : :
761 [ - + ]: 4 : if (local_error != NULL)
762 : : {
763 : 0 : g_propagate_error (error, g_steal_pointer (&local_error));
764 : 0 : return 0;
765 : : }
766 : :
767 [ - + ]: 4 : g_assert (id != 0);
768 : :
769 : 4 : g_mutex_lock (&self->lock);
770 : 4 : g_array_append_val (self->object_ids, id);
771 : 4 : g_mutex_unlock (&self->lock);
772 : :
773 : 4 : return id;
774 : : }
775 : :
776 : : /**
777 : : * gt_dbus_queue_unexport_object:
778 : : * @self: a #GtDBusQueue
779 : : * @id: the name ID returned by gt_dbus_queue_export_object()
780 : : *
781 : : * Make the mock D-Bus service unexport an object on the private bus previously
782 : : * exported using gt_dbus_queue_export_object(). This behaves similarly to
783 : : * g_dbus_connection_unregister_object().
784 : : *
785 : : * This may be called from any thread after gt_dbus_queue_connect() has been
786 : : * called.
787 : : *
788 : : * Since: 0.1.0
789 : : */
790 : : void
791 : 0 : gt_dbus_queue_unexport_object (GtDBusQueue *self,
792 : : guint id)
793 : : {
794 [ # # ]: 0 : g_return_if_fail (self != NULL);
795 [ # # ]: 0 : g_return_if_fail (self->server_thread != NULL);
796 [ # # ]: 0 : g_return_if_fail (id != 0);
797 : :
798 : 0 : g_mutex_lock (&self->lock);
799 : :
800 [ # # ]: 0 : for (gsize i = 0; i < self->object_ids->len; i++)
801 : : {
802 : 0 : guint found_id = g_array_index (self->object_ids, guint, i);
803 : :
804 [ # # ]: 0 : if (found_id == id)
805 : : {
806 : : gboolean was_registered;
807 : :
808 : 0 : g_array_remove_index_fast (self->object_ids, i);
809 : 0 : g_mutex_unlock (&self->lock);
810 : :
811 : : /* This is inherently thread safe. */
812 : 0 : was_registered = g_dbus_connection_unregister_object (self->server_connection,
813 : : id);
814 [ # # ]: 0 : g_assert (was_registered);
815 : :
816 : 0 : return;
817 : : }
818 : : }
819 : :
820 : 0 : g_mutex_unlock (&self->lock);
821 : :
822 : : /* @id wasn’t found. */
823 : 0 : g_assert_not_reached ();
824 : : }
825 : :
826 : : /**
827 : : * gt_dbus_queue_set_server_func:
828 : : * @self: a #GtDBusQueue
829 : : * @func: (not nullable): a #GtDBusQueueServerFunc to run in the server thread
830 : : * @user_data: data to pass to @func
831 : : *
832 : : * Set a function to run in the server thread to handle incoming method calls.
833 : : * This is a requirement when testing code which makes synchronous function
834 : : * calls, as they will block the test thread’s main context until they return.
835 : : * This can also be used when testing asynchronous code, which allows reuse of
836 : : * the same mock service implementation when testing synchronous and
837 : : * asynchronous versions of the same code under test functionality.
838 : : *
839 : : * @func will be executed in the server thread, so must only call thread safe
840 : : * methods of the #GtDBusQueue, and must use thread safe access to @user_data
841 : : * if it’s used in any other threads.
842 : : *
843 : : * Since: 0.1.0
844 : : */
845 : : void
846 : 2 : gt_dbus_queue_set_server_func (GtDBusQueue *self,
847 : : GtDBusQueueServerFunc func,
848 : : gpointer user_data)
849 : : {
850 : : gboolean swapped;
851 : :
852 [ - + ]: 2 : g_return_if_fail (self != NULL);
853 [ - + ]: 2 : g_return_if_fail (func != NULL);
854 : :
855 : : /* Set the data first so it’s gated by the server func. */
856 : 2 : g_atomic_pointer_set (&self->server_func_data, user_data);
857 : 2 : swapped = g_atomic_pointer_compare_and_exchange (&self->server_func, NULL, func);
858 : :
859 : : /* This function should not be called twice. */
860 [ - + ]: 2 : g_assert (swapped);
861 : :
862 : 2 : g_main_context_wakeup (self->server_context);
863 : : }
864 : :
865 : : /* The main thread function for the server thread. This will run until
866 : : * #GtDBusQueue.quitting is set. It will wait for a #GtDBusQueueServerFunc to
867 : : * be set, call it, and then continue to run the #GtDBusQueue.server_context
868 : : * until instructed to quit.
869 : : *
870 : : * Acquires #GtDBusQueue.server_context. */
871 : : static gpointer
872 : 2 : gt_dbus_queue_server_thread_cb (gpointer user_data)
873 : : {
874 : 2 : GtDBusQueue *self = user_data;
875 : : GtDBusQueueServerFunc server_func;
876 : : gpointer server_func_data;
877 : :
878 : 2 : g_main_context_push_thread_default (self->server_context);
879 : :
880 : : /* Wait for the client to provide message handling. */
881 [ + - ]: 10 : while (!g_atomic_int_get (&self->quitting) &&
882 [ + + ]: 10 : g_atomic_pointer_get (&self->server_func) == NULL)
883 : 8 : g_main_context_iteration (self->server_context, TRUE);
884 : :
885 : 2 : server_func = g_atomic_pointer_get (&self->server_func);
886 : 2 : server_func_data = g_atomic_pointer_get (&self->server_func_data);
887 : :
888 [ + - ]: 2 : if (server_func != NULL)
889 : 2 : server_func (self, server_func_data);
890 : :
891 : : /* Wait for the process to signal to quit. This is also the main message
892 : : * handling loop if no @server_func has been provided. */
893 [ + + ]: 13 : while (!g_atomic_int_get (&self->quitting))
894 : 11 : g_main_context_iteration (self->server_context, TRUE);
895 : :
896 : : /* Process any remaining sources while quitting, without blocking. */
897 [ - + ]: 2 : while (g_main_context_iteration (self->server_context, FALSE));
898 : :
899 : 2 : g_main_context_pop_thread_default (self->server_context);
900 : :
901 : 2 : return NULL;
902 : : }
903 : :
904 : : /* Handle an incoming method call to the mock service. This is run in the server
905 : : * thread, under #GtDBusQueue.server_context. It pushes the received message
906 : : * onto the server’s message queue and wakes up any #GMainContext which is
907 : : * potentially blocking on a gt_dbus_queue_pop_message() call. */
908 : : static void
909 : 4 : gt_dbus_queue_method_call (GDBusConnection *connection,
910 : : const gchar *sender,
911 : : const gchar *object_path,
912 : : const gchar *interface_name,
913 : : const gchar *method_name,
914 : : GVariant *parameters,
915 : : GDBusMethodInvocation *invocation,
916 : : gpointer user_data)
917 : : {
918 : 4 : GtDBusQueue *self = user_data;
919 : 4 : GDBusMessage *message = g_dbus_method_invocation_get_message (invocation);
920 : :
921 : 4 : g_debug ("%s: Server pushing message serial %u",
922 : : G_STRFUNC, g_dbus_message_get_serial (message));
923 : 4 : g_async_queue_push (self->server_message_queue, g_object_ref (invocation));
924 : :
925 : : /* Either of these could be listening for the message, depending on whether
926 : : * gt_dbus_queue_pop_message() is being called in the #GtDBusQueueServerFunc,
927 : : * or in the thread where the #GtDBusQueue was constructed (typically the main
928 : : * thread of the test program). */
929 : 4 : g_main_context_wakeup (self->client_context);
930 : 4 : g_main_context_wakeup (self->server_context);
931 : 4 : }
932 : :
933 : : /**
934 : : * gt_dbus_queue_get_n_messages:
935 : : * @self: a #GtDBusQueue
936 : : *
937 : : * Get the number of messages waiting in the server queue to be popped by
938 : : * gt_dbus_queue_pop_message() and processed.
939 : : *
940 : : * If asserting that the queue is empty, gt_dbus_queue_assert_no_messages() is
941 : : * more appropriate.
942 : : *
943 : : * This may be called from any thread.
944 : : *
945 : : * Returns: number of messages waiting to be popped and processed
946 : : * Since: 0.1.0
947 : : */
948 : : gsize
949 : 4 : gt_dbus_queue_get_n_messages (GtDBusQueue *self)
950 : : {
951 : : gint n_messages;
952 : :
953 [ - + ]: 4 : g_return_val_if_fail (self != NULL, 0);
954 : :
955 : 4 : n_messages = g_async_queue_length (self->server_message_queue);
956 : :
957 : 4 : return MAX (n_messages, 0);
958 : : }
959 : :
960 : : /*
961 : : * gt_dbus_queue_pop_message_internal:
962 : : * @self: a #GtDBusQueue
963 : : * @wait: %TRUE to block until a message can be popped, %FALSE to return
964 : : * immediately if no message is queued
965 : : * @out_invocation: (out) (transfer full) (optional) (nullable): return location
966 : : * for the popped #GDBusMethodInvocation, which may be %NULL; pass %NULL to
967 : : * not receive the #GDBusMethodInvocation
968 : : *
969 : : * Internal helper which implements gt_dbus_queue_pop_message() and
970 : : * gt_dbus_queue_try_pop_message().
971 : : *
972 : : * This may be called from any thread after gt_dbus_queue_connect() has been
973 : : * called.
974 : : *
975 : : * Returns: %TRUE if a message was popped (and returned in @out_invocation if
976 : : * @out_invocation was non-%NULL), %FALSE otherwise
977 : : * Since: 0.1.0
978 : : */
979 : : static gboolean
980 : 4 : gt_dbus_queue_pop_message_internal (GtDBusQueue *self,
981 : : gboolean wait,
982 : : GDBusMethodInvocation **out_invocation)
983 : : {
984 : 4 : g_autoptr(GDBusMethodInvocation) invocation = NULL;
985 : 4 : gboolean message_popped = FALSE;
986 : :
987 [ - + ]: 4 : g_return_val_if_fail (self != NULL, FALSE);
988 [ - + ]: 4 : g_return_val_if_fail (self->server_thread != NULL, FALSE);
989 : :
990 [ + - + + ]: 14 : while (wait && (invocation = g_async_queue_try_pop (self->server_message_queue)) == NULL)
991 : : {
992 : : /* This could be the client or server context, depending on whether we’re
993 : : * executing in a #GtDBusQueueServerFunc or not. */
994 : 10 : g_main_context_iteration (g_main_context_get_thread_default (), TRUE);
995 : : }
996 : :
997 [ + - ]: 4 : if (invocation != NULL)
998 : : {
999 : 4 : GDBusMessage *message = g_dbus_method_invocation_get_message (invocation);
1000 : 4 : g_debug ("%s: Client popping message serial %u",
1001 : : G_STRFUNC, g_dbus_message_get_serial (message));
1002 : 4 : message_popped = TRUE;
1003 : : }
1004 : :
1005 [ + - ]: 4 : if (out_invocation != NULL)
1006 : 4 : *out_invocation = g_steal_pointer (&invocation);
1007 : :
1008 : 4 : return message_popped;
1009 : : }
1010 : :
1011 : : /**
1012 : : * gt_dbus_queue_try_pop_message:
1013 : : * @self: a #GtDBusQueue
1014 : : * @out_invocation: (out) (transfer full) (optional) (nullable): return location
1015 : : * for the popped #GDBusMethodInvocation, which may be %NULL; pass %NULL to
1016 : : * not receive the #GDBusMethodInvocation
1017 : : *
1018 : : * Pop a message off the server’s message queue, if one is ready to be popped.
1019 : : * Otherwise, immediately return %NULL.
1020 : : *
1021 : : * This may be called from any thread after gt_dbus_queue_connect() has been
1022 : : * called.
1023 : : *
1024 : : * Returns: %TRUE if a message was popped (and returned in @out_invocation if
1025 : : * @out_invocation was non-%NULL), %FALSE otherwise
1026 : : * Since: 0.1.0
1027 : : */
1028 : : gboolean
1029 : 0 : gt_dbus_queue_try_pop_message (GtDBusQueue *self,
1030 : : GDBusMethodInvocation **out_invocation)
1031 : : {
1032 [ # # ]: 0 : g_return_val_if_fail (self != NULL, FALSE);
1033 : :
1034 : 0 : return gt_dbus_queue_pop_message_internal (self, FALSE, out_invocation);
1035 : : }
1036 : :
1037 : : /**
1038 : : * gt_dbus_queue_pop_message:
1039 : : * @self: a #GtDBusQueue
1040 : : * @out_invocation: (out) (transfer full) (optional) (nullable): return location
1041 : : * for the popped #GDBusMethodInvocation, which may be %NULL; pass %NULL to
1042 : : * not receive the #GDBusMethodInvocation
1043 : : *
1044 : : * Pop a message off the server’s message queue, if one is ready to be popped.
1045 : : * Otherwise, block indefinitely until one is.
1046 : : *
1047 : : * This may be called from any thread after gt_dbus_queue_connect() has been
1048 : : * called.
1049 : : *
1050 : : * Returns: %TRUE if a message was popped (and returned in @out_invocation if
1051 : : * @out_invocation was non-%NULL), %FALSE if the pop timed out
1052 : : * Since: 0.1.0
1053 : : */
1054 : : gboolean
1055 : 4 : gt_dbus_queue_pop_message (GtDBusQueue *self,
1056 : : GDBusMethodInvocation **out_invocation)
1057 : : {
1058 [ - + ]: 4 : g_return_val_if_fail (self != NULL, FALSE);
1059 : :
1060 : 4 : return gt_dbus_queue_pop_message_internal (self, TRUE, out_invocation);
1061 : : }
1062 : :
1063 : : /**
1064 : : * gt_dbus_queue_match_client_message:
1065 : : * @self: a #GtDBusQueue
1066 : : * @invocation: (transfer none): invocation to match against
1067 : : * @expected_object_path: object path the invocation is expected to be calling
1068 : : * @expected_interface_name: interface name the invocation is expected to be calling
1069 : : * @expected_method_name: method name the invocation is expected to be calling
1070 : : * @expected_parameters_string: (nullable): expected parameters for the
1071 : : * invocation, or %NULL to not match its parameters
1072 : : *
1073 : : * Check whether @invocation matches the given expected object path, interface
1074 : : * name, method name and (optionally) parameters, and was sent by the client
1075 : : * connection of the #GtDBusQueue.
1076 : : *
1077 : : * This may be called from any thread after gt_dbus_queue_connect() has been
1078 : : * called.
1079 : : *
1080 : : * @expected_parameters_string is optional, and will be matched against only if
1081 : : * it is non-%NULL. The other arguments are not optional. If non-%NULL,
1082 : : * @expected_parameters_string will be parsed using g_variant_new_parsed(). It
1083 : : * is a programmer error to provide a string which doesn’t parse correctly.
1084 : : *
1085 : : * Returns: %TRUE if @invocation matches the expected arguments,
1086 : : * %FALSE otherwise
1087 : : * Since: 0.1.0
1088 : : */
1089 : : gboolean
1090 : 4 : gt_dbus_queue_match_client_message (GtDBusQueue *self,
1091 : : GDBusMethodInvocation *invocation,
1092 : : const gchar *expected_object_path,
1093 : : const gchar *expected_interface_name,
1094 : : const gchar *expected_method_name,
1095 : : const gchar *expected_parameters_string)
1096 : : {
1097 : 4 : g_autoptr(GVariant) expected_parameters = NULL;
1098 : :
1099 [ - + ]: 4 : g_return_val_if_fail (self != NULL, FALSE);
1100 [ - + + - : 4 : g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+ - - + ]
1101 [ - + ]: 4 : g_return_val_if_fail (g_variant_is_object_path (expected_object_path), FALSE);
1102 [ - + ]: 4 : g_return_val_if_fail (g_dbus_is_interface_name (expected_interface_name), FALSE);
1103 [ - + ]: 4 : g_return_val_if_fail (g_dbus_is_member_name (expected_method_name), FALSE);
1104 : :
1105 [ - + ]: 4 : if (expected_parameters_string != NULL)
1106 : 0 : expected_parameters = g_variant_new_parsed (expected_parameters_string);
1107 : :
1108 : 4 : return (g_str_equal (g_dbus_method_invocation_get_sender (invocation),
1109 [ + - ]: 8 : g_dbus_connection_get_unique_name (self->client_connection)) &&
1110 : 4 : g_str_equal (g_dbus_method_invocation_get_object_path (invocation),
1111 [ + - ]: 4 : expected_object_path) &&
1112 : 4 : g_str_equal (g_dbus_method_invocation_get_interface_name (invocation),
1113 [ + - ]: 4 : expected_interface_name) &&
1114 : 4 : g_str_equal (g_dbus_method_invocation_get_method_name (invocation),
1115 [ + - ]: 8 : expected_method_name) &&
1116 [ - + - - ]: 4 : (expected_parameters == NULL ||
1117 : 0 : g_variant_equal (g_dbus_method_invocation_get_parameters (invocation),
1118 : : expected_parameters)));
1119 : : }
1120 : :
1121 : : /**
1122 : : * gt_dbus_queue_format_message:
1123 : : * @invocation: a #GDBusMethodInvocation to format
1124 : : *
1125 : : * Format a #GDBusMethodInvocation in a human readable way. This format is not
1126 : : * intended to be stable or machine parsable.
1127 : : *
1128 : : * Returns: (transfer full): human readable version of @invocation
1129 : : * Since: 0.1.0
1130 : : */
1131 : : gchar *
1132 : 0 : gt_dbus_queue_format_message (GDBusMethodInvocation *invocation)
1133 : : {
1134 [ # # # # : 0 : g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), NULL);
# # # # ]
1135 : :
1136 : 0 : return g_dbus_message_print (g_dbus_method_invocation_get_message (invocation), 0);
1137 : : }
1138 : :
1139 : : /**
1140 : : * gt_dbus_queue_format_messages:
1141 : : * @self: a #GtDBusQueue
1142 : : *
1143 : : * Format all the messages currently pending in the mock service’s message queue
1144 : : * in a human readable way, with the head of the queue first in the formatted
1145 : : * list. This format is not intended to be stable or machine parsable.
1146 : : *
1147 : : * If no messages are in the queue, an empty string will be returned.
1148 : : *
1149 : : * Returns: (transfer full): human readable version of the pending message queue
1150 : : * Since: 0.1.0
1151 : : */
1152 : : gchar *
1153 : 0 : gt_dbus_queue_format_messages (GtDBusQueue *self)
1154 : : {
1155 : 0 : g_autoptr(GString) output = NULL;
1156 : 0 : g_autoptr(GPtrArray) local_queue = NULL;
1157 : 0 : g_autoptr(GDBusMethodInvocation) message = NULL;
1158 : : gsize i;
1159 : :
1160 [ # # ]: 0 : g_return_val_if_fail (self != NULL, NULL);
1161 : :
1162 : : /* Cyclically pop all the elements off the head of the queue and push them
1163 : : * onto a local queue, printing each one as we go. Then push that lot back
1164 : : * onto the original queue so it’s unchanged overall. Since there are no
1165 : : * accessors to inspect the inner elements of a #GAsyncQueue, this is the best
1166 : : * we can do. */
1167 : 0 : g_async_queue_lock (self->server_message_queue);
1168 : :
1169 : 0 : local_queue = g_ptr_array_new_with_free_func (g_object_unref);
1170 : 0 : output = g_string_new ("");
1171 : :
1172 [ # # ]: 0 : while ((message = g_async_queue_try_pop_unlocked (self->server_message_queue)) != NULL)
1173 : : {
1174 : 0 : g_autofree gchar *formatted = gt_dbus_queue_format_message (message);
1175 : 0 : g_string_append (output, formatted);
1176 : :
1177 : 0 : g_ptr_array_add (local_queue, g_steal_pointer (&message));
1178 : : }
1179 : :
1180 : : /* Reassemble the queue. */
1181 [ # # ]: 0 : for (i = 0; i < local_queue->len; i++)
1182 : : {
1183 : : /* FIXME: Use g_ptr_array_steal() here once we can depend on a new enough
1184 : : * GLib version. */
1185 : 0 : message = g_steal_pointer (&g_ptr_array_index (local_queue, i));
1186 : 0 : g_async_queue_push_unlocked (self->server_message_queue, g_steal_pointer (&message));
1187 : : }
1188 : :
1189 : : /* We’ve stolen all the elements. */
1190 : 0 : g_ptr_array_set_free_func (local_queue, NULL);
1191 : :
1192 : 0 : g_async_queue_unlock (self->server_message_queue);
1193 : :
1194 : 0 : return g_string_free (g_steal_pointer (&output), FALSE);
1195 : : }
1196 : :
1197 : : /*< private >*/
1198 : : /*
1199 : : * gt_dbus_queue_assert_pop_message_impl:
1200 : : * @self: a #GtDBusQueue
1201 : : * @macro_log_domain: #G_LOG_DOMAIN from the call site
1202 : : * @macro_file: C file containing the call site
1203 : : * @macro_line: line containing the call site
1204 : : * @macro_function: function containing the call site
1205 : : * @expected_object_path: object path the invocation is expected to be calling
1206 : : * @expected_interface_name: interface name the invocation is expected to be calling
1207 : : * @expected_method_name: method name the invocation is expected to be calling
1208 : : * @parameters_format: g_variant_get() format string to extract the parameters
1209 : : * from the popped #GDBusMethodInvocation into the return locations provided
1210 : : * in @...
1211 : : * @...: return locations for the parameter placeholders given in @parameters_format
1212 : : *
1213 : : * Internal function which implements the gt_dbus_queue_assert_pop_message()
1214 : : * macro.
1215 : : *
1216 : : * An assertion failure message will be printed if a #GDBusMethodInvocation
1217 : : * can’t be popped from the queue.
1218 : : *
1219 : : * Returns: (transfer full): the popped #GDBusMethodInvocation
1220 : : * Since: 0.1.0
1221 : : */
1222 : : GDBusMethodInvocation *
1223 : 4 : gt_dbus_queue_assert_pop_message_impl (GtDBusQueue *self,
1224 : : const gchar *macro_log_domain,
1225 : : const gchar *macro_file,
1226 : : gint macro_line,
1227 : : const gchar *macro_function,
1228 : : const gchar *expected_object_path,
1229 : : const gchar *expected_interface_name,
1230 : : const gchar *expected_method_name,
1231 : : const gchar *parameters_format,
1232 : : ...)
1233 : : {
1234 : 4 : g_autoptr(GDBusMethodInvocation) invocation = NULL;
1235 : :
1236 [ - + ]: 4 : g_return_val_if_fail (self != NULL, NULL);
1237 [ - + ]: 4 : g_return_val_if_fail (macro_file != NULL, NULL);
1238 [ - + ]: 4 : g_return_val_if_fail (macro_line >= 0, NULL);
1239 [ - + ]: 4 : g_return_val_if_fail (macro_function != NULL, NULL);
1240 [ - + ]: 4 : g_return_val_if_fail (g_variant_is_object_path (expected_object_path), NULL);
1241 [ - + ]: 4 : g_return_val_if_fail (g_dbus_is_interface_name (expected_interface_name), NULL);
1242 [ - + ]: 4 : g_return_val_if_fail (g_dbus_is_member_name (expected_method_name), NULL);
1243 [ - + ]: 4 : g_return_val_if_fail (parameters_format != NULL, NULL);
1244 : :
1245 [ - + ]: 4 : if (!gt_dbus_queue_pop_message (self, &invocation))
1246 : : {
1247 : 0 : g_autofree gchar *message =
1248 : 0 : g_strdup_printf ("Expected message %s.%s from %s, but saw no messages",
1249 : : expected_interface_name, expected_method_name,
1250 : : expected_object_path);
1251 : 0 : g_assertion_message (macro_log_domain, macro_file, macro_line,
1252 : : macro_function, message);
1253 : 0 : return NULL;
1254 : : }
1255 : :
1256 [ - + ]: 4 : if (!gt_dbus_queue_match_client_message (self, invocation,
1257 : : expected_object_path,
1258 : : expected_interface_name,
1259 : : expected_method_name,
1260 : : NULL))
1261 : : {
1262 : 0 : g_autofree gchar *invocation_formatted =
1263 : 0 : gt_dbus_queue_format_message (invocation);
1264 : 0 : g_autofree gchar *message =
1265 : 0 : g_strdup_printf ("Expected message %s.%s from %s, but saw: %s",
1266 : : expected_interface_name, expected_method_name,
1267 : : expected_object_path, invocation_formatted);
1268 : 0 : g_assertion_message (macro_log_domain, macro_file, macro_line,
1269 : : macro_function, message);
1270 : 0 : return NULL;
1271 : : }
1272 : :
1273 : : /* Passed the test! */
1274 : : va_list parameters_args;
1275 : 4 : GVariant *parameters = g_dbus_method_invocation_get_parameters (invocation);
1276 : :
1277 : 4 : va_start (parameters_args, parameters_format);
1278 : 4 : g_variant_get_va (parameters, parameters_format, NULL, ¶meters_args);
1279 : 4 : va_end (parameters_args);
1280 : :
1281 : 4 : return g_steal_pointer (&invocation);
1282 : : }
|