Top |
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() |
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.
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)); } |
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.
Since: 0.1.0
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()
.
Since: 0.1.0
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.
Since: 0.1.0
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.
Since: 0.1.0
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.
Since: 0.1.0
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.
self |
||
assert_queue_empty |
|
Since: 0.1.0
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.
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
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.
Since: 0.1.0
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.
self |
||
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 |
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
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.
Since: 0.1.0
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.
self |
||
func |
a GtDBusQueueServerFunc to run in the server thread. |
[not nullable] |
user_data |
data to pass to |
Since: 0.1.0
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.
Since: 0.1.0
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.
self |
||
out_invocation |
return location
for the popped GDBusMethodInvocation, which may be |
[out][transfer full][optional][nullable] |
TRUE
if a message was popped (and returned in out_invocation
if
out_invocation
was non-NULL
), FALSE
otherwise
Since: 0.1.0
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.
self |
||
out_invocation |
return location
for the popped GDBusMethodInvocation, which may be |
[out][transfer full][optional][nullable] |
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
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.
self |
||
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 |
[nullable] |
Since: 0.1.0
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.
Since: 0.1.0
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.
Since: 0.1.0
#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.
Since: 0.1.0
#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.
self |
||
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 |
Since: 0.1.0
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