glib/gio/tests/gdbus-method-invocation.c
Simon McVittie 7c609f8142 gdbus: Use symbolic constants for interfaces from dbus-specification
Most D-Bus interfaces are domain-specific, but these interfaces from the
D-Bus Specification are intended to be commonly used in any context for
which they are found to be appropriate.

Most of these use `gdbusprivate.h`. One exception is that
`gio/tests/gdbus-example-*` redefine the constants locally: due to these
files' dual role as part of the unit tests and as sample code, it seems
desirable to ensure that they can still be compiled outside GLib.

Signed-off-by: Simon McVittie <smcv@collabora.com>
2024-05-16 22:52:23 +01:00

410 lines
14 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* GIO - GLib Input, Output and Streaming Library
*
* Copyright © 2022 Endless OS Foundation, LLC
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* 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, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include <string.h>
#include <unistd.h>
#include "gdbusprivate.h"
#include "gdbus-tests.h"
static const GDBusArgInfo foo_get_fds_in_args =
{
-1,
"type",
"s",
NULL
};
static const GDBusArgInfo * const foo_get_fds_in_arg_pointers[] = {&foo_get_fds_in_args, NULL};
static const GDBusArgInfo foo_get_fds_out_args =
{
-1,
"some_fd",
"h",
NULL
};
static const GDBusArgInfo * const foo_get_fds_out_arg_pointers[] = {&foo_get_fds_out_args, NULL};
static const GDBusMethodInfo foo_method_info_wrong_return_type =
{
-1,
"WrongReturnType",
NULL, /* in args */
NULL, /* out args */
NULL /* annotations */
};
static const GDBusMethodInfo foo_method_info_close_before_returning =
{
-1,
"CloseBeforeReturning",
NULL, /* in args */
NULL, /* out args */
NULL /* annotations */
};
static const GDBusMethodInfo foo_method_info_get_fds =
{
-1,
"GetFDs",
(GDBusArgInfo **) foo_get_fds_in_arg_pointers,
(GDBusArgInfo **) foo_get_fds_out_arg_pointers,
NULL /* annotations */
};
static const GDBusMethodInfo foo_method_info_return_error =
{
-1,
"ReturnError",
NULL, /* in args */
NULL, /* out args */
NULL /* annotations */
};
static const GDBusMethodInfo * const foo_method_info_pointers[] = {
&foo_method_info_wrong_return_type,
&foo_method_info_close_before_returning,
&foo_method_info_get_fds,
&foo_method_info_return_error,
NULL
};
static const GDBusPropertyInfo foo_property_info[] =
{
{
-1,
"InvalidType",
"s",
G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
NULL
},
{
-1,
"InvalidTypeNull",
"s",
G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
NULL
},
{
-1,
"InvalidValueType",
"s",
G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
NULL
},
};
static const GDBusPropertyInfo * const foo_property_info_pointers[] =
{
&foo_property_info[0],
&foo_property_info[1],
&foo_property_info[2],
NULL
};
static const GDBusInterfaceInfo foo_interface_info =
{
-1,
"org.example.Foo",
(GDBusMethodInfo **) &foo_method_info_pointers,
NULL, /* signals */
(GDBusPropertyInfo **) &foo_property_info_pointers,
NULL, /* annotations */
};
/* ---------------------------------------------------------------------------------------------------- */
static void
test_method_invocation_return_method_call (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *method_name,
GVariant *parameters,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
gboolean no_reply = g_dbus_message_get_flags (g_dbus_method_invocation_get_message (invocation)) & G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED;
if (g_str_equal (interface_name, DBUS_INTERFACE_PROPERTIES) &&
g_str_equal (method_name, "Get"))
{
const gchar *iface_name, *prop_name;
g_variant_get (parameters, "(&s&s)", &iface_name, &prop_name);
g_assert_cmpstr (iface_name, ==, "org.example.Foo");
/* Do different things depending on the property name. */
if (g_str_equal (prop_name, "InvalidType"))
{
if (!no_reply)
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
"Type of return value for property 'Get' call should be '(v)' but got '(s)'");
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "this type is invalid"));
}
else if (g_str_equal (prop_name, "InvalidTypeNull"))
{
if (!no_reply)
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
"Type of return value for property 'Get' call should be '(v)' but got '()'");
g_dbus_method_invocation_return_value (invocation, NULL);
}
else if (g_str_equal (prop_name, "InvalidValueType"))
{
if (!no_reply)
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
"Value returned from property 'Get' call for 'InvalidValueType' should be 's' but is 'u'");
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(v)", g_variant_new_uint32 (123)));
}
else
{
g_assert_not_reached ();
}
g_test_assert_expected_messages ();
}
else if (g_str_equal (interface_name, DBUS_INTERFACE_PROPERTIES) &&
g_str_equal (method_name, "Set"))
{
const gchar *iface_name, *prop_name;
GVariant *value;
g_variant_get (parameters, "(&s&sv)", &iface_name, &prop_name, &value);
g_assert_cmpstr (iface_name, ==, "org.example.Foo");
if (g_str_equal (prop_name, "InvalidType"))
{
if (!no_reply)
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
"Type of return value for property 'Set' call should be '()' but got '(s)'");
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "should be unit"));
}
else
{
g_assert_not_reached ();
}
g_test_assert_expected_messages ();
g_variant_unref (value);
}
else if (g_str_equal (interface_name, DBUS_INTERFACE_PROPERTIES) &&
g_str_equal (method_name, "GetAll"))
{
const gchar *iface_name;
g_variant_get (parameters, "(&s)", &iface_name);
g_assert_cmpstr (iface_name, ==, "org.example.Foo");
if (!no_reply)
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
"Type of return value for property 'GetAll' call should be '(a{sv})' but got '(s)'");
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "should be a different type"));
}
else if (g_str_equal (interface_name, "org.example.Foo") &&
g_str_equal (method_name, "WrongReturnType"))
{
if (!no_reply)
g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
"Type of return value is incorrect: expected '()', got '(s)'");
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "should be a different type"));
}
else if (g_str_equal (interface_name, "org.example.Foo") &&
g_str_equal (method_name, "CloseBeforeReturning"))
{
g_dbus_connection_close (connection, NULL, NULL, NULL);
g_dbus_method_invocation_return_value (invocation, NULL);
}
else if (g_str_equal (interface_name, "org.example.Foo") &&
g_str_equal (method_name, "GetFDs"))
{
const gchar *action;
GUnixFDList *list = NULL;
GError *local_error = NULL;
g_variant_get (parameters, "(&s)", &action);
list = g_unix_fd_list_new ();
g_unix_fd_list_append (list, 1, &local_error);
g_assert_no_error (local_error);
if (g_str_equal (action, "WrongNumber"))
{
g_unix_fd_list_append (list, 1, &local_error);
g_assert_no_error (local_error);
}
if (g_str_equal (action, "Valid") ||
g_str_equal (action, "WrongNumber"))
g_dbus_method_invocation_return_value_with_unix_fd_list (invocation, g_variant_new ("(h)", 0), list);
else
g_assert_not_reached ();
g_object_unref (list);
}
else if (g_str_equal (interface_name, "org.example.Foo") &&
g_str_equal (method_name, "ReturnError"))
{
g_dbus_method_invocation_return_dbus_error (invocation, "org.example.Foo", "SomeError");
}
else
g_assert_not_reached ();
}
static void
ensure_result_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GDBusConnection *connection = G_DBUS_CONNECTION (source);
GVariant *reply;
guint *n_outstanding_calls = user_data;
reply = g_dbus_connection_call_finish (connection, result, NULL);
/* We dont care what the reply is. */
g_clear_pointer (&reply, g_variant_unref);
g_assert_cmpint (*n_outstanding_calls, >, 0);
*n_outstanding_calls = *n_outstanding_calls - 1;
}
static void
test_method_invocation_return (void)
{
GDBusConnection *connection = NULL;
GError *local_error = NULL;
guint registration_id;
const GDBusInterfaceVTable vtable = {
test_method_invocation_return_method_call, NULL, NULL, { 0 }
};
guint n_outstanding_calls = 0;
g_test_summary ("Test calling g_dbus_method_invocation_return_*() in various ways");
/* Connect to the bus. */
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error);
g_assert_no_error (local_error);
g_assert_nonnull (connection);
/* Dont exit the test when the server closes the connection in
* CloseBeforeReturning(). */
g_dbus_connection_set_exit_on_close (connection, FALSE);
/* Register an object which we can call methods on. */
registration_id = g_dbus_connection_register_object (connection,
"/foo",
(GDBusInterfaceInfo *) &foo_interface_info,
&vtable, NULL, NULL, &local_error);
g_assert_no_error (local_error);
g_assert_cmpint (registration_id, !=, 0);
/* Test a variety of error cases */
{
const struct
{
const gchar *interface_name;
const gchar *method_name;
const gchar *parameters_string;
gboolean tests_undefined_behaviour;
}
calls[] =
{
{ DBUS_INTERFACE_PROPERTIES, "Get", "('org.example.Foo', 'InvalidType')", TRUE },
{ DBUS_INTERFACE_PROPERTIES, "Get", "('org.example.Foo', 'InvalidTypeNull')", TRUE },
{ DBUS_INTERFACE_PROPERTIES, "Get", "('org.example.Foo', 'InvalidValueType')", TRUE },
{ DBUS_INTERFACE_PROPERTIES, "Set", "('org.example.Foo', 'InvalidType', <'irrelevant'>)", TRUE },
{ DBUS_INTERFACE_PROPERTIES, "GetAll", "('org.example.Foo',)", TRUE },
{ "org.example.Foo", "WrongReturnType", "()", TRUE },
{ "org.example.Foo", "GetFDs", "('Valid',)", FALSE },
{ "org.example.Foo", "GetFDs", "('WrongNumber',)", TRUE },
{ "org.example.Foo", "ReturnError", "()", FALSE },
{ "org.example.Foo", "CloseBeforeReturning", "()", FALSE },
};
gsize i;
for (i = 0; i < G_N_ELEMENTS (calls); i++)
{
if (calls[i].tests_undefined_behaviour && !g_test_undefined ())
{
g_test_message ("Skipping %s.%s", calls[i].interface_name, calls[i].method_name);
continue;
}
else
{
g_test_message ("Calling %s.%s", calls[i].interface_name, calls[i].method_name);
}
/* Call twice, once expecting a result and once not. Do the call which
* doesnt expect a result first; message ordering should ensure that
* its completed by the time the second call completes, so we dont
* have to account for it separately.
*
* Thats good, because the only way to get g_dbus_connection_call()
* to set %G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED is to not provide
* a callback function. */
n_outstanding_calls++;
g_dbus_connection_call (connection,
g_dbus_connection_get_unique_name (connection),
"/foo",
calls[i].interface_name,
calls[i].method_name,
g_variant_new_parsed (calls[i].parameters_string),
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
NULL, /* no callback */
NULL);
g_dbus_connection_call (connection,
g_dbus_connection_get_unique_name (connection),
"/foo",
calls[i].interface_name,
calls[i].method_name,
g_variant_new_parsed (calls[i].parameters_string),
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
ensure_result_cb,
&n_outstanding_calls);
}
}
/* Wait until all the calls are complete. */
while (n_outstanding_calls > 0)
g_main_context_iteration (NULL, TRUE);
g_dbus_connection_unregister_object (connection, registration_id);
g_object_unref (connection);
}
int
main (int argc,
char *argv[])
{
g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
g_test_add_func ("/gdbus/method-invocation/return", test_method_invocation_return);
return session_bus_run ();
}