GtDBusQueue: libglib-testing Reference Manual

## GtDBusQueue GtDBusQueue — D-Bus service mock implementation |

## Stability Level

Unstable, unless otherwise indicated

## Functions

void | (*GtDBusQueueServerFunc) ()
GtDBusQueue * | gt_dbus_queue_new ()
void | gt_dbus_queue_free ()
GDBusConnection * | gt_dbus_queue_get_client_connection ()
gboolean | gt_dbus_queue_connect ()
void | gt_dbus_queue_disconnect ()
guint | gt_dbus_queue_own_name ()
void | gt_dbus_queue_unown_name ()
guint | gt_dbus_queue_export_object ()
void | gt_dbus_queue_unexport_object ()
void | gt_dbus_queue_set_server_func ()
gsize | gt_dbus_queue_get_n_messages ()
gboolean | gt_dbus_queue_try_pop_message ()
gboolean | gt_dbus_queue_pop_message ()
gboolean | gt_dbus_queue_match_client_message ()
gchar * | gt_dbus_queue_format_message ()
gchar * | gt_dbus_queue_format_messages ()
#define | gt_dbus_queue_assert_no_messages()
#define | gt_dbus_queue_assert_pop_message()

## Types and Values

| GtDBusQueue

## Includes

    #include <libglib-testing/dbus-queue.h>

## Description

GtDBusQueue is an object which allows a D-Bus service to be mocked and implemented to return a variety of results from method calls with a high degree of flexibility. The mock service is driven from within the same process (and potentially the same GMainContext) as the code under test.

This allows a D-Bus service to be mocked without needing to generate and implement a full implementation of its interfaces.

A single GtDBusQueue instance can be used to mock one or more D-Bus services, depending on whether it’s desirable to process the queues of method calls to those services in order or independently from each other. Each GtDBusQueue has a queue of method calls received by the services it is mocking, which are ordered the same as they were received off the bus. It is intended that the test harness which is using GtDBusQueue should pop messages off the queue, and either check they are as expected and return a static reply, or construct a reply dynamically based on their contents.

Messages can be popped off the queue using `gt_dbus_queue_assert_pop_message()`, `gt_dbus_queue_pop_message()` or `gt_dbus_queue_try_pop_message()`. The former two block until a message can be popped, iterating the thread-default GMainContext while they block. The latter returns `FALSE` immediately if the queue is empty.

Popping and handling messages is typically done in the GtDBusQueue server thread using `gt_dbus_queue_set_server_func()`. This will work whether the code under test is synchronous or asynchronous. If the code under test is asynchronous, popping and handling messages can instead be done in the main test thread, but this has no particular advantages.

By default, a GtDBusQueue will not assert that its message queue is empty on destruction unless the `assert_queue_empty` argument is passed to `gt_dbus_queue_disconnect()`. If that argument is `FALSE`, it is highly recommended that `gt_dbus_queue_assert_no_messages()` is called before a GtDBusQueue is destroyed, or after a particular unit test is completed.

Conversely, a GtDBusQueue will not ensure that the thread default GMainContext for the thread where it’s constructed is empty when the GtDBusQueue is finalised. That is the responsibility of the caller who constructed the GMainContext.

#### Usage Example with GLib Testing Framework

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    typedef struct
    {
      GtDBusQueue *queue;  // (owned)
      uid_t valid_uid;
    } BusFixture;

    static void
    bus_set_up (BusFixture    *fixture,
                gconstpointer  test_data)
    {
      g_autoptr(GError) local_error = NULL;
      g_autofree gchar *object_path = NULL;

      fixture->valid_uid = 500;  // arbitrarily chosen
      fixture->queue = gt_dbus_queue_new ();

      gt_dbus_queue_connect (fixture->queue, &local_error);
      g_assert_no_error (local_error);

      gt_dbus_queue_own_name (fixture->queue, "org.freedesktop.Accounts");

      object_path = g_strdup_printf ("/org/freedesktop/Accounts/User%u", fixture->valid_uid);
      gt_dbus_queue_export_object (fixture->queue,
                                   object_path,
                                   (GDBusInterfaceInfo *) &app_filter_interface_info,
                                   &local_error);
      g_assert_no_error (local_error);

      gt_dbus_queue_export_object (fixture->queue,
                                   "/org/freedesktop/Accounts",
                                   (GDBusInterfaceInfo *) &accounts_interface_info,
                                   &local_error);
      g_assert_no_error (local_error);
    }

    static void
    bus_tear_down (BusFixture    *fixture,
                   gconstpointer  test_data)
    {
      gt_dbus_queue_disconnect (fixture->queue, TRUE);
      g_clear_pointer (&fixture->queue, gt_dbus_queue_free);
    }

    // Helper #GAsyncReadyCallback which returns the #GAsyncResult in its @user_data.
    static void
    async_result_cb (GObject      *obj,
                     GAsyncResult *result,
                     gpointer      user_data)
    {
      GAsyncResult **result_out = (GAsyncResult **) user_data;

      g_assert_null (*result_out);
      *result_out = g_object_ref (result);
    }

    // Test that getting an #EpcAppFilter from the mock D-Bus service works. The
    // @test_data is a boolean value indicating whether to do the call
    // synchronously (%FALSE) or asynchronously (%TRUE).
    //
    // The mock D-Bus replies are generated in get_app_filter_server_cb(), which is
    // used for both synchronous and asynchronous calls.
    static void get_app_filter_server_cb (GtDBusQueue *queue,
                                          gpointer     user_data);

    static void
    test_app_filter_bus_get (BusFixture    *fixture,
                             gconstpointer  test_data)
    {
      g_autoptr(EpcAppFilter) app_filter = NULL;
      g_autoptr(GError) local_error = NULL;
      gboolean test_async = GPOINTER_TO_UINT (test_data);

      gt_dbus_queue_set_server_func (fixture->queue, get_app_filter_server_cb, fixture);

      if (test_async)
        {
          g_autoptr(GAsyncResult) result = NULL;

          epc_get_app_filter_async (gt_dbus_queue_get_client_connection (fixture->queue),
                                    fixture->valid_uid,
                                    FALSE, NULL, async_result_cb, &result);

          while (result == NULL)
            g_main_context_iteration (NULL, TRUE);
          app_filter = epc_get_app_filter_finish (result, &local_error);
        }
      else
        {
          app_filter = epc_get_app_filter (gt_dbus_queue_get_client_connection (fixture->queue),
                                           fixture->valid_uid,
                                           FALSE, NULL, &local_error);
        }

      g_assert_no_error (local_error);
      g_assert_nonnull (app_filter);

      // Check the app filter properties.
      g_assert_cmpuint (epc_app_filter_get_user_id (app_filter), ==, fixture->valid_uid);
      g_assert_false (epc_app_filter_is_flatpak_app_allowed (app_filter, "org.gnome.Builder"));
      g_assert_true (epc_app_filter_is_flatpak_app_allowed (app_filter, "org.gnome.Chess"));
    }

    // This is run in a worker thread.
    static void
    get_app_filter_server_cb (GtDBusQueue *queue,
                              gpointer     user_data)
    {
      BusFixture *fixture = user_data;
      g_autoptr(GDBusMethodInvocation) invocation1 = NULL;
      g_autoptr(GDBusMethodInvocation) invocation2 = NULL;
      g_autofree gchar *object_path = NULL;
      g_autofree gchar *reply1 = NULL;

      // Handle the FindUserById() call.
      gint64 user_id;
      invocation1 =
          gt_dbus_queue_assert_pop_message (queue,
                                            "/org/freedesktop/Accounts",
                                            "org.freedesktop.Accounts",
                                            "FindUserById", "(x)", &user_id);
      g_assert_cmpint (user_id, ==, fixture->valid_uid);

      object_path = g_strdup_printf ("/org/freedesktop/Accounts/User%u", (uid_t) user_id);
      reply1 = g_strdup_printf ("(@o '%s',)", object_path);
      g_dbus_method_invocation_return_value (invocation1, g_variant_new_parsed (reply1));

      // Handle the Properties.GetAll() call and return some arbitrary, valid values
      // for the given user.
      const gchar *property_interface;
      invocation2 =
          gt_dbus_queue_assert_pop_message (queue,
                                            object_path,
                                            "org.freedesktop.DBus.Properties",
                                            "GetAll", "(&s)", &property_interface);
      g_assert_cmpstr (property_interface, ==, "com.endlessm.ParentalControls.AppFilter");

      const gchar *reply2 =
        "({"
          "'allow-user-installation': <true>,"
          "'allow-system-installation': <false>,"
          "'app-filter': <(false, ['app/org.gnome.Builder/x86_64/stable'])>,"
          "'oars-filter': <('oars-1.1', { 'violence-bloodshed': 'mild' })>"
        "},)";
      g_dbus_method_invocation_return_value (invocation2, g_variant_new_parsed (reply2));
    }

## Functions

### GtDBusQueueServerFunc ()

    void
    (*GtDBusQueueServerFunc) (GtDBusQueue *queue,
                              gpointer user_data);

Function called in the server thread to handle incoming method calls. See `gt_dbus_queue_set_server_func()` for details.

#### Parameters

queue | a GtDBusQueue |
user_data | user data passed to `gt_dbus_queue_set_server_func()` |

Since: 0.1.0

### gt_dbus_queue_new ()

    GtDBusQueue *
    gt_dbus_queue_new (void);

Create a new GtDBusQueue. Start it using `gt_dbus_queue_connect()`, own a name using `gt_dbus_queue_own_name()` and register objects using `gt_dbus_queue_export_object()`. Start a particular test run using `gt_dbus_queue_set_server_func()`.

#### Returns

a new GtDBusQueue.

[transfer full]

Since: 0.1.0

### gt_dbus_queue_free ()

    void
    gt_dbus_queue_free (GtDBusQueue *self);

Free a GtDBusQueue. This will call `gt_dbus_queue_disconnect()` if it hasn’t been called already, and will assert that there are no messages left in the server message queue.

If you wish to free the GtDBusQueue regardless of whether there are messages left in the server message queue, call `gt_dbus_queue_disconnect()` explicitly before this function, and pass `FALSE` as its second argument.

#### Parameters

self | a GtDBusQueue. | [transfer full]

Since: 0.1.0

### gt_dbus_queue_get_client_connection ()

    GDBusConnection *
    gt_dbus_queue_get_client_connection (GtDBusQueue *self);

Get the client GDBusConnection which should be passed to the code under test as its connection to a bus. This will be `NULL` if `gt_dbus_queue_connect()` has not been called yet, or if `gt_dbus_queue_disconnect()` has been called.

#### Parameters

self | a GtDBusQueue |

#### Returns

the client’s bus connection.

[nullable][transfer none]

Since: 0.1.0

### gt_dbus_queue_connect ()

    gboolean
    gt_dbus_queue_connect (GtDBusQueue *self,
                           GError **error);

Create a private bus, mock D-Bus service, and a client GDBusConnection to be used by the code under test. Once this function has been called, the test harness may call `gt_dbus_queue_own_name()` and `gt_dbus_queue_export_object()` and then run the code under test.

This must be called from the thread which constructed the GtDBusQueue.

#### Parameters

self | a GtDBusQueue |
error | return location for a GError, or `NULL` |

#### Returns

`TRUE` on success, `FALSE` otherwise

Since: 0.1.0

### gt_dbus_queue_disconnect ()

    void
    gt_dbus_queue_disconnect (GtDBusQueue *self,
                              gboolean assert_queue_empty);

Disconnect the mock D-Bus service and client GDBusConnection, and shut down the private bus.

This must be called from the thread which constructed the GtDBusQueue.

#### Parameters

self | a GtDBusQueue |
assert_queue_empty | `TRUE` to assert that the server message queue is empty before disconnecting, `FALSE` to do nothing |

Since: 0.1.0

### gt_dbus_queue_own_name ()

    guint
    gt_dbus_queue_own_name (GtDBusQueue *self,
                            const gchar *name);

Make the mock D-Bus service acquire the given `name` on the private bus, so that code under test can address the mock service using `name` . This behaves similarly to `g_bus_own_name()`.

This may be called from any thread after `gt_dbus_queue_connect()` has been called.

#### Parameters

self | a GtDBusQueue |
name | the well-known D-Bus name to own |

#### Returns

ID for the name ownership, which may be passed to `gt_dbus_queue_unown_name()` to release it in future; guaranteed to be non-zero

Since: 0.1.0

### gt_dbus_queue_unown_name ()

    void
    gt_dbus_queue_unown_name (GtDBusQueue *self,
                              guint id);

Make the mock D-Bus service release a name on the private bus previously acquired using `gt_dbus_queue_own_name()`. This behaves similarly to `g_bus_unown_name()`.

This may be called from any thread after `gt_dbus_queue_connect()` has been called.

#### Parameters

self | a GtDBusQueue |
id | the name ID returned by `gt_dbus_queue_own_name()` |

Since: 0.1.0

### gt_dbus_queue_export_object ()

    guint
    gt_dbus_queue_export_object (GtDBusQueue *self,
                                 const gchar *object_path,
                                 GDBusInterfaceInfo *interface_info,
                                 GError **error);

Make the mock D-Bus service export an interface matching `interface_info` at the given `object_path` , so that code under test can call methods at that `object_path` . This behaves similarly to `g_dbus_connection_register_object()`.

This may be called from any thread after `gt_dbus_queue_connect()` has been called.

#### Parameters

self | a GtDBusQueue |
object_path | the path to export an object on |
interface_info | definition of the interface to export. | [transfer none]
error | return location for a GError, or `NULL` |

#### Returns

ID for the exported object, which may be passed to `gt_dbus_queue_unexport_object()` to release it in future; guaranteed to be non-zero

Since: 0.1.0

### gt_dbus_queue_unexport_object ()

    void
    gt_dbus_queue_unexport_object (GtDBusQueue *self,
                                   guint id);

Make the mock D-Bus service unexport an object on the private bus previously exported using `gt_dbus_queue_export_object()`. This behaves similarly to `g_dbus_connection_unregister_object()`.

This may be called from any thread after `gt_dbus_queue_connect()` has been called.

#### Parameters

self | a GtDBusQueue |
id | the name ID returned by `gt_dbus_queue_export_object()` |

Since: 0.1.0

### gt_dbus_queue_set_server_func ()

    void
    gt_dbus_queue_set_server_func (GtDBusQueue *self,
                                   GtDBusQueueServerFunc func,
                                   gpointer user_data);

Set a function to run in the server thread to handle incoming method calls. This is a requirement when testing code which makes synchronous function calls, as they will block the test thread’s main context until they return. This can also be used when testing asynchronous code, which allows reuse of the same mock service implementation when testing synchronous and asynchronous versions of the same code under test functionality.

`func` will be executed in the server thread, so must only call thread safe methods of the GtDBusQueue, and must use thread safe access to `user_data` if it’s used in any other threads.

#### Parameters

self | a GtDBusQueue |
func | a GtDBusQueueServerFunc to run in the server thread. | [not nullable]
user_data | data to pass to `func` |

Since: 0.1.0

### gt_dbus_queue_get_n_messages ()

    gsize
    gt_dbus_queue_get_n_messages (GtDBusQueue *self);

Get the number of messages waiting in the server queue to be popped by `gt_dbus_queue_pop_message()` and processed.

If asserting that the queue is empty, `gt_dbus_queue_assert_no_messages()` is more appropriate.

This may be called from any thread.

#### Parameters

self | a GtDBusQueue |

#### Returns

number of messages waiting to be popped and processed

Since: 0.1.0

### gt_dbus_queue_try_pop_message ()

    gboolean
    gt_dbus_queue_try_pop_message (GtDBusQueue *self,
                                   GDBusMethodInvocation **out_invocation);

Pop a message off the server’s message queue, if one is ready to be popped. Otherwise, immediately return `NULL`.

This may be called from any thread after `gt_dbus_queue_connect()` has been called.

#### Parameters

self | a GtDBusQueue |
out_invocation | return location for the popped GDBusMethodInvocation, which may be `NULL`; pass `NULL` to not receive the GDBusMethodInvocation. | [out][transfer full][optional][nullable]

#### Returns

`TRUE` if a message was popped (and returned in `out_invocation` if `out_invocation` was non-`NULL`), `FALSE` otherwise

Since: 0.1.0

### gt_dbus_queue_pop_message ()

    gboolean
    gt_dbus_queue_pop_message (GtDBusQueue *self,
                               GDBusMethodInvocation **out_invocation);

Pop a message off the server’s message queue, if one is ready to be popped. Otherwise, block indefinitely until one is.

This may be called from any thread after `gt_dbus_queue_connect()` has been called.

#### Parameters

self | a GtDBusQueue |
out_invocation | return location for the popped GDBusMethodInvocation, which may be `NULL`; pass `NULL` to not receive the GDBusMethodInvocation. | [out][transfer full][optional][nullable]

#### Returns

`TRUE` if a message was popped (and returned in `out_invocation` if `out_invocation` was non-`NULL`), `FALSE` if the pop timed out

Since: 0.1.0

### gt_dbus_queue_match_client_message ()

    gboolean
    gt_dbus_queue_match_client_message (GtDBusQueue *self,
                                        GDBusMethodInvocation *invocation,
                                        const gchar *expected_object_path,
                                        const gchar *expected_interface_name,
                                        const gchar *expected_method_name,
                                        const gchar *expected_parameters_string);

Check whether `invocation` matches the given expected object path, interface name, method name and (optionally) parameters, and was sent by the client connection of the GtDBusQueue.

This may be called from any thread after `gt_dbus_queue_connect()` has been called.

`expected_parameters_string` is optional, and will be matched against only if it is non-`NULL`. The other arguments are not optional. If non-`NULL`, `expected_parameters_string` will be parsed using `g_variant_new_parsed()`. It is a programmer error to provide a string which doesn’t parse correctly.

#### Parameters

self | a GtDBusQueue |
invocation | invocation to match against. | [transfer none]
expected_object_path | object path the invocation is expected to be calling |
expected_interface_name | interface name the invocation is expected to be calling |
expected_method_name | method name the invocation is expected to be calling |
expected_parameters_string | expected parameters for the invocation, or `NULL` to not match its parameters. | [nullable]

#### Returns

`TRUE` if `invocation` matches the expected arguments, `FALSE` otherwise

Since: 0.1.0

### gt_dbus_queue_format_message ()

    gchar *
    gt_dbus_queue_format_message (GDBusMethodInvocation *invocation);

Format a GDBusMethodInvocation in a human readable way. This format is not intended to be stable or machine parsable.

#### Parameters

invocation | a GDBusMethodInvocation to format |

#### Returns

human readable version of `invocation` .

[transfer full]

Since: 0.1.0

### gt_dbus_queue_format_messages ()

    gchar *
    gt_dbus_queue_format_messages (GtDBusQueue *self);

Format all the messages currently pending in the mock service’s message queue in a human readable way, with the head of the queue first in the formatted list. This format is not intended to be stable or machine parsable.

If no messages are in the queue, an empty string will be returned.

#### Parameters

self | a GtDBusQueue |

#### Returns

human readable version of the pending message queue.

[transfer full]

Since: 0.1.0

### gt_dbus_queue_assert_no_messages()

    #define             gt_dbus_queue_assert_no_messages(self)

Assert that there are no messages currently in the mock service’s message queue.

If there are, an assertion fails and some debug output is printed.

#### Parameters

self | a GtDBusQueue |

Since: 0.1.0

### gt_dbus_queue_assert_pop_message()

    #define             gt_dbus_queue_assert_pop_message(self, expected_object_path, expected_interface_name, expected_method_name, parameters_format, ...)

Assert that a message can be popped off the mock service’s message queue (using `gt_dbus_queue_pop_message()`, which will block) and that it is a method call from the GtDBusQueue’s client connection to the mock service, calling `expected_method_name` on `expected_interface_name` at `expected_object_path` (as determined using `gt_dbus_queue_match_client_message()` with a `NULL` parameters argument). The parameters in the method call will be returned in the return locations given in the varargs, according to the `parameters_format` , using `g_variant_get_va()`.

If a timeout occurs when popping a message, or if the popped message doesn’t match the expected object path, interface name or method name, an assertion fails and some debug output is printed.

#### Parameters

self | a GtDBusQueue |
expected_object_path | object path the invocation is expected to be calling |
expected_interface_name | interface name the invocation is expected to be calling |
expected_method_name | method name the invocation is expected to be calling |
parameters_format | g_variant_get() format string to extract the parameters from the popped GDBusMethodInvocation into the return locations provided in @... |
... | return locations for the parameter placeholders given in `parameters_format` |

#### Returns

the popped GDBusMethodInvocation.

[transfer full]

Since: 0.1.0

## Types and Values

### GtDBusQueue

    typedef struct _GtDBusQueue GtDBusQueue;

An object which allows a D-Bus service to be mocked and implemented to return a variety of results from method calls with a high degree of flexibility. The mock service is driven from within the same process (and potentially the same GMainContext) as the code under test.

This allows a D-Bus service to be mocked without needing to generate and implement a full implementation of its interfaces.

Since: 0.1.0