glib/gio/tests/gdbus-export.c
Philip Withnall 34ce204fd7 tests: Add D-Bus object/subtree unregistration tests
These tests cover the fixes from the previous two commits.

Signed-off-by: Philip Withnall <pwithnall@endlessos.org>

Helps: #2400
2021-10-28 14:53:48 +01:00

2002 lines
72 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.

/* GLib testing framework examples and tests
*
* Copyright (C) 2008-2010 Red Hat, 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, see <http://www.gnu.org/licenses/>.
*
* Author: David Zeuthen <davidz@redhat.com>
*/
#include <gio/gio.h>
#include <unistd.h>
#include <string.h>
#include "gdbus-tests.h"
/* all tests rely on a shared mainloop */
static GMainLoop *loop = NULL;
static GDBusConnection *c = NULL;
/* ---------------------------------------------------------------------------------------------------- */
/* Test that we can export objects, the hierarchy is correct and the right handlers are invoked */
/* ---------------------------------------------------------------------------------------------------- */
static const GDBusArgInfo foo_method1_in_args =
{
-1,
"an_input_string",
"s",
NULL
};
static const GDBusArgInfo * const foo_method1_in_arg_pointers[] = {&foo_method1_in_args, NULL};
static const GDBusArgInfo foo_method1_out_args =
{
-1,
"an_output_string",
"s",
NULL
};
static const GDBusArgInfo * const foo_method1_out_arg_pointers[] = {&foo_method1_out_args, NULL};
static const GDBusMethodInfo foo_method_info_method1 =
{
-1,
"Method1",
(GDBusArgInfo **) &foo_method1_in_arg_pointers,
(GDBusArgInfo **) &foo_method1_out_arg_pointers,
NULL
};
static const GDBusMethodInfo foo_method_info_method2 =
{
-1,
"Method2",
NULL,
NULL,
NULL
};
static const GDBusMethodInfo * const foo_method_info_pointers[] = {&foo_method_info_method1, &foo_method_info_method2, NULL};
static const GDBusSignalInfo foo_signal_info =
{
-1,
"SignalAlpha",
NULL,
NULL
};
static const GDBusSignalInfo * const foo_signal_info_pointers[] = {&foo_signal_info, NULL};
static const GDBusPropertyInfo foo_property_info[] =
{
{
-1,
"PropertyUno",
"s",
G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
NULL
},
{
-1,
"NotWritable",
"s",
G_DBUS_PROPERTY_INFO_FLAGS_READABLE,
NULL
},
{
-1,
"NotReadable",
"s",
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,
(GDBusSignalInfo **) &foo_signal_info_pointers,
(GDBusPropertyInfo **)&foo_property_info_pointers,
NULL,
};
/* Foo2 is just Foo without the properties */
static const GDBusInterfaceInfo foo2_interface_info =
{
-1,
"org.example.Foo2",
(GDBusMethodInfo **) &foo_method_info_pointers,
(GDBusSignalInfo **) &foo_signal_info_pointers,
NULL,
NULL
};
static void
foo_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)
{
if (g_strcmp0 (method_name, "Method1") == 0)
{
const gchar *input;
gchar *output;
g_variant_get (parameters, "(&s)", &input);
output = g_strdup_printf ("You passed the string '%s'. Jolly good!", input);
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", output));
g_free (output);
}
else
{
g_dbus_method_invocation_return_dbus_error (invocation,
"org.example.SomeError",
"How do you like them apples, buddy!");
}
}
static GVariant *
foo_get_property (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *property_name,
GError **error,
gpointer user_data)
{
GVariant *ret;
gchar *s;
s = g_strdup_printf ("Property '%s' Is What It Is!", property_name);
ret = g_variant_new_string (s);
g_free (s);
return ret;
}
static gboolean
foo_set_property (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *property_name,
GVariant *value,
GError **error,
gpointer user_data)
{
gchar *s;
s = g_variant_print (value, TRUE);
g_set_error (error,
G_DBUS_ERROR,
G_DBUS_ERROR_SPAWN_FILE_INVALID,
"Returning some error instead of writing the value '%s' to the property '%s'",
s, property_name);
g_free (s);
return FALSE;
}
static const GDBusInterfaceVTable foo_vtable =
{
foo_method_call,
foo_get_property,
foo_set_property,
{ 0 },
};
/* -------------------- */
static const GDBusMethodInfo bar_method_info[] =
{
{
-1,
"MethodA",
NULL,
NULL,
NULL
},
{
-1,
"MethodB",
NULL,
NULL,
NULL
}
};
static const GDBusMethodInfo * const bar_method_info_pointers[] = {&bar_method_info[0], &bar_method_info[1], NULL};
static const GDBusSignalInfo bar_signal_info[] =
{
{
-1,
"SignalMars",
NULL,
NULL
}
};
static const GDBusSignalInfo * const bar_signal_info_pointers[] = {&bar_signal_info[0], NULL};
static const GDBusPropertyInfo bar_property_info[] =
{
{
-1,
"PropertyDuo",
"s",
G_DBUS_PROPERTY_INFO_FLAGS_READABLE,
NULL
}
};
static const GDBusPropertyInfo * const bar_property_info_pointers[] = {&bar_property_info[0], NULL};
static const GDBusInterfaceInfo bar_interface_info =
{
-1,
"org.example.Bar",
(GDBusMethodInfo **) bar_method_info_pointers,
(GDBusSignalInfo **) bar_signal_info_pointers,
(GDBusPropertyInfo **) bar_property_info_pointers,
NULL,
};
/* -------------------- */
static const GDBusMethodInfo dyna_method_info[] =
{
{
-1,
"DynaCyber",
NULL,
NULL,
NULL
}
};
static const GDBusMethodInfo * const dyna_method_info_pointers[] = {&dyna_method_info[0], NULL};
static const GDBusInterfaceInfo dyna_interface_info =
{
-1,
"org.example.Dyna",
(GDBusMethodInfo **) dyna_method_info_pointers,
NULL, /* no signals */
NULL, /* no properties */
NULL,
};
static void
dyna_cyber (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *method_name,
GVariant *parameters,
GDBusMethodInvocation *invocation,
gpointer user_data)
{
GPtrArray *data = user_data;
gchar *node_name;
guint n;
node_name = strrchr (object_path, '/') + 1;
/* Add new node if it is not already known */
for (n = 0; n < data->len ; n++)
{
if (g_strcmp0 (g_ptr_array_index (data, n), node_name) == 0)
goto out;
}
g_ptr_array_add (data, g_strdup(node_name));
out:
g_dbus_method_invocation_return_value (invocation, NULL);
}
static const GDBusInterfaceVTable dyna_interface_vtable =
{
dyna_cyber,
NULL,
NULL,
{ 0 }
};
/* ---------------------------------------------------------------------------------------------------- */
static void
introspect_callback (GDBusProxy *proxy,
GAsyncResult *res,
gpointer user_data)
{
gchar **xml_data = user_data;
GVariant *result;
GError *error;
error = NULL;
result = g_dbus_proxy_call_finish (proxy,
res,
&error);
g_assert_no_error (error);
g_assert (result != NULL);
g_variant_get (result, "(s)", xml_data);
g_variant_unref (result);
g_main_loop_quit (loop);
}
static gint
compare_strings (gconstpointer a,
gconstpointer b)
{
const gchar *sa = *(const gchar **) a;
const gchar *sb = *(const gchar **) b;
/* Array terminator must sort last */
if (sa == NULL)
return 1;
if (sb == NULL)
return -1;
return strcmp (sa, sb);
}
static gchar **
get_nodes_at (GDBusConnection *c,
const gchar *object_path)
{
GError *error;
GDBusProxy *proxy;
gchar *xml_data;
GPtrArray *p;
GDBusNodeInfo *node_info;
guint n;
error = NULL;
proxy = g_dbus_proxy_new_sync (c,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
NULL,
g_dbus_connection_get_unique_name (c),
object_path,
"org.freedesktop.DBus.Introspectable",
NULL,
&error);
g_assert_no_error (error);
g_assert (proxy != NULL);
/* do this async to avoid libdbus-1 deadlocks */
xml_data = NULL;
g_dbus_proxy_call (proxy,
"Introspect",
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
(GAsyncReadyCallback) introspect_callback,
&xml_data);
g_main_loop_run (loop);
g_assert (xml_data != NULL);
node_info = g_dbus_node_info_new_for_xml (xml_data, &error);
g_assert_no_error (error);
g_assert (node_info != NULL);
p = g_ptr_array_new ();
for (n = 0; node_info->nodes != NULL && node_info->nodes[n] != NULL; n++)
{
const GDBusNodeInfo *sub_node_info = node_info->nodes[n];
g_ptr_array_add (p, g_strdup (sub_node_info->path));
}
g_ptr_array_add (p, NULL);
g_object_unref (proxy);
g_free (xml_data);
g_dbus_node_info_unref (node_info);
/* Nodes are semantically unordered; sort array so tests can rely on order */
g_ptr_array_sort (p, compare_strings);
return (gchar **) g_ptr_array_free (p, FALSE);
}
static gboolean
has_interface (GDBusConnection *c,
const gchar *object_path,
const gchar *interface_name)
{
GError *error;
GDBusProxy *proxy;
gchar *xml_data;
GDBusNodeInfo *node_info;
gboolean ret;
error = NULL;
proxy = g_dbus_proxy_new_sync (c,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
NULL,
g_dbus_connection_get_unique_name (c),
object_path,
"org.freedesktop.DBus.Introspectable",
NULL,
&error);
g_assert_no_error (error);
g_assert (proxy != NULL);
/* do this async to avoid libdbus-1 deadlocks */
xml_data = NULL;
g_dbus_proxy_call (proxy,
"Introspect",
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
(GAsyncReadyCallback) introspect_callback,
&xml_data);
g_main_loop_run (loop);
g_assert (xml_data != NULL);
node_info = g_dbus_node_info_new_for_xml (xml_data, &error);
g_assert_no_error (error);
g_assert (node_info != NULL);
ret = (g_dbus_node_info_lookup_interface (node_info, interface_name) != NULL);
g_object_unref (proxy);
g_free (xml_data);
g_dbus_node_info_unref (node_info);
return ret;
}
static guint
count_interfaces (GDBusConnection *c,
const gchar *object_path)
{
GError *error;
GDBusProxy *proxy;
gchar *xml_data;
GDBusNodeInfo *node_info;
guint ret;
error = NULL;
proxy = g_dbus_proxy_new_sync (c,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
NULL,
g_dbus_connection_get_unique_name (c),
object_path,
"org.freedesktop.DBus.Introspectable",
NULL,
&error);
g_assert_no_error (error);
g_assert (proxy != NULL);
/* do this async to avoid libdbus-1 deadlocks */
xml_data = NULL;
g_dbus_proxy_call (proxy,
"Introspect",
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
(GAsyncReadyCallback) introspect_callback,
&xml_data);
g_main_loop_run (loop);
g_assert (xml_data != NULL);
node_info = g_dbus_node_info_new_for_xml (xml_data, &error);
g_assert_no_error (error);
g_assert (node_info != NULL);
ret = 0;
while (node_info->interfaces != NULL && node_info->interfaces[ret] != NULL)
ret++;
g_object_unref (proxy);
g_free (xml_data);
g_dbus_node_info_unref (node_info);
return ret;
}
static void
dyna_create_callback (GDBusProxy *proxy,
GAsyncResult *res,
gpointer user_data)
{
GVariant *result;
GError *error;
error = NULL;
result = g_dbus_proxy_call_finish (proxy,
res,
&error);
g_assert_no_error (error);
g_assert (result != NULL);
g_variant_unref (result);
g_main_loop_quit (loop);
}
/* Dynamically create @object_name under /foo/dyna */
static void
dyna_create (GDBusConnection *c,
const gchar *object_name)
{
GError *error;
GDBusProxy *proxy;
gchar *object_path;
object_path = g_strconcat ("/foo/dyna/", object_name, NULL);
error = NULL;
proxy = g_dbus_proxy_new_sync (c,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
NULL,
g_dbus_connection_get_unique_name (c),
object_path,
"org.example.Dyna",
NULL,
&error);
g_assert_no_error (error);
g_assert (proxy != NULL);
/* do this async to avoid libdbus-1 deadlocks */
g_dbus_proxy_call (proxy,
"DynaCyber",
g_variant_new ("()"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
(GAsyncReadyCallback) dyna_create_callback,
NULL);
g_main_loop_run (loop);
g_assert_no_error (error);
g_object_unref (proxy);
g_free (object_path);
return;
}
typedef struct
{
guint num_unregistered_calls;
guint num_unregistered_subtree_calls;
guint num_subtree_nodes;
} ObjectRegistrationData;
static void
on_object_unregistered (gpointer user_data)
{
ObjectRegistrationData *data = user_data;
data->num_unregistered_calls++;
}
static void
on_subtree_unregistered (gpointer user_data)
{
ObjectRegistrationData *data = user_data;
data->num_unregistered_subtree_calls++;
}
static gboolean
_g_strv_has_string (const gchar* const * haystack,
const gchar *needle)
{
guint n;
for (n = 0; haystack != NULL && haystack[n] != NULL; n++)
{
if (g_strcmp0 (haystack[n], needle) == 0)
return TRUE;
}
return FALSE;
}
/* -------------------- */
static gchar **
subtree_enumerate (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
gpointer user_data)
{
ObjectRegistrationData *data = user_data;
GPtrArray *p;
gchar **nodes;
guint n;
p = g_ptr_array_new ();
for (n = 0; n < data->num_subtree_nodes; n++)
{
g_ptr_array_add (p, g_strdup_printf ("vp%d", n));
g_ptr_array_add (p, g_strdup_printf ("evp%d", n));
}
g_ptr_array_add (p, NULL);
nodes = (gchar **) g_ptr_array_free (p, FALSE);
return nodes;
}
/* Only allows certain objects, and aborts on unknowns */
static GDBusInterfaceInfo **
subtree_introspect (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *node,
gpointer user_data)
{
const GDBusInterfaceInfo *interfaces[2] = {
NULL /* filled in below */, NULL
};
/* VPs implement the Foo interface, EVPs implement the Bar interface. The root
* does not implement any interfaces
*/
if (node == NULL)
{
return NULL;
}
else if (g_str_has_prefix (node, "vp"))
{
interfaces[0] = &foo_interface_info;
}
else if (g_str_has_prefix (node, "evp"))
{
interfaces[0] = &bar_interface_info;
}
else
{
g_assert_not_reached ();
}
return g_memdup2 (interfaces, 2 * sizeof (void *));
}
static const GDBusInterfaceVTable *
subtree_dispatch (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *node,
gpointer *out_user_data,
gpointer user_data)
{
if (g_strcmp0 (interface_name, "org.example.Foo") == 0)
return &foo_vtable;
else
return NULL;
}
static const GDBusSubtreeVTable subtree_vtable =
{
subtree_enumerate,
subtree_introspect,
subtree_dispatch,
{ 0 }
};
/* -------------------- */
static gchar **
dynamic_subtree_enumerate (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
gpointer user_data)
{
GPtrArray *data = user_data;
gchar **nodes = g_new (gchar*, data->len + 1);
guint n;
for (n = 0; n < data->len; n++)
{
nodes[n] = g_strdup (g_ptr_array_index (data, n));
}
nodes[data->len] = NULL;
return nodes;
}
/* Allow all objects to be introspected */
static GDBusInterfaceInfo **
dynamic_subtree_introspect (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *node,
gpointer user_data)
{
const GDBusInterfaceInfo *interfaces[2] = { &dyna_interface_info, NULL };
return g_memdup2 (interfaces, 2 * sizeof (void *));
}
static const GDBusInterfaceVTable *
dynamic_subtree_dispatch (GDBusConnection *connection,
const gchar *sender,
const gchar *object_path,
const gchar *interface_name,
const gchar *node,
gpointer *out_user_data,
gpointer user_data)
{
*out_user_data = user_data;
return &dyna_interface_vtable;
}
static const GDBusSubtreeVTable dynamic_subtree_vtable =
{
dynamic_subtree_enumerate,
dynamic_subtree_introspect,
dynamic_subtree_dispatch,
{ 0 }
};
/* -------------------- */
typedef struct
{
const gchar *object_path;
gboolean check_remote_errors;
} TestDispatchThreadFuncArgs;
static gpointer
test_dispatch_thread_func (gpointer user_data)
{
TestDispatchThreadFuncArgs *args = user_data;
const gchar *object_path = args->object_path;
GDBusProxy *foo_proxy;
GVariant *value;
GVariant *inner;
GError *error;
gchar *s;
const gchar *value_str;
foo_proxy = g_dbus_proxy_new_sync (c,
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
NULL,
g_dbus_connection_get_unique_name (c),
object_path,
"org.example.Foo",
NULL,
&error);
g_assert (foo_proxy != NULL);
/* generic interfaces */
error = NULL;
value = g_dbus_proxy_call_sync (foo_proxy,
"org.freedesktop.DBus.Peer.Ping",
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
g_assert_no_error (error);
g_assert (value != NULL);
g_variant_unref (value);
/* user methods */
error = NULL;
value = g_dbus_proxy_call_sync (foo_proxy,
"Method1",
g_variant_new ("(s)", "winwinwin"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
g_assert_no_error (error);
g_assert (value != NULL);
g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("(s)")));
g_variant_get (value, "(&s)", &value_str);
g_assert_cmpstr (value_str, ==, "You passed the string 'winwinwin'. Jolly good!");
g_variant_unref (value);
error = NULL;
value = g_dbus_proxy_call_sync (foo_proxy,
"Method2",
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_DBUS_ERROR);
g_assert_cmpstr (error->message, ==, "GDBus.Error:org.example.SomeError: How do you like them apples, buddy!");
g_error_free (error);
g_assert (value == NULL);
error = NULL;
value = g_dbus_proxy_call_sync (foo_proxy,
"Method2",
g_variant_new ("(s)", "failfailfail"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Type of message, “(s)”, does not match expected type “()”");
g_error_free (error);
g_assert (value == NULL);
error = NULL;
value = g_dbus_proxy_call_sync (foo_proxy,
"NonExistantMethod",
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD);
g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.UnknownMethod: No such method “NonExistantMethod”");
g_error_free (error);
g_assert (value == NULL);
error = NULL;
value = g_dbus_proxy_call_sync (foo_proxy,
"org.example.FooXYZ.NonExistant",
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD);
g_error_free (error);
g_assert (value == NULL);
/* user properties */
error = NULL;
value = g_dbus_proxy_call_sync (foo_proxy,
"org.freedesktop.DBus.Properties.Get",
g_variant_new ("(ss)",
"org.example.Foo",
"PropertyUno"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
g_assert_no_error (error);
g_assert (value != NULL);
g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("(v)")));
g_variant_get (value, "(v)", &inner);
g_assert (g_variant_is_of_type (inner, G_VARIANT_TYPE_STRING));
g_assert_cmpstr (g_variant_get_string (inner, NULL), ==, "Property 'PropertyUno' Is What It Is!");
g_variant_unref (value);
g_variant_unref (inner);
error = NULL;
value = g_dbus_proxy_call_sync (foo_proxy,
"org.freedesktop.DBus.Properties.Get",
g_variant_new ("(ss)",
"org.example.Foo",
"ThisDoesntExist"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
g_assert (value == NULL);
g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: No such property “ThisDoesntExist”");
g_error_free (error);
error = NULL;
value = g_dbus_proxy_call_sync (foo_proxy,
"org.freedesktop.DBus.Properties.Get",
g_variant_new ("(ss)",
"org.example.Foo",
"NotReadable"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
g_assert (value == NULL);
g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Property “NotReadable” is not readable");
g_error_free (error);
error = NULL;
value = g_dbus_proxy_call_sync (foo_proxy,
"org.freedesktop.DBus.Properties.Set",
g_variant_new ("(ssv)",
"org.example.Foo",
"NotReadable",
g_variant_new_string ("But Writable you are!")),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
g_assert (value == NULL);
if (args->check_remote_errors)
{
/* _with_closures variant doesn't support customizing error data. */
g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_SPAWN_FILE_INVALID);
g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.Spawn.FileInvalid: Returning some error instead of writing the value ''But Writable you are!'' to the property 'NotReadable'");
}
g_assert (error != NULL && error->domain == G_DBUS_ERROR);
g_error_free (error);
error = NULL;
value = g_dbus_proxy_call_sync (foo_proxy,
"org.freedesktop.DBus.Properties.Set",
g_variant_new ("(ssv)",
"org.example.Foo",
"NotWritable",
g_variant_new_uint32 (42)),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
g_assert (value == NULL);
g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS);
g_assert_cmpstr (error->message, ==, "GDBus.Error:org.freedesktop.DBus.Error.InvalidArgs: Property “NotWritable” is not writable");
g_error_free (error);
error = NULL;
value = g_dbus_proxy_call_sync (foo_proxy,
"org.freedesktop.DBus.Properties.GetAll",
g_variant_new ("(s)",
"org.example.Foo"),
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
g_assert_no_error (error);
g_assert (value != NULL);
g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("(a{sv})")));
s = g_variant_print (value, TRUE);
g_assert_cmpstr (s, ==, "({'PropertyUno': <\"Property 'PropertyUno' Is What It Is!\">, 'NotWritable': <\"Property 'NotWritable' Is What It Is!\">},)");
g_free (s);
g_variant_unref (value);
g_object_unref (foo_proxy);
g_main_loop_quit (loop);
return NULL;
}
static void
test_dispatch (const gchar *object_path,
gboolean check_remote_errors)
{
GThread *thread;
TestDispatchThreadFuncArgs args = {object_path, check_remote_errors};
/* run this in a thread to avoid deadlocks */
thread = g_thread_new ("test_dispatch",
test_dispatch_thread_func,
(gpointer) &args);
g_main_loop_run (loop);
g_thread_join (thread);
}
static void
test_object_registration (void)
{
GError *error;
ObjectRegistrationData data;
GPtrArray *dyna_data;
gchar **nodes;
guint boss_foo_reg_id;
guint boss_bar_reg_id;
guint worker1_foo_reg_id;
guint worker1p1_foo_reg_id;
guint worker2_bar_reg_id;
guint intern1_foo_reg_id;
guint intern2_bar_reg_id;
guint intern2_foo_reg_id;
guint intern3_bar_reg_id;
guint registration_id;
guint subtree_registration_id;
guint non_subtree_object_path_foo_reg_id;
guint non_subtree_object_path_bar_reg_id;
guint dyna_subtree_registration_id;
guint num_successful_registrations;
data.num_unregistered_calls = 0;
data.num_unregistered_subtree_calls = 0;
data.num_subtree_nodes = 0;
num_successful_registrations = 0;
error = NULL;
c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
g_assert_no_error (error);
g_assert (c != NULL);
registration_id = g_dbus_connection_register_object (c,
"/foo/boss",
(GDBusInterfaceInfo *) &foo_interface_info,
&foo_vtable,
&data,
on_object_unregistered,
&error);
g_assert_no_error (error);
g_assert (registration_id > 0);
boss_foo_reg_id = registration_id;
num_successful_registrations++;
registration_id = g_dbus_connection_register_object (c,
"/foo/boss",
(GDBusInterfaceInfo *) &bar_interface_info,
NULL,
&data,
on_object_unregistered,
&error);
g_assert_no_error (error);
g_assert (registration_id > 0);
boss_bar_reg_id = registration_id;
num_successful_registrations++;
registration_id = g_dbus_connection_register_object (c,
"/foo/boss/worker1",
(GDBusInterfaceInfo *) &foo_interface_info,
NULL,
&data,
on_object_unregistered,
&error);
g_assert_no_error (error);
g_assert (registration_id > 0);
worker1_foo_reg_id = registration_id;
num_successful_registrations++;
registration_id = g_dbus_connection_register_object (c,
"/foo/boss/worker1p1",
(GDBusInterfaceInfo *) &foo_interface_info,
NULL,
&data,
on_object_unregistered,
&error);
g_assert_no_error (error);
g_assert (registration_id > 0);
worker1p1_foo_reg_id = registration_id;
num_successful_registrations++;
registration_id = g_dbus_connection_register_object (c,
"/foo/boss/worker2",
(GDBusInterfaceInfo *) &bar_interface_info,
NULL,
&data,
on_object_unregistered,
&error);
g_assert_no_error (error);
g_assert (registration_id > 0);
worker2_bar_reg_id = registration_id;
num_successful_registrations++;
registration_id = g_dbus_connection_register_object (c,
"/foo/boss/interns/intern1",
(GDBusInterfaceInfo *) &foo_interface_info,
NULL,
&data,
on_object_unregistered,
&error);
g_assert_no_error (error);
g_assert (registration_id > 0);
intern1_foo_reg_id = registration_id;
num_successful_registrations++;
/* ... and try again at another path */
registration_id = g_dbus_connection_register_object (c,
"/foo/boss/interns/intern2",
(GDBusInterfaceInfo *) &bar_interface_info,
NULL,
&data,
on_object_unregistered,
&error);
g_assert_no_error (error);
g_assert (registration_id > 0);
intern2_bar_reg_id = registration_id;
num_successful_registrations++;
/* register at the same path/interface - this should fail */
registration_id = g_dbus_connection_register_object (c,
"/foo/boss/interns/intern2",
(GDBusInterfaceInfo *) &bar_interface_info,
NULL,
&data,
on_object_unregistered,
&error);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS);
g_assert (!g_dbus_error_is_remote_error (error));
g_error_free (error);
error = NULL;
g_assert (registration_id == 0);
/* register at different interface - shouldn't fail */
registration_id = g_dbus_connection_register_object (c,
"/foo/boss/interns/intern2",
(GDBusInterfaceInfo *) &foo_interface_info,
NULL,
&data,
on_object_unregistered,
&error);
g_assert_no_error (error);
g_assert (registration_id > 0);
intern2_foo_reg_id = registration_id;
num_successful_registrations++;
/* unregister it via the id */
g_assert (g_dbus_connection_unregister_object (c, registration_id));
g_main_context_iteration (NULL, FALSE);
g_assert_cmpint (data.num_unregistered_calls, ==, 1);
intern2_foo_reg_id = 0;
/* register it back */
registration_id = g_dbus_connection_register_object (c,
"/foo/boss/interns/intern2",
(GDBusInterfaceInfo *) &foo_interface_info,
NULL,
&data,
on_object_unregistered,
&error);
g_assert_no_error (error);
g_assert (registration_id > 0);
intern2_foo_reg_id = registration_id;
num_successful_registrations++;
registration_id = g_dbus_connection_register_object (c,
"/foo/boss/interns/intern3",
(GDBusInterfaceInfo *) &bar_interface_info,
NULL,
&data,
on_object_unregistered,
&error);
g_assert_no_error (error);
g_assert (registration_id > 0);
intern3_bar_reg_id = registration_id;
num_successful_registrations++;
/* now register a whole subtree at /foo/boss/executives */
subtree_registration_id = g_dbus_connection_register_subtree (c,
"/foo/boss/executives",
&subtree_vtable,
G_DBUS_SUBTREE_FLAGS_NONE,
&data,
on_subtree_unregistered,
&error);
g_assert_no_error (error);
g_assert (subtree_registration_id > 0);
/* try registering it again.. this should fail */
registration_id = g_dbus_connection_register_subtree (c,
"/foo/boss/executives",
&subtree_vtable,
G_DBUS_SUBTREE_FLAGS_NONE,
&data,
on_subtree_unregistered,
&error);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS);
g_assert (!g_dbus_error_is_remote_error (error));
g_error_free (error);
error = NULL;
g_assert (registration_id == 0);
/* unregister it, then register it again */
g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 0);
g_assert (g_dbus_connection_unregister_subtree (c, subtree_registration_id));
g_main_context_iteration (NULL, FALSE);
g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 1);
subtree_registration_id = g_dbus_connection_register_subtree (c,
"/foo/boss/executives",
&subtree_vtable,
G_DBUS_SUBTREE_FLAGS_NONE,
&data,
on_subtree_unregistered,
&error);
g_assert_no_error (error);
g_assert (subtree_registration_id > 0);
/* try to register something under /foo/boss/executives - this should work
* because registered subtrees and registered objects can coexist.
*
* Make the exported object implement *two* interfaces so we can check
* that the right introspection handler is invoked.
*/
registration_id = g_dbus_connection_register_object (c,
"/foo/boss/executives/non_subtree_object",
(GDBusInterfaceInfo *) &bar_interface_info,
NULL,
&data,
on_object_unregistered,
&error);
g_assert_no_error (error);
g_assert (registration_id > 0);
non_subtree_object_path_bar_reg_id = registration_id;
num_successful_registrations++;
registration_id = g_dbus_connection_register_object (c,
"/foo/boss/executives/non_subtree_object",
(GDBusInterfaceInfo *) &foo_interface_info,
NULL,
&data,
on_object_unregistered,
&error);
g_assert_no_error (error);
g_assert (registration_id > 0);
non_subtree_object_path_foo_reg_id = registration_id;
num_successful_registrations++;
/* now register a dynamic subtree, spawning objects as they are called */
dyna_data = g_ptr_array_new_with_free_func (g_free);
dyna_subtree_registration_id = g_dbus_connection_register_subtree (c,
"/foo/dyna",
&dynamic_subtree_vtable,
G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES,
dyna_data,
(GDestroyNotify)g_ptr_array_unref,
&error);
g_assert_no_error (error);
g_assert (dyna_subtree_registration_id > 0);
/* First assert that we have no nodes in the dynamic subtree */
nodes = get_nodes_at (c, "/foo/dyna");
g_assert (nodes != NULL);
g_assert_cmpint (g_strv_length (nodes), ==, 0);
g_strfreev (nodes);
g_assert_cmpint (count_interfaces (c, "/foo/dyna"), ==, 4);
/* Install three nodes in the dynamic subtree via the dyna_data backdoor and
* assert that they show up correctly in the introspection data */
g_ptr_array_add (dyna_data, g_strdup ("lol"));
g_ptr_array_add (dyna_data, g_strdup ("cat"));
g_ptr_array_add (dyna_data, g_strdup ("cheezburger"));
nodes = get_nodes_at (c, "/foo/dyna");
g_assert (nodes != NULL);
g_assert_cmpint (g_strv_length (nodes), ==, 3);
g_assert_cmpstr (nodes[0], ==, "cat");
g_assert_cmpstr (nodes[1], ==, "cheezburger");
g_assert_cmpstr (nodes[2], ==, "lol");
g_strfreev (nodes);
g_assert_cmpint (count_interfaces (c, "/foo/dyna/lol"), ==, 4);
g_assert_cmpint (count_interfaces (c, "/foo/dyna/cat"), ==, 4);
g_assert_cmpint (count_interfaces (c, "/foo/dyna/cheezburger"), ==, 4);
/* Call a non-existing object path and assert that it has been created */
dyna_create (c, "dynamicallycreated");
nodes = get_nodes_at (c, "/foo/dyna");
g_assert (nodes != NULL);
g_assert_cmpint (g_strv_length (nodes), ==, 4);
g_assert_cmpstr (nodes[0], ==, "cat");
g_assert_cmpstr (nodes[1], ==, "cheezburger");
g_assert_cmpstr (nodes[2], ==, "dynamicallycreated");
g_assert_cmpstr (nodes[3], ==, "lol");
g_strfreev (nodes);
g_assert_cmpint (count_interfaces (c, "/foo/dyna/dynamicallycreated"), ==, 4);
/* now check that the object hierarchy is properly generated... yes, it's a bit
* perverse that we round-trip to the bus to introspect ourselves ;-)
*/
nodes = get_nodes_at (c, "/");
g_assert (nodes != NULL);
g_assert_cmpint (g_strv_length (nodes), ==, 1);
g_assert_cmpstr (nodes[0], ==, "foo");
g_strfreev (nodes);
g_assert_cmpint (count_interfaces (c, "/"), ==, 0);
nodes = get_nodes_at (c, "/foo");
g_assert (nodes != NULL);
g_assert_cmpint (g_strv_length (nodes), ==, 2);
g_assert_cmpstr (nodes[0], ==, "boss");
g_assert_cmpstr (nodes[1], ==, "dyna");
g_strfreev (nodes);
g_assert_cmpint (count_interfaces (c, "/foo"), ==, 0);
nodes = get_nodes_at (c, "/foo/boss");
g_assert (nodes != NULL);
g_assert_cmpint (g_strv_length (nodes), ==, 5);
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "worker1"));
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "worker1p1"));
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "worker2"));
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "interns"));
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "executives"));
g_strfreev (nodes);
/* any registered object always implement org.freedesktop.DBus.[Peer,Introspectable,Properties] */
g_assert_cmpint (count_interfaces (c, "/foo/boss"), ==, 5);
g_assert (has_interface (c, "/foo/boss", foo_interface_info.name));
g_assert (has_interface (c, "/foo/boss", bar_interface_info.name));
/* check subtree nodes - we should have only non_subtree_object in /foo/boss/executives
* because data.num_subtree_nodes is 0
*/
nodes = get_nodes_at (c, "/foo/boss/executives");
g_assert (nodes != NULL);
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
g_assert_cmpint (g_strv_length (nodes), ==, 1);
g_strfreev (nodes);
g_assert_cmpint (count_interfaces (c, "/foo/boss/executives"), ==, 0);
/* now change data.num_subtree_nodes and check */
data.num_subtree_nodes = 2;
nodes = get_nodes_at (c, "/foo/boss/executives");
g_assert (nodes != NULL);
g_assert_cmpint (g_strv_length (nodes), ==, 5);
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp0"));
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp1"));
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp0"));
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp1"));
/* check that /foo/boss/executives/non_subtree_object is not handled by the
* subtree handlers - we can do this because objects from subtree handlers
* has exactly one interface and non_subtree_object has two
*/
g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/non_subtree_object"), ==, 5);
g_assert (has_interface (c, "/foo/boss/executives/non_subtree_object", foo_interface_info.name));
g_assert (has_interface (c, "/foo/boss/executives/non_subtree_object", bar_interface_info.name));
/* check that the vp and evp objects are handled by the subtree handlers */
g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/vp0"), ==, 4);
g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/vp1"), ==, 4);
g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/evp0"), ==, 4);
g_assert_cmpint (count_interfaces (c, "/foo/boss/executives/evp1"), ==, 4);
g_assert (has_interface (c, "/foo/boss/executives/vp0", foo_interface_info.name));
g_assert (has_interface (c, "/foo/boss/executives/vp1", foo_interface_info.name));
g_assert (has_interface (c, "/foo/boss/executives/evp0", bar_interface_info.name));
g_assert (has_interface (c, "/foo/boss/executives/evp1", bar_interface_info.name));
g_strfreev (nodes);
data.num_subtree_nodes = 3;
nodes = get_nodes_at (c, "/foo/boss/executives");
g_assert (nodes != NULL);
g_assert_cmpint (g_strv_length (nodes), ==, 7);
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp0"));
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp1"));
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "vp2"));
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp0"));
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp1"));
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "evp2"));
g_strfreev (nodes);
/* This is to check that a bug (rather, class of bugs) in gdbusconnection.c's
*
* g_dbus_connection_list_registered_unlocked()
*
* where /foo/boss/worker1 reported a child '1', is now fixed.
*/
nodes = get_nodes_at (c, "/foo/boss/worker1");
g_assert (nodes != NULL);
g_assert_cmpint (g_strv_length (nodes), ==, 0);
g_strfreev (nodes);
/* check that calls are properly dispatched to the functions in foo_vtable for objects
* implementing the org.example.Foo interface
*
* We do this for both a regular registered object (/foo/boss) and also for an object
* registered through the subtree mechanism.
*/
test_dispatch ("/foo/boss", TRUE);
test_dispatch ("/foo/boss/executives/vp0", TRUE);
/* To prevent from exiting and attaching a D-Bus tool like D-Feet; uncomment: */
#if 0
g_debug ("Point D-feet or other tool at: %s", g_test_dbus_get_temporary_address());
g_main_loop_run (loop);
#endif
/* check that unregistering the subtree handler works */
g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 1);
g_assert (g_dbus_connection_unregister_subtree (c, subtree_registration_id));
g_main_context_iteration (NULL, FALSE);
g_assert_cmpint (data.num_unregistered_subtree_calls, ==, 2);
nodes = get_nodes_at (c, "/foo/boss/executives");
g_assert (nodes != NULL);
g_assert_cmpint (g_strv_length (nodes), ==, 1);
g_assert (_g_strv_has_string ((const gchar* const *) nodes, "non_subtree_object"));
g_strfreev (nodes);
g_assert (g_dbus_connection_unregister_object (c, boss_foo_reg_id));
g_assert (g_dbus_connection_unregister_object (c, boss_bar_reg_id));
g_assert (g_dbus_connection_unregister_object (c, worker1_foo_reg_id));
g_assert (g_dbus_connection_unregister_object (c, worker1p1_foo_reg_id));
g_assert (g_dbus_connection_unregister_object (c, worker2_bar_reg_id));
g_assert (g_dbus_connection_unregister_object (c, intern1_foo_reg_id));
g_assert (g_dbus_connection_unregister_object (c, intern2_bar_reg_id));
g_assert (g_dbus_connection_unregister_object (c, intern2_foo_reg_id));
g_assert (g_dbus_connection_unregister_object (c, intern3_bar_reg_id));
g_assert (g_dbus_connection_unregister_object (c, non_subtree_object_path_bar_reg_id));
g_assert (g_dbus_connection_unregister_object (c, non_subtree_object_path_foo_reg_id));
g_main_context_iteration (NULL, FALSE);
g_assert_cmpint (data.num_unregistered_calls, ==, num_successful_registrations);
/* check that we no longer export any objects - TODO: it looks like there's a bug in
* libdbus-1 here: libdbus still reports the '/foo' object; so disable the test for now
*/
#if 0
nodes = get_nodes_at (c, "/");
g_assert (nodes != NULL);
g_assert_cmpint (g_strv_length (nodes), ==, 0);
g_strfreev (nodes);
#endif
g_object_unref (c);
}
static void
test_object_registration_with_closures (void)
{
GError *error;
guint registration_id;
error = NULL;
c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
g_assert_no_error (error);
g_assert (c != NULL);
registration_id = g_dbus_connection_register_object_with_closures (c,
"/foo/boss",
(GDBusInterfaceInfo *) &foo_interface_info,
g_cclosure_new (G_CALLBACK (foo_method_call), NULL, NULL),
g_cclosure_new (G_CALLBACK (foo_get_property), NULL, NULL),
g_cclosure_new (G_CALLBACK (foo_set_property), NULL, NULL),
&error);
g_assert_no_error (error);
g_assert (registration_id > 0);
test_dispatch ("/foo/boss", FALSE);
g_assert (g_dbus_connection_unregister_object (c, registration_id));
g_object_unref (c);
}
static const GDBusInterfaceInfo test_interface_info1 =
{
-1,
"org.example.Foo",
(GDBusMethodInfo **) NULL,
(GDBusSignalInfo **) NULL,
(GDBusPropertyInfo **) NULL,
NULL,
};
static const GDBusInterfaceInfo test_interface_info2 =
{
-1,
"org.freedesktop.DBus.Properties",
(GDBusMethodInfo **) NULL,
(GDBusSignalInfo **) NULL,
(GDBusPropertyInfo **) NULL,
NULL,
};
static void
check_interfaces (GDBusConnection *c,
const gchar *object_path,
const gchar **interfaces)
{
GError *error;
GDBusProxy *proxy;
gchar *xml_data;
GDBusNodeInfo *node_info;
gint i, j;
error = NULL;
proxy = g_dbus_proxy_new_sync (c,
G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
NULL,
g_dbus_connection_get_unique_name (c),
object_path,
"org.freedesktop.DBus.Introspectable",
NULL,
&error);
g_assert_no_error (error);
g_assert (proxy != NULL);
/* do this async to avoid libdbus-1 deadlocks */
xml_data = NULL;
g_dbus_proxy_call (proxy,
"Introspect",
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
(GAsyncReadyCallback) introspect_callback,
&xml_data);
g_main_loop_run (loop);
g_assert (xml_data != NULL);
node_info = g_dbus_node_info_new_for_xml (xml_data, &error);
g_assert_no_error (error);
g_assert (node_info != NULL);
g_assert (node_info->interfaces != NULL);
for (i = 0; node_info->interfaces[i]; i++) ;
#if 0
if (g_strv_length ((gchar**)interfaces) != i - 1)
{
g_printerr ("expected ");
for (i = 0; interfaces[i]; i++)
g_printerr ("%s ", interfaces[i]);
g_printerr ("\ngot ");
for (i = 0; node_info->interfaces[i]; i++)
g_printerr ("%s ", node_info->interfaces[i]->name);
g_printerr ("\n");
}
#endif
g_assert_cmpint (g_strv_length ((gchar**)interfaces), ==, i - 1);
for (i = 0; interfaces[i]; i++)
{
for (j = 0; node_info->interfaces[j]; j++)
{
if (strcmp (interfaces[i], node_info->interfaces[j]->name) == 0)
goto found;
}
g_assert_not_reached ();
found: ;
}
g_object_unref (proxy);
g_free (xml_data);
g_dbus_node_info_unref (node_info);
}
static void
test_registered_interfaces (void)
{
GError *error;
guint id1, id2;
const gchar *interfaces[] = {
"org.example.Foo",
"org.freedesktop.DBus.Properties",
"org.freedesktop.DBus.Introspectable",
NULL,
};
error = NULL;
c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
g_assert_no_error (error);
g_assert (c != NULL);
id1 = g_dbus_connection_register_object (c,
"/test",
(GDBusInterfaceInfo *) &test_interface_info1,
NULL,
NULL,
NULL,
&error);
g_assert_no_error (error);
g_assert (id1 > 0);
id2 = g_dbus_connection_register_object (c,
"/test",
(GDBusInterfaceInfo *) &test_interface_info2,
NULL,
NULL,
NULL,
&error);
g_assert_no_error (error);
g_assert (id2 > 0);
check_interfaces (c, "/test", interfaces);
g_assert (g_dbus_connection_unregister_object (c, id1));
g_assert (g_dbus_connection_unregister_object (c, id2));
g_object_unref (c);
}
/* ---------------------------------------------------------------------------------------------------- */
static void
test_async_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)
{
const GDBusPropertyInfo *property;
/* Strictly speaking, this function should also expect to receive
* method calls not on the org.freedesktop.DBus.Properties interface,
* but we don't do any during this testcase, so assert that.
*/
g_assert_cmpstr (interface_name, ==, "org.freedesktop.DBus.Properties");
g_assert (g_dbus_method_invocation_get_method_info (invocation) == NULL);
property = g_dbus_method_invocation_get_property_info (invocation);
/* We should never be seeing any property calls on the com.example.Bar
* interface because it doesn't export any properties.
*
* In each case below make sure the interface is org.example.Foo.
*/
/* Do a whole lot of asserts to make sure that invalid calls are still
* getting properly rejected by GDBusConnection and that our
* environment is as we expect it to be.
*/
if (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");
g_assert (property != NULL);
g_assert_cmpstr (prop_name, ==, property->name);
g_assert (property->flags & G_DBUS_PROPERTY_INFO_FLAGS_READABLE);
g_dbus_method_invocation_return_value (invocation, g_variant_new ("(v)", g_variant_new_string (prop_name)));
}
else if (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");
g_assert (property != NULL);
g_assert_cmpstr (prop_name, ==, property->name);
g_assert (property->flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE);
g_assert (g_variant_is_of_type (value, G_VARIANT_TYPE (property->signature)));
g_dbus_method_invocation_return_value (invocation, g_variant_new ("()"));
g_variant_unref (value);
}
else if (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");
g_assert (property == NULL);
g_dbus_method_invocation_return_value (invocation,
g_variant_new_parsed ("({ 'PropertyUno': < 'uno' >,"
" 'NotWritable': < 'notwrite' > },)"));
}
else
g_assert_not_reached ();
}
static gint outstanding_cases;
static void
ensure_result_cb (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GDBusConnection *connection = G_DBUS_CONNECTION (source);
GVariant *reply;
reply = g_dbus_connection_call_finish (connection, result, NULL);
if (user_data == NULL)
{
/* Expected an error */
g_assert (reply == NULL);
}
else
{
/* Expected a reply of a particular format. */
gchar *str;
g_assert (reply != NULL);
str = g_variant_print (reply, TRUE);
g_assert_cmpstr (str, ==, (const gchar *) user_data);
g_free (str);
g_variant_unref (reply);
}
g_assert_cmpint (outstanding_cases, >, 0);
outstanding_cases--;
}
static void
test_async_case (GDBusConnection *connection,
const gchar *expected_reply,
const gchar *method,
const gchar *format_string,
...)
{
va_list ap;
va_start (ap, format_string);
g_dbus_connection_call (connection, g_dbus_connection_get_unique_name (connection), "/foo",
"org.freedesktop.DBus.Properties", method, g_variant_new_va (format_string, NULL, &ap),
NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, ensure_result_cb, (gpointer) expected_reply);
va_end (ap);
outstanding_cases++;
}
static void
test_async_properties (void)
{
GError *error = NULL;
guint registration_id, registration_id2;
static const GDBusInterfaceVTable vtable = {
test_async_method_call, NULL, NULL, { 0 }
};
c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
g_assert_no_error (error);
g_assert (c != NULL);
registration_id = g_dbus_connection_register_object (c,
"/foo",
(GDBusInterfaceInfo *) &foo_interface_info,
&vtable, NULL, NULL, &error);
g_assert_no_error (error);
g_assert (registration_id);
registration_id2 = g_dbus_connection_register_object (c,
"/foo",
(GDBusInterfaceInfo *) &foo2_interface_info,
&vtable, NULL, NULL, &error);
g_assert_no_error (error);
g_assert (registration_id);
test_async_case (c, NULL, "random", "()");
/* Test a variety of error cases */
test_async_case (c, NULL, "Get", "(si)", "wrong signature", 5);
test_async_case (c, NULL, "Get", "(ss)", "org.example.WrongInterface", "zzz");
test_async_case (c, NULL, "Get", "(ss)", "org.example.Foo", "NoSuchProperty");
test_async_case (c, NULL, "Get", "(ss)", "org.example.Foo", "NotReadable");
test_async_case (c, NULL, "Set", "(si)", "wrong signature", 5);
test_async_case (c, NULL, "Set", "(ssv)", "org.example.WrongInterface", "zzz", g_variant_new_string (""));
test_async_case (c, NULL, "Set", "(ssv)", "org.example.Foo", "NoSuchProperty", g_variant_new_string (""));
test_async_case (c, NULL, "Set", "(ssv)", "org.example.Foo", "NotWritable", g_variant_new_string (""));
test_async_case (c, NULL, "Set", "(ssv)", "org.example.Foo", "PropertyUno", g_variant_new_object_path ("/wrong"));
test_async_case (c, NULL, "GetAll", "(si)", "wrong signature", 5);
test_async_case (c, NULL, "GetAll", "(s)", "org.example.WrongInterface");
/* Make sure that we get no unexpected async property calls for com.example.Foo2 */
test_async_case (c, NULL, "Get", "(ss)", "org.example.Foo2", "zzz");
test_async_case (c, NULL, "Set", "(ssv)", "org.example.Foo2", "zzz", g_variant_new_string (""));
test_async_case (c, "(@a{sv} {},)", "GetAll", "(s)", "org.example.Foo2");
/* Now do the proper things */
test_async_case (c, "(<'PropertyUno'>,)", "Get", "(ss)", "org.example.Foo", "PropertyUno");
test_async_case (c, "(<'NotWritable'>,)", "Get", "(ss)", "org.example.Foo", "NotWritable");
test_async_case (c, "()", "Set", "(ssv)", "org.example.Foo", "PropertyUno", g_variant_new_string (""));
test_async_case (c, "()", "Set", "(ssv)", "org.example.Foo", "NotReadable", g_variant_new_string (""));
test_async_case (c, "({'PropertyUno': <'uno'>, 'NotWritable': <'notwrite'>},)", "GetAll", "(s)", "org.example.Foo");
while (outstanding_cases)
g_main_context_iteration (NULL, TRUE);
g_dbus_connection_unregister_object (c, registration_id);
g_dbus_connection_unregister_object (c, registration_id2);
g_object_unref (c);
}
typedef struct
{
GDBusConnection *connection; /* (owned) */
guint registration_id;
guint subtree_registration_id;
} ThreadedUnregistrationData;
static gpointer
unregister_thread_cb (gpointer user_data)
{
ThreadedUnregistrationData *data = user_data;
/* Sleeping here makes the race more likely to be hit, as it balances the
* time taken to set up the thread and unregister, with the time taken to
* make and handle the D-Bus call. This will likely change with future kernel
* versions, but there isnt a more deterministic synchronisation point that
* I can think of to use instead. */
usleep (330);
if (data->registration_id > 0)
g_assert_true (g_dbus_connection_unregister_object (data->connection, data->registration_id));
if (data->subtree_registration_id > 0)
g_assert_true (g_dbus_connection_unregister_subtree (data->connection, data->subtree_registration_id));
return NULL;
}
static void
async_result_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GAsyncResult **result_out = user_data;
*result_out = g_object_ref (result);
g_main_context_wakeup (NULL);
}
/* Returns %TRUE if this iteration resolved the race with the unregistration
* first, %FALSE if the call handler was invoked first. */
static gboolean
test_threaded_unregistration_iteration (gboolean subtree)
{
ThreadedUnregistrationData data = { NULL, 0, 0 };
ObjectRegistrationData object_registration_data = { 0, 0, 2 };
GError *local_error = NULL;
GThread *unregister_thread = NULL;
const gchar *object_path;
GVariant *value = NULL;
const gchar *value_str;
GAsyncResult *call_result = NULL;
gboolean unregistration_was_first;
data.connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error);
g_assert_no_error (local_error);
g_assert_nonnull (data.connection);
/* Register an object or a subtree */
if (!subtree)
{
data.registration_id = g_dbus_connection_register_object (data.connection,
"/foo/boss",
(GDBusInterfaceInfo *) &foo_interface_info,
&foo_vtable,
&object_registration_data,
on_object_unregistered,
&local_error);
g_assert_no_error (local_error);
g_assert_cmpint (data.registration_id, >, 0);
object_path = "/foo/boss";
}
else
{
data.subtree_registration_id = g_dbus_connection_register_subtree (data.connection,
"/foo/boss/executives",
&subtree_vtable,
G_DBUS_SUBTREE_FLAGS_NONE,
&object_registration_data,
on_subtree_unregistered,
&local_error);
g_assert_no_error (local_error);
g_assert_cmpint (data.subtree_registration_id, >, 0);
object_path = "/foo/boss/executives/vp0";
}
/* Allow the registrations to go through. */
g_main_context_iteration (NULL, FALSE);
/* Spawn a thread to unregister the object/subtree. This will race with
* the call we subsequently make. */
unregister_thread = g_thread_new ("unregister-object",
unregister_thread_cb, &data);
/* Call a method on the object (or an object in the subtree). The callback
* will be invoked in this main context. */
g_dbus_connection_call (data.connection,
g_dbus_connection_get_unique_name (data.connection),
object_path,
"org.example.Foo",
"Method1",
g_variant_new ("(s)", "winwinwin"),
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
async_result_cb,
&call_result);
while (call_result == NULL)
g_main_context_iteration (NULL, TRUE);
value = g_dbus_connection_call_finish (data.connection, call_result, &local_error);
/* The result of the method could either be an error (that the object doesnt
* exist) or a valid result, depending on how the thread was scheduled
* relative to the call. */
unregistration_was_first = (value == NULL);
if (value != NULL)
{
g_assert_no_error (local_error);
g_assert_true (g_variant_is_of_type (value, G_VARIANT_TYPE ("(s)")));
g_variant_get (value, "(&s)", &value_str);
g_assert_cmpstr (value_str, ==, "You passed the string 'winwinwin'. Jolly good!");
}
else
{
g_assert_null (value);
g_assert_error (local_error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD);
}
g_clear_pointer (&value, g_variant_unref);
g_clear_error (&local_error);
/* Tidy up. */
g_thread_join (g_steal_pointer (&unregister_thread));
g_clear_object (&call_result);
g_clear_object (&data.connection);
return unregistration_was_first;
}
static void
test_threaded_unregistration (gconstpointer test_data)
{
gboolean subtree = GPOINTER_TO_INT (test_data);
guint i;
guint n_iterations_unregistration_first = 0;
guint n_iterations_call_first = 0;
g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/2400");
g_test_summary ("Test that object/subtree unregistration from one thread "
"doesnt cause problems when racing with method callbacks "
"in another thread for that object or subtree");
/* Run iterations of the test until its likely weve hit the race. Limit the
* number of iterations so the test doesnt run forever if not. The choice of
* 100 is arbitrary. */
for (i = 0; i < 1000 && (n_iterations_unregistration_first < 100 || n_iterations_call_first < 100); i++)
{
if (test_threaded_unregistration_iteration (subtree))
n_iterations_unregistration_first++;
else
n_iterations_call_first++;
}
/* If the condition below is met, we probably failed to reproduce the race.
* Dont fail the test, though, as we cant always control whether we hit the
* race, and spurious test failures are annoying. */
if (n_iterations_unregistration_first < 100 ||
n_iterations_call_first < 100)
g_test_skip_printf ("Failed to reproduce race (%u iterations with unregistration first, %u with call first); skipping test",
n_iterations_unregistration_first, n_iterations_call_first);
}
/* ---------------------------------------------------------------------------------------------------- */
int
main (int argc,
char *argv[])
{
gint ret;
g_test_init (&argc, &argv, NULL);
/* all the tests rely on a shared main loop */
loop = g_main_loop_new (NULL, FALSE);
g_test_add_func ("/gdbus/object-registration", test_object_registration);
g_test_add_func ("/gdbus/object-registration-with-closures", test_object_registration_with_closures);
g_test_add_func ("/gdbus/registered-interfaces", test_registered_interfaces);
g_test_add_func ("/gdbus/async-properties", test_async_properties);
g_test_add_data_func ("/gdbus/threaded-unregistration/object", GINT_TO_POINTER (FALSE), test_threaded_unregistration);
g_test_add_data_func ("/gdbus/threaded-unregistration/subtree", GINT_TO_POINTER (TRUE), test_threaded_unregistration);
/* TODO: check that we spit out correct introspection data */
/* TODO: check that registering a whole subtree works */
ret = session_bus_run ();
g_main_loop_unref (loop);
return ret;
}