glib/gio/tests/gdbus-export.c
Ryan Lortie f754c4e85b GDBusConnection: allow async property handling
The existing advice in the documentation to "simply" register the
"org.freedesktop.DBus.Properties" interface if you want to handle
properties asynchronously is pretty unreasonable.  If you want to handle
this interface you have to deal with all properties for all interfaces
on the path, and you have to do all of the checking for yourself.  You
also have to provide your own introspection data.

Introduce a new convention for dealing with properties asynchronously.

If the user provides NULL for their get_property() or set_property()
functions in the vtable and has properties registered then the
properties are sent to the method_call() handler.  We get lucky here
that this function takes an "interface_name" parameter that we can set
to "org.freedesktop.DBus.Properties".

We also do the user the favour of setting the GDBusPropertyInfo on the
GDBusMethodInvocation for their convenience (for much the same reasons
as they might want the already-available GDBusMethodInfo).

Add a testcase as well as a bunch of documentation about this new
feature.

https://bugzilla.gnome.org/show_bug.cgi?id=698375
2013-06-22 13:38:31 -04:00

1731 lines
61 KiB
C

/* 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 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., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* 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,
};
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'",
property_name, s);
g_free (s);
return FALSE;
}
static const GDBusInterfaceVTable foo_vtable =
{
foo_method_call,
foo_get_property,
foo_set_property
};
/* -------------------- */
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
};
/* ---------------------------------------------------------------------------------------------------- */
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 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);
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_memdup (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
};
/* -------------------- */
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_memdup (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
};
/* -------------------- */
static gpointer
test_dispatch_thread_func (gpointer user_data)
{
const gchar *object_path = user_data;
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);
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 'NotReadable' to the property ''But Writable you are!''");
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)
{
GThread *thread;
/* run this in a thread to avoid deadlocks */
thread = g_thread_new ("test_dispatch",
test_dispatch_thread_func,
(gpointer) object_path);
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 ();
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, "lol");
g_ptr_array_add (dyna_data, "cat");
g_ptr_array_add (dyna_data, "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], ==, "lol");
g_assert_cmpstr (nodes[1], ==, "cat");
g_assert_cmpstr (nodes[2], ==, "cheezburger");
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], ==, "lol");
g_assert_cmpstr (nodes[1], ==, "cat");
g_assert_cmpstr (nodes[2], ==, "cheezburger");
g_assert_cmpstr (nodes[3], ==, "dynamicallycreated");
g_strfreev (nodes);
g_assert_cmpint (count_interfaces (c, "/foo/dyna/dynamicallycreated"), ==, 4);
/* now check that the object hierarachy 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");
test_dispatch ("/foo/boss/executives/vp0");
/* 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 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_print ("expected ");
for (i = 0; interfaces[i]; i++)
g_print ("%s ", interfaces[i]);
g_print ("\ngot ");
for (i = 0; node_info->interfaces[i]; i++)
g_print ("%s ", node_info->interfaces[i]->name);
g_print ("\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);
/* 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;
static const GDBusInterfaceVTable vtable = {
test_async_method_call, NULL, 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",
(GDBusInterfaceInfo *) &foo_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");
/* 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_object_unref (c);
}
/* ---------------------------------------------------------------------------------------------------- */
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);
session_bus_up ();
g_test_add_func ("/gdbus/object-registration", test_object_registration);
g_test_add_func ("/gdbus/registered-interfaces", test_registered_interfaces);
g_test_add_func ("/gdbus/async-properties", test_async_properties);
/* TODO: check that we spit out correct introspection data */
/* TODO: check that registering a whole subtree works */
ret = g_test_run();
/* tear down bus */
session_bus_down ();
return ret;
}