/* 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 */ #include #include #include #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_TYPE_DBUS_PROXY, 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_TYPE_DBUS_PROXY, 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_TYPE_DBUS_PROXY, 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_TYPE_DBUS_PROXY, 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 GPtrArray * subtree_introspect (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *node, gpointer user_data) { GPtrArray *interfaces; /* VPs implement the Foo interface, EVPs implement the Bar interface. The root * does not implement any interfaces */ interfaces = g_ptr_array_new (); if (g_str_has_prefix (node, "vp")) { g_ptr_array_add (interfaces, (gpointer) &foo_interface_info); } else if (g_str_has_prefix (node, "evp")) { g_ptr_array_add (interfaces, (gpointer) &bar_interface_info); } else if (g_strcmp0 (node, "/") == 0) { /* do nothing */ } else { g_assert_not_reached (); } return interfaces; } 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 GPtrArray * dynamic_subtree_introspect (GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *node, gpointer user_data) { GPtrArray *interfaces; /* All nodes (including the root node) implements the Dyna interface */ interfaces = g_ptr_array_new (); g_ptr_array_add (interfaces, (gpointer) &dyna_interface_info); return interfaces; } 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_TYPE_DBUS_PROXY, 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); 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; GError *error; /* run this in a thread to avoid deadlocks */ error = NULL; thread = g_thread_create (test_dispatch_thread_func, (gpointer) object_path, TRUE, &error); g_assert_no_error (error); g_assert (thread != NULL); 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 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", &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", &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", &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/worker2", &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", &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", &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", &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", &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_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", &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", &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_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", &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", &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), ==, 4); g_assert (_g_strv_has_string ((const gchar* const *) nodes, "worker1")); 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); /* 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", session_bus_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_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, 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_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); } /* ---------------------------------------------------------------------------------------------------- */ int main (int argc, char *argv[]) { gint ret; g_type_init (); g_test_init (&argc, &argv, NULL); /* all the tests rely on a shared main loop */ loop = g_main_loop_new (NULL, FALSE); /* all the tests use a session bus with a well-known address that we can bring up and down * using session_bus_up() and session_bus_down(). */ g_unsetenv ("DISPLAY"); g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE); session_bus_up (); /* TODO: wait a bit for the bus to come up.. ideally session_bus_up() won't return * until one can connect to the bus but that's not how things work right now */ usleep (500 * 1000); g_test_add_func ("/gdbus/object-registration", test_object_registration); /* 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; }