LCOV - code coverage report
Current view: top level - libglib-testing - dbus-queue.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 227 297 76.4 %
Date: 2022-03-10 11:32:37 Functions: 18 23 78.3 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 100 224 44.6 %

           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                 :            :                                                 &gt_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, &parameters_args);
    1279                 :          4 :   va_end (parameters_args);
    1280                 :            : 
    1281                 :          4 :   return g_steal_pointer (&invocation);
    1282                 :            : }

Generated by: LCOV version 1.14