Commit ac7de376 authored by Philip Withnall's avatar Philip Withnall

Merge branch 'dbus-queue' into 'master'

lib: Add GtDBusQueue for creating mock D-Bus services

See merge request !4
parents d47f30b1 6d69790b
Pipeline #46406 passed with stages
in 14 minutes and 22 seconds
This diff is collapsed.
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright © 2018 Endless Mobile, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Authors:
* - Philip Withnall <withnall@endlessm.com>
*/
#pragma once
#include <gio/gio.h>
#include <glib.h>
#include <glib-object.h>
G_BEGIN_DECLS
typedef struct _GtDBusQueue GtDBusQueue;
GtDBusQueue *gt_dbus_queue_new (void);
void gt_dbus_queue_free (GtDBusQueue *self);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (GtDBusQueue, gt_dbus_queue_free)
GDBusConnection *gt_dbus_queue_get_client_connection (GtDBusQueue *self);
gboolean gt_dbus_queue_connect (GtDBusQueue *self,
GError **error);
void gt_dbus_queue_disconnect (GtDBusQueue *self,
gboolean assert_queue_empty);
guint gt_dbus_queue_own_name (GtDBusQueue *self,
const gchar *name);
void gt_dbus_queue_unown_name (GtDBusQueue *self,
guint id);
guint gt_dbus_queue_export_object (GtDBusQueue *self,
const gchar *object_path,
GDBusInterfaceInfo *interface_info,
GError **error);
void gt_dbus_queue_unexport_object (GtDBusQueue *self,
guint id);
typedef void (*GtDBusQueueServerFunc) (GtDBusQueue *queue,
gpointer user_data);
void gt_dbus_queue_set_server_func (GtDBusQueue *self,
GtDBusQueueServerFunc func,
gpointer user_data);
gsize gt_dbus_queue_get_n_messages (GtDBusQueue *self);
gboolean gt_dbus_queue_try_pop_message (GtDBusQueue *self,
GDBusMethodInvocation **out_invocation);
gboolean gt_dbus_queue_pop_message (GtDBusQueue *self,
GDBusMethodInvocation **out_invocation);
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);
gchar *gt_dbus_queue_format_message (GDBusMethodInvocation *invocation);
gchar *gt_dbus_queue_format_messages (GtDBusQueue *self);
/**
* gt_dbus_queue_assert_no_messages:
* @self: a #GtDBusQueue
*
* 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_no_messages(self) \
G_STMT_START { \
if (gt_dbus_queue_get_n_messages (self) > 0) \
{ \
g_autofree gchar *anm_list = gt_dbus_queue_format_messages (self); \
g_autofree gchar *anm_message = \
g_strdup_printf ("Expected no messages, but saw %" G_GSIZE_FORMAT ":\n%s", \
gt_dbus_queue_get_n_messages (self), \
anm_list); \
g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
anm_message); \
} \
} G_STMT_END
/**
* gt_dbus_queue_assert_pop_message:
* @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
*
* 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.
*
* Returns: (transfer full): the popped #GDBusMethodInvocation
* Since: 0.1.0
*/
#define gt_dbus_queue_assert_pop_message(self, expected_object_path, expected_interface_name, expected_method_name, parameters_format, ...) \
gt_dbus_queue_assert_pop_message_impl (self, G_LOG_DOMAIN, __FILE__, __LINE__, \
G_STRFUNC, expected_object_path, \
expected_interface_name, \
expected_method_name, \
parameters_format, __VA_ARGS__)
/* Private implementations of the assertion functions above. */
/*< private >*/
GDBusMethodInvocation *gt_dbus_queue_assert_pop_message_impl (GtDBusQueue *self,
const gchar *macro_log_domain,
const gchar *macro_file,
gint macro_line,
const gchar *macro_function,
const gchar *object_path,
const gchar *interface_name,
const gchar *method_name,
const gchar *parameters_format,
...);
G_END_DECLS
......@@ -14,6 +14,7 @@
<reference id="reference">
<title>API Reference</title>
<xi:include href="xml/dbus-queue.xml" />
<xi:include href="xml/signal-logger.xml" />
</reference>
......
<SECTION>
<TITLE>GtDBusQueue</TITLE>
<FILE>dbus-queue</FILE>
<SUBSECTION>
GtDBusQueue
GtDBusQueueServerFunc
gt_dbus_queue_new
gt_dbus_queue_free
gt_dbus_queue_get_client_connection
gt_dbus_queue_connect
gt_dbus_queue_disconnect
gt_dbus_queue_own_name
gt_dbus_queue_unown_name
gt_dbus_queue_export_object
gt_dbus_queue_unexport_object
gt_dbus_queue_set_server_func
gt_dbus_queue_get_n_messages
gt_dbus_queue_try_pop_message
gt_dbus_queue_pop_message
gt_dbus_queue_match_client_message
gt_dbus_queue_format_message
gt_dbus_queue_format_messages
gt_dbus_queue_assert_no_messages
gt_dbus_queue_assert_pop_message
<SUBSECTION Private>
gt_dbus_queue_assert_pop_message_impl
</SECTION>
<SECTION>
<TITLE>GtSignalLogger</TITLE>
<FILE>signal-logger</FILE>
......
libglib_testing_api_version = '0'
libglib_testing_api_name = 'glib-testing-' + libglib_testing_api_version
libglib_testing_sources = [
'dbus-queue.c',
'signal-logger.c',
]
libglib_testing_headers = [
'dbus-queue.h',
'signal-logger.h',
]
......
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright © 2018 Endless Mobile, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Authors:
* - Philip Withnall <withnall@endlessm.com>
*/
#include <gio/gio.h>
#include <glib.h>
#include <libglib-testing/dbus-queue.h>
#include <locale.h>
#include "test-service-iface.h"
/* Test that creating and destroying a D-Bus queue works. A basic smoketest. */
static void
test_dbus_queue_construction (void)
{
g_autoptr(GtDBusQueue) queue = NULL;
queue = gt_dbus_queue_new ();
/* Call a method to avoid warnings about unused variables. */
g_assert_cmpuint (gt_dbus_queue_get_n_messages (queue), ==, 0);
}
/* Fixture for tests which interact with the com.example.Test service over
* D-Bus.
*
* It exports one object (with ID 123) and an manager object. The method return
* values from ID 123 are up to the test in question.
*/
typedef struct
{
GtDBusQueue *queue; /* (owned) */
guint valid_id;
} 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_id = 123; /* 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, "com.example.Test");
object_path = g_strdup_printf ("/com/example/Test/Object%u", fixture->valid_id);
gt_dbus_queue_export_object (fixture->queue,
object_path,
(GDBusInterfaceInfo *) &object_interface_info,
&local_error);
g_assert_no_error (local_error);
gt_dbus_queue_export_object (fixture->queue,
"/com/example/Test",
(GDBusInterfaceInfo *) &manager_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 making two calls in series to a mock D-Bus service works. The
* @test_data is a boolean value indicating whether to do the calls
* synchronously (%FALSE) or asynchronously (%TRUE).
*
* The mock D-Bus replies are generated in series_server_cb(), which is
* used for both synchronous and asynchronous calls. */
static void series_server_cb (GtDBusQueue *queue,
gpointer user_data);
static void
test_dbus_queue_series (BusFixture *fixture,
gconstpointer test_data)
{
g_autoptr(GVariant) reply = NULL;
g_autoptr(GError) local_error = NULL;
gboolean test_async = GPOINTER_TO_UINT (test_data);
GDBusConnection *client_connection = gt_dbus_queue_get_client_connection (fixture->queue);
g_autofree gchar *object_path = NULL;
g_autoptr(GVariant) properties = NULL;
const gchar *some_str;
guint some_int;
gt_dbus_queue_set_server_func (fixture->queue, series_server_cb, fixture);
if (test_async)
{
g_autoptr(GAsyncResult) result = NULL;
g_dbus_connection_call (client_connection,
"com.example.Test",
"/com/example/Test",
"com.example.Test.Manager",
"GetObjectPath",
g_variant_new ("(u)", 123),
G_VARIANT_TYPE ("(o)"),
G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout (ms) */
NULL, /* cancellable */
async_result_cb,
&result);
while (result == NULL)
g_main_context_iteration (NULL, TRUE);
reply = g_dbus_connection_call_finish (client_connection, result, &local_error);
}
else
{
reply = g_dbus_connection_call_sync (client_connection,
"com.example.Test",
"/com/example/Test",
"com.example.Test.Manager",
"GetObjectPath",
g_variant_new ("(u)", 123),
G_VARIANT_TYPE ("(o)"),
G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout (ms) */
NULL, /* cancellable */
&local_error);
}
g_assert_no_error (local_error);
g_assert_nonnull (reply);
g_variant_get (reply, "(o)", &object_path);
g_clear_pointer (&reply, g_variant_unref);
/* Get and check the object properties. */
if (test_async)
{
g_autoptr(GAsyncResult) result = NULL;
g_dbus_connection_call (client_connection,
"com.example.Test",
object_path,
"org.freedesktop.DBus.Properties",
"GetAll",
g_variant_new ("(s)", "com.example.Test.Object"),
G_VARIANT_TYPE ("(a{sv})"),
G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout (ms) */
NULL, /* cancellable */
async_result_cb,
&result);
while (result == NULL)
g_main_context_iteration (NULL, TRUE);
reply = g_dbus_connection_call_finish (client_connection, result, &local_error);
}
else
{
reply = g_dbus_connection_call_sync (client_connection,
"com.example.Test",
object_path,
"org.freedesktop.DBus.Properties",
"GetAll",
g_variant_new ("(s)", "com.example.Test.Object"),
G_VARIANT_TYPE ("(a{sv})"),
G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout (ms) */
NULL, /* cancellable */
&local_error);
}
g_assert_no_error (local_error);
g_assert_nonnull (reply);
properties = g_variant_get_child_value (reply, 0);
g_variant_lookup (properties, "some-str", "&s", &some_str);
g_assert_cmpstr (some_str, ==, "hello");
g_variant_lookup (properties, "some-int", "u", &some_int);
g_assert_cmpuint (some_int, ==, 11);
}
/* This is run in a worker thread. */
static void
series_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 GetObjectPath() call. */
guint object_id;
invocation1 =
gt_dbus_queue_assert_pop_message (queue,
"/com/example/Test",
"com.example.Test.Manager",
"GetObjectPath", "(u)", &object_id);
g_assert_cmpint (object_id, ==, fixture->valid_id);
object_path = g_strdup_printf ("/com/example/Test/Object%u", object_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 values for
* the given object. */
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.example.Test.Object");
const gchar *reply2 =
"({"
"'some-str': <'hello'>,"
"'some-int': <@u 11>"
"},)";
g_dbus_method_invocation_return_value (invocation2, g_variant_new_parsed (reply2));
}
int
main (int argc,
char *argv[])
{
setlocale (LC_ALL, "");
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/dbus-queue/construction",
test_dbus_queue_construction);
g_test_add ("/dbus-queue/series-async", BusFixture, GUINT_TO_POINTER (TRUE),
bus_set_up, test_dbus_queue_series, bus_tear_down);
g_test_add ("/dbus-queue/series-sync", BusFixture, GUINT_TO_POINTER (FALSE),
bus_set_up, test_dbus_queue_series, bus_tear_down);
return g_test_run ();
}
......@@ -11,6 +11,7 @@ envs = test_env + [
]
test_programs = [
['dbus-queue', ['test-service-iface.h'], deps],
['signal-logger', [], deps],
]
......
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright © 2018 Endless Mobile, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Authors:
* - Philip Withnall <withnall@endlessm.com>
*/
#pragma once
#include <gio/gio.h>
#include <glib.h>
#include <glib-object.h>
G_BEGIN_DECLS
/* Static definition of some test D-Bus interfaces.
* FIXME: Once we can depend on a new enough version of GLib, generate this
* from introspection XML using `gdbus-codegen --interface-info-{header,body}`. */
static const GDBusPropertyInfo object_property_some_string =
{
.ref_count = -1, /* static */
.name = "some-string",
.signature = "s",
.flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
.annotations = NULL,
};
static const GDBusPropertyInfo object_property_some_int =
{
.ref_count = -1, /* static */
.name = "some-int",
.signature = "u",
.flags = G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
.annotations = NULL,
};
static const GDBusPropertyInfo *object_properties[] =
{
(GDBusPropertyInfo *) &object_property_some_string,
(GDBusPropertyInfo *) &object_property_some_int,
NULL,
};
static const GDBusInterfaceInfo object_interface_info =
{
.ref_count = -1, /* static */
.name = (gchar *) "com.example.Test.Object",
.methods = NULL,
.signals = NULL,
.properties = (GDBusPropertyInfo **) &object_properties,
.annotations = NULL,
};
static const GDBusArgInfo manager_method_get_object_path_arg_object_id =
{
.ref_count = -1, /* static */
.name = (gchar *) "ObjectId",
.signature = (gchar *) "u",
.annotations = NULL,
};
static const GDBusArgInfo manager_method_get_object_path_arg_object_path =
{
.ref_count = -1, /* static */
.name = (gchar *) "ObjectPath",
.signature = (gchar *) "o",
.annotations = NULL,
};
static const GDBusArgInfo *manager_method_get_object_path_in_args[] =
{
(GDBusArgInfo *) &manager_method_get_object_path_arg_object_id,
NULL,
};
static const GDBusArgInfo *manager_method_get_object_path_out_args[] =
{
(GDBusArgInfo *) &manager_method_get_object_path_arg_object_path,
NULL,
};
static const GDBusMethodInfo manager_method_get_object_path =
{
.ref_count = -1, /* static */
.name = (gchar *) "GetObjectPath",
.in_args = (GDBusArgInfo **) &manager_method_get_object_path_in_args,
.out_args = (GDBusArgInfo **) &manager_method_get_object_path_out_args,
.annotations = NULL,
};
static const GDBusMethodInfo *manager_methods[] =
{
(GDBusMethodInfo *) &manager_method_get_object_path,
NULL,
};
static const GDBusInterfaceInfo manager_interface_info =
{
.ref_count = -1, /* static */
.name = (gchar *) "com.example.Test.Manager",
.methods = (GDBusMethodInfo **) &manager_methods,
.signals = NULL,
.properties = NULL,
.annotations = NULL,
};
G_END_DECLS
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment