From cd13da2fa715d280d97c03bd6baa93f38447b1f3 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Tue, 17 Apr 2012 15:30:23 +0100 Subject: [PATCH 2/3] dbus_g_connection_call_async: add and test This is basically the same API as g_dbus_connection_call_async, but implemented in terms of libdbus. This means we can use a DBusGConnection in telepathy-glib to make method calls that look suspiciously like a GDBus call, reducing the size of the "flag day" commit we'll have to make when we switch from dbus-glib to GDBus. To support this, make GIO a public dependency. Signed-off-by: Simon McVittie --- .gitignore | 1 + dbus-glib-1-uninstalled.pc.in | 2 +- dbus-glib-1.pc.in | 2 +- dbus/Makefile.am | 1 + dbus/dbus-gdbus.c | 198 +++++++++++++++++++++++++++++++ dbus/dbus-glib.h | 16 +++ test/core/Makefile.am | 10 ++ test/core/not-quite-gdbus.c | 261 +++++++++++++++++++++++++++++++++++++++++ test/core/run-test.sh | 1 + 9 files changed, 490 insertions(+), 2 deletions(-) create mode 100644 dbus/dbus-gdbus.c create mode 100644 test/core/not-quite-gdbus.c diff --git a/.gitignore b/.gitignore index 7495223..dee06bf 100644 --- a/.gitignore +++ b/.gitignore @@ -108,6 +108,7 @@ test/core/test-service-glib-glue.h /test/core/test-service-glib-subclass-glue.h test/core/test-thread-client test/core/test-thread-server +/test/core/test-not-quite-gdbus /test/core/test-registrations /test/core/test-variant-recursion test/data/valid-service-files/debug-echo.service diff --git a/dbus-glib-1-uninstalled.pc.in b/dbus-glib-1-uninstalled.pc.in index 150db5f..d5061d3 100644 --- a/dbus-glib-1-uninstalled.pc.in +++ b/dbus-glib-1-uninstalled.pc.in @@ -6,7 +6,7 @@ includedir=@includedir@ Name: dbus-glib Description: GLib integration for the free desktop message bus Version: @VERSION@ -Requires: dbus-1 glib-2.0 gobject-2.0 +Requires: dbus-1 glib-2.0 gobject-2.0 gio-2.0 Libs: ${pc_top_builddir}/${pcfiledir}/dbus/libdbus-glib-1.la Cflags: -I${pc_top_builddir}/${pcfiledir} diff --git a/dbus-glib-1.pc.in b/dbus-glib-1.pc.in index 599901a..c86f33b 100644 --- a/dbus-glib-1.pc.in +++ b/dbus-glib-1.pc.in @@ -6,7 +6,7 @@ includedir=@includedir@ Name: dbus-glib Description: GLib integration for the free desktop message bus Version: @VERSION@ -Requires: dbus-1 glib-2.0 gobject-2.0 +Requires: dbus-1 glib-2.0 gobject-2.0 gio-2.0 Libs: -L${libdir} -ldbus-glib-1 Cflags: -I${includedir}/dbus-1.0 diff --git a/dbus/Makefile.am b/dbus/Makefile.am index 2c10c1c..103852e 100644 --- a/dbus/Makefile.am +++ b/dbus/Makefile.am @@ -25,6 +25,7 @@ DBUS_GLIB_INTERNALS = \ dbus-gvalue-utils.h libdbus_glib_1_la_SOURCES = \ + dbus-gdbus.c \ dbus-glib.c \ dbus-gmain.c \ dbus-gmarshal.c \ diff --git a/dbus/dbus-gdbus.c b/dbus/dbus-gdbus.c new file mode 100644 index 0000000..7836849 --- /dev/null +++ b/dbus/dbus-gdbus.c @@ -0,0 +1,198 @@ +/* dbus-gdbus - make dbus-glib behave enough like GDBus to facilitate porting + * + * Copyright © 2012 Collabora Ltd. + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include "config.h" + +#include + +static void oom (void) G_GNUC_NORETURN; +static void +oom (void) +{ + g_error ("no memory"); +} + +static GQuark +expected_type_quark (void) +{ + static GQuark q; + + if (G_UNLIKELY (q == 0)) + q = g_quark_from_static_string ("dbus-glib-expected-reply-type"); + + return q; +} + +static void +pc_notify (DBusPendingCall *pc, + gpointer data) +{ + GSimpleAsyncResult *simple = data; + + if (pc != NULL) + { + g_simple_async_result_set_op_res_gpointer (simple, + dbus_pending_call_steal_reply (pc), + (GDestroyNotify) dbus_message_unref); + + dbus_pending_call_unref (pc); + } + + /* We have to do the real work in an idle, so we don't break re-entrant + * calls (the dbus-glib event source isn't re-entrant) */ + g_simple_async_result_complete_in_idle (simple); + + /* simple is unreffed by the pending call's destructor */ +} + +void +dbus_g_connection_call_async (DBusGConnection *self, + const gchar *bus_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + const GVariantType *reply_type, + GDBusCallFlags flags, + gint timeout_msec, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + DBusConnection *libdbus = dbus_g_connection_get_connection (self); + DBusMessage *message; + DBusPendingCall *pc = NULL; + GSimpleAsyncResult *simple; + + g_return_if_fail (bus_name == NULL || g_dbus_is_name (bus_name)); + g_return_if_fail (object_path != NULL); + g_return_if_fail (g_variant_is_object_path (object_path)); + g_return_if_fail (interface_name != NULL); + g_return_if_fail (g_dbus_is_interface_name (interface_name)); + g_return_if_fail (method_name != NULL); + g_return_if_fail (g_dbus_is_member_name (method_name)); + g_return_if_fail (timeout_msec >= -1); + g_return_if_fail (parameters == NULL || + g_variant_is_of_type (parameters, G_VARIANT_TYPE_TUPLE)); + + /* only one flag is supported */ + g_return_if_fail (flags == (flags & G_DBUS_CALL_FLAGS_NO_AUTO_START)); + + message = dbus_message_new_method_call (bus_name, object_path, + interface_name, method_name); + + if (message == NULL) + oom (); + + if (flags & G_DBUS_CALL_FLAGS_NO_AUTO_START) + dbus_message_set_auto_start (message, FALSE); + + if (callback == NULL) + { + dbus_message_set_no_reply (message, TRUE); + + if (!dbus_connection_send (libdbus, message, NULL)) + oom (); + + dbus_message_unref (message); + return; + } + + simple = g_simple_async_result_new (NULL, + callback, + user_data, + dbus_g_connection_call_async); + g_simple_async_result_set_check_cancellable (simple, cancellable); + + if (reply_type != NULL) + g_object_set_qdata_full ((GObject *) simple, expected_type_quark (), + g_variant_type_copy (reply_type), + (GDestroyNotify) g_variant_type_free); + + if (!dbus_connection_send_with_reply (libdbus, message, &pc, + timeout_msec)) + oom (); + + dbus_message_unref (message); + + if (pc == NULL || dbus_pending_call_get_completed (pc)) + { + pc_notify (pc, simple); + } + else if (!dbus_pending_call_set_notify (pc, pc_notify, simple, + g_object_unref)) + { + oom (); + } +} + +GVariant * +dbus_g_connection_call_finish (GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + DBusMessage *reply; + + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, + NULL, dbus_g_connection_call_async), NULL); + + reply = g_simple_async_result_get_op_res_gpointer (simple); + + if (reply == NULL) + { + g_set_error_literal (error, DBUS_GERROR, DBUS_GERROR_DISCONNECTED, + "DBusConnection disconnected"); + return NULL; + } + else if (dbus_message_get_type (reply) == DBUS_MESSAGE_TYPE_METHOD_RETURN) + { + GVariant *tuple = dbus_g_message_read_variants (reply, error); + GVariantType *expected; + + expected = g_object_get_qdata ((GObject *) simple, + expected_type_quark ()); + + if (expected != NULL && !g_variant_is_of_type (tuple, expected)) + { + gchar *str = g_variant_type_dup_string (expected); + + g_set_error (error, DBUS_GERROR, DBUS_GERROR_INVALID_ARGS, + "Method returned type \"%s\", we expected \"%s\"", + g_variant_get_type_string (tuple), str); + g_free (str); + g_variant_unref (tuple); + tuple = NULL; + } + + return tuple; + } + else + { + DBusError dbus_error = DBUS_ERROR_INIT; + + if (dbus_set_error_from_message (&dbus_error, reply)) + { + dbus_set_g_error (error, &dbus_error); + dbus_error_free (&dbus_error); + } + else + { + g_set_error_literal (error, DBUS_GERROR, DBUS_GERROR_INVALID_ARGS, + "Unexpected message type from method call"); + } + + return NULL; + } +} diff --git a/dbus/dbus-glib.h b/dbus/dbus-glib.h index 9a2076f..f4691f2 100644 --- a/dbus/dbus-glib.h +++ b/dbus/dbus-glib.h @@ -26,6 +26,7 @@ #include #include +#include G_BEGIN_DECLS @@ -332,6 +333,21 @@ typedef struct { gpointer userdata; } DBusGAsyncData; +void dbus_g_connection_call_async (DBusGConnection *self, + const gchar *bus_name, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + const GVariantType *reply_type, + GDBusCallFlags flags, + gint timeout_msec, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GVariant *dbus_g_connection_call_finish (GAsyncResult *result, + GError **error); + #undef DBUS_INSIDE_DBUS_GLIB_H #include diff --git a/test/core/Makefile.am b/test/core/Makefile.am index e1d3502..fe01525 100644 --- a/test/core/Makefile.am +++ b/test/core/Makefile.am @@ -55,6 +55,7 @@ noinst_PROGRAMS = \ peer-client \ test-types \ test-30574 \ + test-not-quite-gdbus \ test-proxy-peer \ test-registrations \ test-variant-recursion \ @@ -63,6 +64,15 @@ noinst_PROGRAMS = \ test_30574_SOURCES = \ 30574.c +test_not_quite_gdbus_SOURCES = \ + my-object.c \ + my-object.h \ + my-object-subclass.c \ + my-object-subclass.h \ + my-object-marshal.c \ + not-quite-gdbus.c \ + $(NULL) + test_proxy_peer_SOURCES = \ my-object-marshal.c \ my-object.c \ diff --git a/test/core/not-quite-gdbus.c b/test/core/not-quite-gdbus.c new file mode 100644 index 0000000..573fbc0 --- /dev/null +++ b/test/core/not-quite-gdbus.c @@ -0,0 +1,261 @@ +/* Regression test for imitating GDBus. + * + * Copyright © 2008-2012 Collabora Ltd. + * Copyright © 2008-2011 Nokia Corporation + * + * In preparation for dbus-glib relicensing (if it ever happens), this file is + * licensed under (at your option) either the AFL v2.1, the GPL v2 or later, + * or an MIT/X11-style license: + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include + +#include +#include + +#include "my-object.h" + +GMainLoop *loop = NULL; + +typedef struct { + DBusError dbus_error; + DBusGConnection *bus; + DBusConnection *libdbus; + const char *unique_name; + GObject *object; +} Fixture; + +/* tp_* functions from telepathy-glib, under a permissive license: + * + * Copying and distribution of [the file they came from], with or without + * modification, are permitted in any medium without royalty provided the + * copyright notice and this notice are preserved. */ + +/* A GAsyncReadyCallback whose user_data is a GAsyncResult **. It writes a + * reference to the result into that pointer. */ +static void +tp_tests_result_ready_cb (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + GAsyncResult **result = user_data; + + *result = g_object_ref (res); +} + +/* Run until *result contains a result. Intended to be used with a pending + * async call that uses tp_tests_result_ready_cb. */ +static void +tp_tests_run_until_result (GAsyncResult **result) +{ + /* not synchronous */ + g_assert (*result == NULL); + + while (*result == NULL) + g_main_context_iteration (NULL, TRUE); +} + +#define IFACE "org.freedesktop.DBus.GLib.Tests.MyObject" + +static void +setup (Fixture *f, + gconstpointer path_to_use) +{ + dbus_error_init (&f->dbus_error); + + f->bus = dbus_g_bus_get_private (DBUS_BUS_SESSION, NULL, NULL); + g_assert (f->bus != NULL); + + f->object = g_object_new (MY_TYPE_OBJECT, NULL); + g_assert (MY_IS_OBJECT (f->object)); + + f->libdbus = dbus_g_connection_get_connection (f->bus); + f->unique_name = dbus_bus_get_unique_name (f->libdbus); + + dbus_g_connection_register_g_object (f->bus, "/foo", f->object); + g_assert (dbus_g_connection_lookup_g_object (f->bus, "/foo") == + f->object); +} + +static void +teardown (Fixture *f, + gconstpointer test_data G_GNUC_UNUSED) +{ + /* we close the connection before releasing the object, to test fd.o #5688 + * in test_lookup() */ + if (f->bus != NULL) + { + dbus_connection_close (dbus_g_connection_get_connection (f->bus)); + dbus_g_connection_unref (f->bus); + } + + if (f->object != NULL) + { + g_object_unref (f->object); + } + + /* This is safe to call on an initialized-but-unset DBusError, a bit like + * g_clear_error */ + dbus_error_free (&f->dbus_error); +} + +static void +test_success (Fixture *f, + gconstpointer test_data G_GNUC_UNUSED) +{ + GAsyncResult *result = NULL; + GError *error = NULL; + GVariant *v; + + /* called for its side-effect: increment val from 0 to 1 */ + dbus_g_connection_call_async (f->bus, f->unique_name, "/foo", IFACE, + "IncrementVal", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, + -1, NULL, NULL, NULL); + + /* called for its reply */ + dbus_g_connection_call_async (f->bus, f->unique_name, "/foo", IFACE, + "GetVal", NULL, G_VARIANT_TYPE ("(u)"), G_DBUS_CALL_FLAGS_NONE, + -1, NULL, tp_tests_result_ready_cb, &result); + tp_tests_run_until_result (&result); + + v = dbus_g_connection_call_finish (result, &error); + g_assert_no_error (error); + g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE ("(u)"))); + g_assert_cmpuint (g_variant_get_uint32 (g_variant_get_child_value (v, 0)), + ==, 1); + g_variant_unref (v); +} + +static void +test_failure (Fixture *f, + gconstpointer test_data G_GNUC_UNUSED) +{ + GAsyncResult *result = NULL; + GError *error = NULL; + GVariant *v; + + my_object_save_error ((MyObject *) f->object, G_IO_ERROR, G_IO_ERROR_BUSY, + "No."); + + dbus_g_connection_call_async (f->bus, f->unique_name, "/foo", IFACE, + "ThrowError", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, + -1, NULL, tp_tests_result_ready_cb, &result); + tp_tests_run_until_result (&result); + + v = dbus_g_connection_call_finish (result, &error); + g_assert_error (error, DBUS_GERROR, DBUS_GERROR_REMOTE_EXCEPTION); + g_assert (v == NULL); +} + +static void +test_cancel (Fixture *f, + gconstpointer test_data G_GNUC_UNUSED) +{ + GAsyncResult *result = NULL; + GError *error = NULL; + GVariant *v; + GCancellable *cancellable = g_cancellable_new (); + + dbus_g_connection_call_async (f->bus, f->unique_name, "/foo", IFACE, + "ThrowError", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, + -1, cancellable, tp_tests_result_ready_cb, &result); + g_cancellable_cancel (cancellable); + + tp_tests_run_until_result (&result); + + v = dbus_g_connection_call_finish (result, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + g_assert (v == NULL); +} + +static void +test_disconnect (Fixture *f, + gconstpointer test_data G_GNUC_UNUSED) +{ + GAsyncResult *result = NULL; + GError *error = NULL; + GVariant *v; + + dbus_connection_close (f->libdbus); + + dbus_g_connection_call_async (f->bus, f->unique_name, "/foo", IFACE, + "ThrowError", NULL, NULL, G_DBUS_CALL_FLAGS_NONE, + -1, NULL, tp_tests_result_ready_cb, &result); + + tp_tests_run_until_result (&result); + + v = dbus_g_connection_call_finish (result, &error); + g_assert_error (error, DBUS_GERROR, DBUS_GERROR_DISCONNECTED); + g_assert (v == NULL); +} + +static void +test_mismatch (Fixture *f, + gconstpointer test_data G_GNUC_UNUSED) +{ + GAsyncResult *result = NULL; + GError *error = NULL; + GVariant *v; + + /* deliberately mismatched: it will return (u) but we ask for (s) */ + dbus_g_connection_call_async (f->bus, f->unique_name, "/foo", IFACE, + "GetVal", NULL, G_VARIANT_TYPE ("(s)"), G_DBUS_CALL_FLAGS_NONE, + -1, NULL, tp_tests_result_ready_cb, &result); + tp_tests_run_until_result (&result); + + v = dbus_g_connection_call_finish (result, &error); + g_assert_error (error, DBUS_GERROR, DBUS_GERROR_INVALID_ARGS); + g_assert (v == NULL); +} + +int +main (int argc, char **argv) +{ + loop = g_main_loop_new (NULL, FALSE); + + g_type_init (); + g_log_set_always_fatal (G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL); + dbus_g_type_specialized_init (); + g_test_bug_base ("https://bugs.freedesktop.org/show_bug.cgi?id="); + g_test_init (&argc, &argv, NULL); + + g_test_add ("/not-quite-gdbus/success", Fixture, NULL, + setup, test_success, teardown); + g_test_add ("/not-quite-gdbus/cancel", Fixture, NULL, + setup, test_cancel, teardown); + g_test_add ("/not-quite-gdbus/disconnect", Fixture, NULL, + setup, test_disconnect, teardown); + g_test_add ("/not-quite-gdbus/failure", Fixture, NULL, + setup, test_failure, teardown); + g_test_add ("/not-quite-gdbus/mismatch", Fixture, NULL, + setup, test_mismatch, teardown); + + return g_test_run (); +} diff --git a/test/core/run-test.sh b/test/core/run-test.sh index b4c2fbc..37ea3ae 100755 --- a/test/core/run-test.sh +++ b/test/core/run-test.sh @@ -50,4 +50,5 @@ else ${DBUS_TOP_BUILDDIR}/libtool --mode=execute $DEBUG $DBUS_TOP_BUILDDIR/test/core/test-gvariant || die "test-gvariant failed" ${DBUS_TOP_BUILDDIR}/libtool --mode=execute $DEBUG $DBUS_TOP_BUILDDIR/test/core/test-30574 || die "test-30574 failed" ${DBUS_TOP_BUILDDIR}/libtool --mode=execute $DEBUG $DBUS_TOP_BUILDDIR/test/core/test-error-mapping || die "test-error-mapping failed" + ${DBUS_TOP_BUILDDIR}/libtool --mode=execute $DEBUG $DBUS_TOP_BUILDDIR/test/core/test-not-quite-gdbus || die "test-not-quite-gdbus failed" fi -- 1.7.10