| /* GLib testing framework examples and tests |
| * |
| * Copyright (C) 2008-2010 Red Hat, Inc. |
| * Copyright (C) 2021 Frederic Martinsons |
| * |
| * 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> |
| * Author: Frederic Martinsons <frederic.martinsons@gmail.com> |
| */ |
| |
| #include <gio/gio.h> |
| #include <unistd.h> |
| |
| #include "gdbus-tests.h" |
| |
| /* all tests rely on a shared mainloop */ |
| static GMainLoop *loop; |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| /* Test that g_bus_own_name() works correctly */ |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| typedef struct |
| { |
| gboolean expect_null_connection; |
| guint num_bus_acquired; |
| guint num_acquired; |
| guint num_lost; |
| guint num_free_func; |
| } OwnNameData; |
| |
| static void |
| own_name_data_free_func (OwnNameData *data) |
| { |
| data->num_free_func++; |
| g_main_loop_quit (loop); |
| } |
| |
| static void |
| bus_acquired_handler (GDBusConnection *connection, |
| const gchar *name, |
| gpointer user_data) |
| { |
| OwnNameData *data = user_data; |
| g_dbus_connection_set_exit_on_close (connection, FALSE); |
| data->num_bus_acquired += 1; |
| g_main_loop_quit (loop); |
| } |
| |
| static void |
| name_acquired_handler (GDBusConnection *connection, |
| const gchar *name, |
| gpointer user_data) |
| { |
| OwnNameData *data = user_data; |
| data->num_acquired += 1; |
| g_main_loop_quit (loop); |
| } |
| |
| static void |
| name_lost_handler (GDBusConnection *connection, |
| const gchar *name, |
| gpointer user_data) |
| { |
| OwnNameData *data = user_data; |
| if (data->expect_null_connection) |
| { |
| g_assert (connection == NULL); |
| } |
| else |
| { |
| g_assert (connection != NULL); |
| g_dbus_connection_set_exit_on_close (connection, FALSE); |
| } |
| data->num_lost += 1; |
| g_main_loop_quit (loop); |
| } |
| |
| static void |
| test_bus_own_name (void) |
| { |
| guint id; |
| guint id2; |
| OwnNameData data; |
| OwnNameData data2; |
| const gchar *name; |
| GDBusConnection *c; |
| GError *error; |
| gboolean name_has_owner_reply; |
| GDBusConnection *c2; |
| GVariant *result; |
| |
| error = NULL; |
| name = "org.gtk.GDBus.Name1"; |
| |
| /* |
| * First check that name_lost_handler() is invoked if there is no bus. |
| * |
| * Also make sure name_lost_handler() isn't invoked when unowning the name. |
| */ |
| data.num_bus_acquired = 0; |
| data.num_free_func = 0; |
| data.num_acquired = 0; |
| data.num_lost = 0; |
| data.expect_null_connection = TRUE; |
| id = g_bus_own_name (G_BUS_TYPE_SESSION, |
| name, |
| G_BUS_NAME_OWNER_FLAGS_NONE, |
| bus_acquired_handler, |
| name_acquired_handler, |
| name_lost_handler, |
| &data, |
| (GDestroyNotify) own_name_data_free_func); |
| g_assert_cmpint (data.num_bus_acquired, ==, 0); |
| g_assert_cmpint (data.num_acquired, ==, 0); |
| g_assert_cmpint (data.num_lost, ==, 0); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data.num_bus_acquired, ==, 0); |
| g_assert_cmpint (data.num_acquired, ==, 0); |
| g_assert_cmpint (data.num_lost, ==, 1); |
| g_bus_unown_name (id); |
| g_assert_cmpint (data.num_acquired, ==, 0); |
| g_assert_cmpint (data.num_lost, ==, 1); |
| g_assert_cmpint (data.num_free_func, ==, 1); |
| |
| /* |
| * Bring up a bus, then own a name and check bus_acquired_handler() then name_acquired_handler() is invoked. |
| */ |
| session_bus_up (); |
| data.num_bus_acquired = 0; |
| data.num_acquired = 0; |
| data.num_lost = 0; |
| data.expect_null_connection = FALSE; |
| id = g_bus_own_name (G_BUS_TYPE_SESSION, |
| name, |
| G_BUS_NAME_OWNER_FLAGS_NONE, |
| bus_acquired_handler, |
| name_acquired_handler, |
| name_lost_handler, |
| &data, |
| (GDestroyNotify) own_name_data_free_func); |
| g_assert_cmpint (data.num_bus_acquired, ==, 0); |
| g_assert_cmpint (data.num_acquired, ==, 0); |
| g_assert_cmpint (data.num_lost, ==, 0); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data.num_bus_acquired, ==, 1); |
| g_assert_cmpint (data.num_acquired, ==, 0); |
| g_assert_cmpint (data.num_lost, ==, 0); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data.num_bus_acquired, ==, 1); |
| g_assert_cmpint (data.num_acquired, ==, 1); |
| g_assert_cmpint (data.num_lost, ==, 0); |
| |
| /* |
| * Check that the name was actually acquired. |
| */ |
| c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); |
| g_assert (c != NULL); |
| g_assert (!g_dbus_connection_is_closed (c)); |
| result = g_dbus_connection_call_sync (c, |
| "org.freedesktop.DBus", /* bus name */ |
| "/org/freedesktop/DBus", /* object path */ |
| "org.freedesktop.DBus", /* interface name */ |
| "NameHasOwner", /* method name */ |
| g_variant_new ("(s)", name), |
| G_VARIANT_TYPE ("(b)"), |
| G_DBUS_CALL_FLAGS_NONE, |
| -1, |
| NULL, |
| &error); |
| g_assert_no_error (error); |
| g_assert (result != NULL); |
| g_variant_get (result, "(b)", &name_has_owner_reply); |
| g_assert (name_has_owner_reply); |
| g_variant_unref (result); |
| |
| /* |
| * Stop owning the name - this should invoke our free func |
| */ |
| g_bus_unown_name (id); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data.num_free_func, ==, 2); |
| |
| /* |
| * Check that the name was actually released. |
| */ |
| result = g_dbus_connection_call_sync (c, |
| "org.freedesktop.DBus", /* bus name */ |
| "/org/freedesktop/DBus", /* object path */ |
| "org.freedesktop.DBus", /* interface name */ |
| "NameHasOwner", /* method name */ |
| g_variant_new ("(s)", name), |
| G_VARIANT_TYPE ("(b)"), |
| G_DBUS_CALL_FLAGS_NONE, |
| -1, |
| NULL, |
| &error); |
| g_assert_no_error (error); |
| g_assert (result != NULL); |
| g_variant_get (result, "(b)", &name_has_owner_reply); |
| g_assert (!name_has_owner_reply); |
| g_variant_unref (result); |
| |
| /* Now try owning the name and then immediately decide to unown the name */ |
| g_assert_cmpint (data.num_bus_acquired, ==, 1); |
| g_assert_cmpint (data.num_acquired, ==, 1); |
| g_assert_cmpint (data.num_lost, ==, 0); |
| g_assert_cmpint (data.num_free_func, ==, 2); |
| id = g_bus_own_name (G_BUS_TYPE_SESSION, |
| name, |
| G_BUS_NAME_OWNER_FLAGS_NONE, |
| bus_acquired_handler, |
| name_acquired_handler, |
| name_lost_handler, |
| &data, |
| (GDestroyNotify) own_name_data_free_func); |
| g_assert_cmpint (data.num_bus_acquired, ==, 1); |
| g_assert_cmpint (data.num_acquired, ==, 1); |
| g_assert_cmpint (data.num_lost, ==, 0); |
| g_assert_cmpint (data.num_free_func, ==, 2); |
| g_bus_unown_name (id); |
| g_assert_cmpint (data.num_bus_acquired, ==, 1); |
| g_assert_cmpint (data.num_acquired, ==, 1); |
| g_assert_cmpint (data.num_lost, ==, 0); |
| g_assert_cmpint (data.num_free_func, ==, 2); |
| g_main_loop_run (loop); /* the GDestroyNotify is called in idle because the bus is acquired in idle */ |
| g_assert_cmpint (data.num_free_func, ==, 3); |
| |
| /* |
| * Own the name again. |
| */ |
| data.num_bus_acquired = 0; |
| data.num_acquired = 0; |
| data.num_lost = 0; |
| data.expect_null_connection = FALSE; |
| id = g_bus_own_name_with_closures (G_BUS_TYPE_SESSION, |
| name, |
| G_BUS_NAME_OWNER_FLAGS_NONE, |
| g_cclosure_new (G_CALLBACK (bus_acquired_handler), |
| &data, |
| NULL), |
| g_cclosure_new (G_CALLBACK (name_acquired_handler), |
| &data, |
| NULL), |
| g_cclosure_new (G_CALLBACK (name_lost_handler), |
| &data, |
| (GClosureNotify) own_name_data_free_func)); |
| g_assert_cmpint (data.num_bus_acquired, ==, 0); |
| g_assert_cmpint (data.num_acquired, ==, 0); |
| g_assert_cmpint (data.num_lost, ==, 0); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data.num_bus_acquired, ==, 1); |
| g_assert_cmpint (data.num_acquired, ==, 0); |
| g_assert_cmpint (data.num_lost, ==, 0); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data.num_bus_acquired, ==, 1); |
| g_assert_cmpint (data.num_acquired, ==, 1); |
| g_assert_cmpint (data.num_lost, ==, 0); |
| |
| /* |
| * Try owning the name with another object on the same connection - this should |
| * fail because we already own the name. |
| */ |
| data2.num_free_func = 0; |
| data2.num_bus_acquired = 0; |
| data2.num_acquired = 0; |
| data2.num_lost = 0; |
| data2.expect_null_connection = FALSE; |
| id2 = g_bus_own_name (G_BUS_TYPE_SESSION, |
| name, |
| G_BUS_NAME_OWNER_FLAGS_NONE, |
| bus_acquired_handler, |
| name_acquired_handler, |
| name_lost_handler, |
| &data2, |
| (GDestroyNotify) own_name_data_free_func); |
| g_assert_cmpint (data2.num_bus_acquired, ==, 0); |
| g_assert_cmpint (data2.num_acquired, ==, 0); |
| g_assert_cmpint (data2.num_lost, ==, 0); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data2.num_bus_acquired, ==, 1); |
| g_assert_cmpint (data2.num_acquired, ==, 0); |
| g_assert_cmpint (data2.num_lost, ==, 0); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data2.num_bus_acquired, ==, 1); |
| g_assert_cmpint (data2.num_acquired, ==, 0); |
| g_assert_cmpint (data2.num_lost, ==, 1); |
| g_bus_unown_name (id2); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data2.num_bus_acquired, ==, 1); |
| g_assert_cmpint (data2.num_acquired, ==, 0); |
| g_assert_cmpint (data2.num_lost, ==, 1); |
| g_assert_cmpint (data2.num_free_func, ==, 1); |
| |
| /* |
| * Create a secondary (e.g. private) connection and try owning the name on that |
| * connection. This should fail both with and without _REPLACE because we |
| * didn't specify ALLOW_REPLACEMENT. |
| */ |
| c2 = _g_bus_get_priv (G_BUS_TYPE_SESSION, NULL, NULL); |
| g_assert (c2 != NULL); |
| g_assert (!g_dbus_connection_is_closed (c2)); |
| /* first without _REPLACE */ |
| data2.num_bus_acquired = 0; |
| data2.num_acquired = 0; |
| data2.num_lost = 0; |
| data2.expect_null_connection = FALSE; |
| data2.num_free_func = 0; |
| id2 = g_bus_own_name_on_connection (c2, |
| name, |
| G_BUS_NAME_OWNER_FLAGS_NONE, |
| name_acquired_handler, |
| name_lost_handler, |
| &data2, |
| (GDestroyNotify) own_name_data_free_func); |
| g_assert_cmpint (data2.num_bus_acquired, ==, 0); |
| g_assert_cmpint (data2.num_acquired, ==, 0); |
| g_assert_cmpint (data2.num_lost, ==, 0); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data2.num_bus_acquired, ==, 0); |
| g_assert_cmpint (data2.num_acquired, ==, 0); |
| g_assert_cmpint (data2.num_lost, ==, 1); |
| g_bus_unown_name (id2); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data2.num_bus_acquired, ==, 0); |
| g_assert_cmpint (data2.num_acquired, ==, 0); |
| g_assert_cmpint (data2.num_lost, ==, 1); |
| g_assert_cmpint (data2.num_free_func, ==, 1); |
| /* then with _REPLACE */ |
| data2.num_bus_acquired = 0; |
| data2.num_acquired = 0; |
| data2.num_lost = 0; |
| data2.expect_null_connection = FALSE; |
| data2.num_free_func = 0; |
| id2 = g_bus_own_name_on_connection (c2, |
| name, |
| G_BUS_NAME_OWNER_FLAGS_REPLACE, |
| name_acquired_handler, |
| name_lost_handler, |
| &data2, |
| (GDestroyNotify) own_name_data_free_func); |
| g_assert_cmpint (data2.num_bus_acquired, ==, 0); |
| g_assert_cmpint (data2.num_acquired, ==, 0); |
| g_assert_cmpint (data2.num_lost, ==, 0); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data2.num_bus_acquired, ==, 0); |
| g_assert_cmpint (data2.num_acquired, ==, 0); |
| g_assert_cmpint (data2.num_lost, ==, 1); |
| g_bus_unown_name (id2); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data2.num_bus_acquired, ==, 0); |
| g_assert_cmpint (data2.num_acquired, ==, 0); |
| g_assert_cmpint (data2.num_lost, ==, 1); |
| g_assert_cmpint (data2.num_free_func, ==, 1); |
| |
| /* |
| * Stop owning the name and grab it again with _ALLOW_REPLACEMENT. |
| */ |
| data.expect_null_connection = FALSE; |
| g_bus_unown_name (id); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data.num_bus_acquired, ==, 1); |
| g_assert_cmpint (data.num_acquired, ==, 1); |
| g_assert_cmpint (data.num_free_func, ==, 4); |
| /* grab it again */ |
| data.num_bus_acquired = 0; |
| data.num_acquired = 0; |
| data.num_lost = 0; |
| data.expect_null_connection = FALSE; |
| id = g_bus_own_name (G_BUS_TYPE_SESSION, |
| name, |
| G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, |
| bus_acquired_handler, |
| name_acquired_handler, |
| name_lost_handler, |
| &data, |
| (GDestroyNotify) own_name_data_free_func); |
| g_assert_cmpint (data.num_bus_acquired, ==, 0); |
| g_assert_cmpint (data.num_acquired, ==, 0); |
| g_assert_cmpint (data.num_lost, ==, 0); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data.num_bus_acquired, ==, 1); |
| g_assert_cmpint (data.num_acquired, ==, 0); |
| g_assert_cmpint (data.num_lost, ==, 0); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data.num_bus_acquired, ==, 1); |
| g_assert_cmpint (data.num_acquired, ==, 1); |
| g_assert_cmpint (data.num_lost, ==, 0); |
| |
| /* |
| * Now try to grab the name from the secondary connection. |
| * |
| */ |
| /* first without _REPLACE - this won't make us acquire the name */ |
| data2.num_bus_acquired = 0; |
| data2.num_acquired = 0; |
| data2.num_lost = 0; |
| data2.expect_null_connection = FALSE; |
| data2.num_free_func = 0; |
| id2 = g_bus_own_name_on_connection (c2, |
| name, |
| G_BUS_NAME_OWNER_FLAGS_NONE, |
| name_acquired_handler, |
| name_lost_handler, |
| &data2, |
| (GDestroyNotify) own_name_data_free_func); |
| g_assert_cmpint (data2.num_bus_acquired, ==, 0); |
| g_assert_cmpint (data2.num_acquired, ==, 0); |
| g_assert_cmpint (data2.num_lost, ==, 0); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data2.num_bus_acquired, ==, 0); |
| g_assert_cmpint (data2.num_acquired, ==, 0); |
| g_assert_cmpint (data2.num_lost, ==, 1); |
| g_bus_unown_name (id2); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data2.num_bus_acquired, ==, 0); |
| g_assert_cmpint (data2.num_acquired, ==, 0); |
| g_assert_cmpint (data2.num_lost, ==, 1); |
| g_assert_cmpint (data2.num_free_func, ==, 1); |
| /* then with _REPLACE - here we should acquire the name - e.g. owner should lose it |
| * and owner2 should acquire it */ |
| data2.num_bus_acquired = 0; |
| data2.num_acquired = 0; |
| data2.num_lost = 0; |
| data2.expect_null_connection = FALSE; |
| data2.num_free_func = 0; |
| id2 = g_bus_own_name_on_connection (c2, |
| name, |
| G_BUS_NAME_OWNER_FLAGS_REPLACE, |
| name_acquired_handler, |
| name_lost_handler, |
| &data2, |
| (GDestroyNotify) own_name_data_free_func); |
| g_assert_cmpint (data.num_acquired, ==, 1); |
| g_assert_cmpint (data.num_lost, ==, 0); |
| g_assert_cmpint (data2.num_acquired, ==, 0); |
| g_assert_cmpint (data2.num_lost, ==, 0); |
| /* wait for handlers for both owner and owner2 to fire */ |
| while (data.num_lost == 0 || data2.num_acquired == 0) |
| g_main_loop_run (loop); |
| g_assert_cmpint (data.num_acquired, ==, 1); |
| g_assert_cmpint (data.num_lost, ==, 1); |
| g_assert_cmpint (data2.num_acquired, ==, 1); |
| g_assert_cmpint (data2.num_lost, ==, 0); |
| g_assert_cmpint (data2.num_bus_acquired, ==, 0); |
| /* ok, make owner2 release the name - then wait for owner to automagically reacquire it */ |
| g_bus_unown_name (id2); |
| g_main_loop_run (loop); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data2.num_free_func, ==, 1); |
| g_assert_cmpint (data.num_acquired, ==, 2); |
| g_assert_cmpint (data.num_lost, ==, 1); |
| |
| /* |
| * Finally, nuke the bus and check name_lost_handler() is invoked. |
| * |
| */ |
| data.expect_null_connection = TRUE; |
| session_bus_stop (); |
| while (data.num_lost != 2) |
| g_main_loop_run (loop); |
| g_assert_cmpint (data.num_acquired, ==, 2); |
| g_assert_cmpint (data.num_lost, ==, 2); |
| g_bus_unown_name (id); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data.num_free_func, ==, 5); |
| |
| g_object_unref (c); |
| g_object_unref (c2); |
| |
| session_bus_down (); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| /* Test that g_bus_watch_name() works correctly */ |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| typedef struct |
| { |
| gboolean expect_null_connection; |
| guint num_acquired; |
| guint num_lost; |
| guint num_appeared; |
| guint num_vanished; |
| guint num_free_func; |
| } WatchNameData; |
| |
| typedef struct |
| { |
| WatchNameData data; |
| GDBusConnection *connection; |
| GMutex cond_mutex; |
| GCond cond; |
| gboolean started; |
| gboolean name_acquired; |
| gboolean ended; |
| gboolean unwatch_early; |
| GMutex mutex; |
| guint watch_id; |
| } WatchNameThreadData; |
| |
| static void |
| watch_name_data_free_func (WatchNameData *data) |
| { |
| data->num_free_func++; |
| g_main_loop_quit (loop); |
| } |
| |
| static void |
| w_bus_acquired_handler (GDBusConnection *connection, |
| const gchar *name, |
| gpointer user_data) |
| { |
| } |
| |
| static void |
| w_name_acquired_handler (GDBusConnection *connection, |
| const gchar *name, |
| gpointer user_data) |
| { |
| OwnNameData *data = user_data; |
| data->num_acquired += 1; |
| g_main_loop_quit (loop); |
| } |
| |
| static void |
| w_name_lost_handler (GDBusConnection *connection, |
| const gchar *name, |
| gpointer user_data) |
| { |
| OwnNameData *data = user_data; |
| data->num_lost += 1; |
| g_main_loop_quit (loop); |
| } |
| |
| static void |
| name_appeared_handler (GDBusConnection *connection, |
| const gchar *name, |
| const gchar *name_owner, |
| gpointer user_data) |
| { |
| WatchNameData *data = user_data; |
| |
| if (data->expect_null_connection) |
| { |
| g_assert (connection == NULL); |
| } |
| else |
| { |
| g_assert (connection != NULL); |
| g_dbus_connection_set_exit_on_close (connection, FALSE); |
| } |
| data->num_appeared += 1; |
| g_main_loop_quit (loop); |
| } |
| |
| static void |
| name_vanished_handler (GDBusConnection *connection, |
| const gchar *name, |
| gpointer user_data) |
| { |
| WatchNameData *data = user_data; |
| |
| if (data->expect_null_connection) |
| { |
| g_assert (connection == NULL); |
| } |
| else |
| { |
| g_assert (connection != NULL); |
| g_dbus_connection_set_exit_on_close (connection, FALSE); |
| } |
| data->num_vanished += 1; |
| g_main_loop_quit (loop); |
| } |
| |
| typedef struct |
| { |
| guint watcher_flags; |
| gboolean watch_with_closures; |
| gboolean existing_service; |
| } WatchNameTest; |
| |
| static const WatchNameTest watch_no_closures_no_flags = { |
| .watcher_flags = G_BUS_NAME_WATCHER_FLAGS_NONE, |
| .watch_with_closures = FALSE, |
| .existing_service = FALSE |
| }; |
| |
| static const WatchNameTest watch_no_closures_flags_auto_start = { |
| .watcher_flags = G_BUS_NAME_WATCHER_FLAGS_AUTO_START, |
| .watch_with_closures = FALSE, |
| .existing_service = FALSE |
| }; |
| |
| static const WatchNameTest watch_no_closures_flags_auto_start_service_exist = { |
| .watcher_flags = G_BUS_NAME_WATCHER_FLAGS_AUTO_START, |
| .watch_with_closures = FALSE, |
| .existing_service = TRUE |
| }; |
| |
| static const WatchNameTest watch_closures_no_flags = { |
| .watcher_flags = G_BUS_NAME_WATCHER_FLAGS_NONE, |
| .watch_with_closures = TRUE, |
| .existing_service = FALSE |
| }; |
| |
| static const WatchNameTest watch_closures_flags_auto_start = { |
| .watcher_flags = G_BUS_NAME_WATCHER_FLAGS_AUTO_START, |
| .watch_with_closures = TRUE, |
| .existing_service = FALSE |
| }; |
| |
| static void |
| stop_service (GDBusConnection *connection, |
| WatchNameData *data) |
| { |
| GError *error = NULL; |
| GDBusProxy *proxy = NULL; |
| GVariant *result = NULL; |
| |
| data->num_vanished = 0; |
| |
| proxy = g_dbus_proxy_new_sync (connection, |
| G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, |
| NULL, |
| "org.gtk.GDBus.FakeService", |
| "/org/gtk/GDBus/FakeService", |
| "org.gtk.GDBus.FakeService", |
| NULL, |
| &error); |
| g_assert_no_error (error); |
| |
| result = g_dbus_proxy_call_sync (proxy, |
| "Quit", |
| NULL, |
| G_DBUS_CALL_FLAGS_NO_AUTO_START, |
| 100, |
| NULL, |
| &error); |
| g_assert_no_error (error); |
| g_object_unref (proxy); |
| if (result) |
| g_variant_unref (result); |
| while (data->num_vanished == 0) |
| g_main_loop_run (loop); |
| } |
| |
| static void |
| test_bus_watch_name (gconstpointer d) |
| { |
| WatchNameData data; |
| OwnNameData own_data; |
| guint id; |
| guint owner_id; |
| GDBusConnection *connection; |
| const WatchNameTest *watch_name_test; |
| const gchar *name; |
| |
| watch_name_test = (WatchNameTest *) d; |
| |
| if (watch_name_test->existing_service) |
| { |
| name = "org.gtk.GDBus.FakeService"; |
| } |
| else |
| { |
| name = "org.gtk.GDBus.Name1"; |
| } |
| |
| /* |
| * First check that name_vanished_handler() is invoked if there is no bus. |
| * |
| * Also make sure name_vanished_handler() isn't invoked when unwatching the name. |
| */ |
| data.num_free_func = 0; |
| data.num_appeared = 0; |
| data.num_vanished = 0; |
| data.expect_null_connection = TRUE; |
| id = g_bus_watch_name (G_BUS_TYPE_SESSION, |
| name, |
| watch_name_test->watcher_flags, |
| name_appeared_handler, |
| name_vanished_handler, |
| &data, |
| (GDestroyNotify) watch_name_data_free_func); |
| g_assert_cmpint (data.num_appeared, ==, 0); |
| g_assert_cmpint (data.num_vanished, ==, 0); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data.num_appeared, ==, 0); |
| g_assert_cmpint (data.num_vanished, ==, 1); |
| g_bus_unwatch_name (id); |
| g_assert_cmpint (data.num_appeared, ==, 0); |
| g_assert_cmpint (data.num_vanished, ==, 1); |
| g_assert_cmpint (data.num_free_func, ==, 1); |
| data.num_free_func = 0; |
| |
| /* |
| * Now bring up a bus, own a name, and then start watching it. |
| */ |
| session_bus_up (); |
| /* own the name */ |
| own_data.num_free_func = 0; |
| own_data.num_acquired = 0; |
| own_data.num_lost = 0; |
| data.expect_null_connection = FALSE; |
| owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, |
| name, |
| G_BUS_NAME_OWNER_FLAGS_NONE, |
| w_bus_acquired_handler, |
| w_name_acquired_handler, |
| w_name_lost_handler, |
| &own_data, |
| (GDestroyNotify) own_name_data_free_func); |
| g_main_loop_run (loop); |
| g_assert_cmpint (own_data.num_acquired, ==, 1); |
| g_assert_cmpint (own_data.num_lost, ==, 0); |
| |
| connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); |
| g_assert (connection != NULL); |
| |
| /* now watch the name */ |
| data.num_appeared = 0; |
| data.num_vanished = 0; |
| if (watch_name_test->watch_with_closures) |
| { |
| id = g_bus_watch_name_on_connection_with_closures (connection, |
| name, |
| watch_name_test->watcher_flags, |
| g_cclosure_new (G_CALLBACK (name_appeared_handler), |
| &data, |
| NULL), |
| g_cclosure_new (G_CALLBACK (name_vanished_handler), |
| &data, |
| (GClosureNotify) watch_name_data_free_func)); |
| } |
| else |
| { |
| id = g_bus_watch_name_on_connection (connection, |
| name, |
| watch_name_test->watcher_flags, |
| name_appeared_handler, |
| name_vanished_handler, |
| &data, |
| (GDestroyNotify) watch_name_data_free_func); |
| } |
| g_assert_cmpint (data.num_appeared, ==, 0); |
| g_assert_cmpint (data.num_vanished, ==, 0); |
| g_main_loop_run (loop); |
| g_assert_cmpint (data.num_appeared, ==, 1); |
| g_assert_cmpint (data.num_vanished, ==, 0); |
| |
| /* |
| * Unwatch the name. |
| */ |
| g_bus_unwatch_name (id); |
| g_assert_cmpint (data.num_free_func, ==, 1); |
| |
| /* unown the name */ |
| g_bus_unown_name (owner_id); |
| g_main_loop_run (loop); |
| g_assert_cmpint (own_data.num_acquired, ==, 1); |
| g_assert_cmpint (own_data.num_free_func, ==, 1); |
| own_data.num_free_func = 0; |
| /* |
| * Create a watcher and then make a name be owned. |
| * |
| * This should trigger name_appeared_handler() ... |
| */ |
| /* watch the name */ |
| data.num_appeared = 0; |
| data.num_vanished = 0; |
| data.num_free_func = 0; |
| if (watch_name_test->watch_with_closures) |
| { |
| id = g_bus_watch_name_with_closures (G_BUS_TYPE_SESSION, |
| name, |
| watch_name_test->watcher_flags, |
| g_cclosure_new (G_CALLBACK (name_appeared_handler), |
| &data, |
| NULL), |
| g_cclosure_new (G_CALLBACK (name_vanished_handler), |
| &data, |
| (GClosureNotify) watch_name_data_free_func)); |
| } |
| else |
| { |
| id = g_bus_watch_name (G_BUS_TYPE_SESSION, |
| name, |
| watch_name_test->watcher_flags, |
| name_appeared_handler, |
| name_vanished_handler, |
| &data, |
| (GDestroyNotify) watch_name_data_free_func); |
| } |
| |
| g_assert_cmpint (data.num_appeared, ==, 0); |
| g_assert_cmpint (data.num_vanished, ==, 0); |
| g_main_loop_run (loop); |
| if (watch_name_test->existing_service) |
| { |
| g_assert_cmpint (data.num_appeared, ==, 1); |
| g_assert_cmpint (data.num_vanished, ==, 0); |
| } |
| else |
| { |
| g_assert_cmpint (data.num_appeared, ==, 0); |
| g_assert_cmpint (data.num_vanished, ==, 1); |
| } |
| |
| if (!watch_name_test->existing_service) |
| { |
| /* own the name */ |
| own_data.num_acquired = 0; |
| own_data.num_lost = 0; |
| own_data.expect_null_connection = FALSE; |
| owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, |
| name, |
| G_BUS_NAME_OWNER_FLAGS_NONE, |
| w_bus_acquired_handler, |
| w_name_acquired_handler, |
| w_name_lost_handler, |
| &own_data, |
| (GDestroyNotify) own_name_data_free_func); |
| while (own_data.num_acquired == 0 || data.num_appeared == 0) |
| g_main_loop_run (loop); |
| g_assert_cmpint (own_data.num_acquired, ==, 1); |
| g_assert_cmpint (own_data.num_lost, ==, 0); |
| g_assert_cmpint (data.num_appeared, ==, 1); |
| g_assert_cmpint (data.num_vanished, ==, 1); |
| } |
| |
| data.expect_null_connection = TRUE; |
| if (watch_name_test->existing_service) |
| { |
| data.expect_null_connection = FALSE; |
| stop_service (connection, &data); |
| } |
| g_object_unref (connection); |
| /* |
| * Nuke the bus and check that the name vanishes and is lost. |
| */ |
| session_bus_stop (); |
| if (!watch_name_test->existing_service) |
| { |
| g_main_loop_run (loop); |
| g_assert_cmpint (own_data.num_lost, ==, 1); |
| g_assert_cmpint (data.num_vanished, ==, 2); |
| } |
| else |
| { |
| g_assert_cmpint (own_data.num_lost, ==, 0); |
| g_assert_cmpint (data.num_vanished, ==, 1); |
| } |
| g_bus_unwatch_name (id); |
| g_assert_cmpint (data.num_free_func, ==, 1); |
| |
| if (!watch_name_test->existing_service) |
| { |
| g_bus_unown_name (owner_id); |
| g_main_loop_run (loop); |
| g_assert_cmpint (own_data.num_free_func, ==, 1); |
| } |
| session_bus_down (); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| /* Called in the same thread as watcher_thread() */ |
| static void |
| t_watch_name_data_free_func (WatchNameThreadData *thread_data) |
| { |
| thread_data->data.num_free_func++; |
| } |
| |
| /* Called in the same thread as watcher_thread() */ |
| static void |
| t_name_appeared_handler (GDBusConnection *connection, |
| const gchar *name, |
| const gchar *name_owner, |
| gpointer user_data) |
| { |
| WatchNameThreadData *thread_data = user_data; |
| thread_data->data.num_appeared += 1; |
| } |
| |
| /* Called in the same thread as watcher_thread() */ |
| static void |
| t_name_vanished_handler (GDBusConnection *connection, |
| const gchar *name, |
| gpointer user_data) |
| { |
| WatchNameThreadData *thread_data = user_data; |
| thread_data->data.num_vanished += 1; |
| } |
| |
| /* Called in the thread which constructed the GDBusConnection */ |
| static void |
| connection_closed_cb (GDBusConnection *connection, |
| gboolean remote_peer_vanished, |
| GError *error, |
| gpointer user_data) |
| { |
| WatchNameThreadData *thread_data = (WatchNameThreadData *) user_data; |
| if (thread_data->unwatch_early) |
| { |
| g_mutex_lock (&thread_data->mutex); |
| g_bus_unwatch_name (g_atomic_int_get (&thread_data->watch_id)); |
| g_atomic_int_set (&thread_data->watch_id, 0); |
| g_cond_signal (&thread_data->cond); |
| g_mutex_unlock (&thread_data->mutex); |
| } |
| } |
| |
| static gpointer |
| watcher_thread (gpointer user_data) |
| { |
| WatchNameThreadData *thread_data = user_data; |
| GMainContext *thread_context; |
| |
| thread_context = g_main_context_new (); |
| g_main_context_push_thread_default (thread_context); |
| |
| // Notify that the thread has started |
| g_mutex_lock (&thread_data->cond_mutex); |
| g_atomic_int_set (&thread_data->started, TRUE); |
| g_cond_signal (&thread_data->cond); |
| g_mutex_unlock (&thread_data->cond_mutex); |
| |
| // Wait for the main thread to own the name before watching it |
| g_mutex_lock (&thread_data->cond_mutex); |
| while (!g_atomic_int_get (&thread_data->name_acquired)) |
| g_cond_wait (&thread_data->cond, &thread_data->cond_mutex); |
| g_mutex_unlock (&thread_data->cond_mutex); |
| |
| thread_data->data.num_appeared = 0; |
| thread_data->data.num_vanished = 0; |
| thread_data->data.num_free_func = 0; |
| // g_signal_connect_after is important to have default handler be called before our code |
| g_signal_connect_after (thread_data->connection, "closed", G_CALLBACK (connection_closed_cb), thread_data); |
| |
| g_mutex_lock (&thread_data->mutex); |
| thread_data->watch_id = g_bus_watch_name_on_connection (thread_data->connection, |
| "org.gtk.GDBus.Name1", |
| G_BUS_NAME_WATCHER_FLAGS_NONE, |
| t_name_appeared_handler, |
| t_name_vanished_handler, |
| thread_data, |
| (GDestroyNotify) t_watch_name_data_free_func); |
| g_mutex_unlock (&thread_data->mutex); |
| |
| g_assert_cmpint (thread_data->data.num_appeared, ==, 0); |
| g_assert_cmpint (thread_data->data.num_vanished, ==, 0); |
| while (thread_data->data.num_appeared == 0) |
| g_main_context_iteration (thread_context, TRUE); |
| g_assert_cmpint (thread_data->data.num_appeared, ==, 1); |
| g_assert_cmpint (thread_data->data.num_vanished, ==, 0); |
| thread_data->data.num_appeared = 0; |
| |
| /* Close the connection and: |
| * - check that we had received a vanished event even begin in different thread |
| * - or check that unwatching the bus when a vanished had been scheduled |
| * make it correctly unscheduled (unwatch_early condition) |
| */ |
| g_dbus_connection_close_sync (thread_data->connection, NULL, NULL); |
| if (thread_data->unwatch_early) |
| { |
| // Wait for the main thread to iterate in order to have close connection handled |
| g_mutex_lock (&thread_data->mutex); |
| while (g_atomic_int_get (&thread_data->watch_id) != 0) |
| g_cond_wait (&thread_data->cond, &thread_data->mutex); |
| g_mutex_unlock (&thread_data->mutex); |
| |
| while (thread_data->data.num_free_func == 0) |
| g_main_context_iteration (thread_context, TRUE); |
| g_assert_cmpint (thread_data->data.num_vanished, ==, 0); |
| g_assert_cmpint (thread_data->data.num_appeared, ==, 0); |
| g_assert_cmpint (thread_data->data.num_free_func, ==, 1); |
| } |
| else |
| { |
| while (thread_data->data.num_vanished == 0) |
| { |
| /* |
| * Close of connection is treated in the context of the thread which |
| * creates the connection. We must run iteration on it (to have the 'closed' |
| * signal handled) and also run current thread loop to have name_vanished |
| * callback handled. |
| */ |
| g_main_context_iteration (thread_context, TRUE); |
| } |
| g_assert_cmpint (thread_data->data.num_vanished, ==, 1); |
| g_assert_cmpint (thread_data->data.num_appeared, ==, 0); |
| g_mutex_lock (&thread_data->mutex); |
| g_bus_unwatch_name (g_atomic_int_get (&thread_data->watch_id)); |
| g_atomic_int_set (&thread_data->watch_id, 0); |
| g_mutex_unlock (&thread_data->mutex); |
| while (thread_data->data.num_free_func == 0) |
| g_main_context_iteration (thread_context, TRUE); |
| g_assert_cmpint (thread_data->data.num_free_func, ==, 1); |
| } |
| |
| g_mutex_lock (&thread_data->cond_mutex); |
| thread_data->ended = TRUE; |
| g_main_context_wakeup (NULL); |
| g_cond_signal (&thread_data->cond); |
| g_mutex_unlock (&thread_data->cond_mutex); |
| |
| g_signal_handlers_disconnect_by_func (thread_data->connection, connection_closed_cb, thread_data); |
| g_object_unref (thread_data->connection); |
| g_main_context_pop_thread_default (thread_context); |
| g_main_context_unref (thread_context); |
| |
| g_mutex_lock (&thread_data->mutex); |
| g_assert_cmpint (thread_data->watch_id, ==, 0); |
| g_mutex_unlock (&thread_data->mutex); |
| return NULL; |
| } |
| |
| static void |
| watch_with_different_context (gboolean unwatch_early) |
| { |
| OwnNameData own_data; |
| WatchNameThreadData thread_data; |
| GDBusConnection *connection; |
| GThread *watcher; |
| guint id; |
| |
| session_bus_up (); |
| |
| connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); |
| g_assert (connection != NULL); |
| |
| g_mutex_init (&thread_data.mutex); |
| g_mutex_init (&thread_data.cond_mutex); |
| g_cond_init (&thread_data.cond); |
| thread_data.started = FALSE; |
| thread_data.name_acquired = FALSE; |
| thread_data.ended = FALSE; |
| thread_data.connection = g_object_ref (connection); |
| thread_data.unwatch_early = unwatch_early; |
| |
| // Create a thread which will watch a name and wait for it to be ready |
| g_mutex_lock (&thread_data.cond_mutex); |
| watcher = g_thread_new ("watcher", watcher_thread, &thread_data); |
| while (!g_atomic_int_get (&thread_data.started)) |
| g_cond_wait (&thread_data.cond, &thread_data.cond_mutex); |
| g_mutex_unlock (&thread_data.cond_mutex); |
| |
| own_data.num_acquired = 0; |
| own_data.num_lost = 0; |
| own_data.num_free_func = 0; |
| own_data.expect_null_connection = FALSE; |
| // Own the name to avoid direct name vanished in watcher thread |
| id = g_bus_own_name_on_connection (connection, |
| "org.gtk.GDBus.Name1", |
| G_BUS_NAME_OWNER_FLAGS_REPLACE, |
| w_name_acquired_handler, |
| w_name_lost_handler, |
| &own_data, |
| (GDestroyNotify) own_name_data_free_func); |
| while (own_data.num_acquired == 0) |
| g_main_context_iteration (NULL, TRUE); |
| g_assert_cmpint (own_data.num_acquired, ==, 1); |
| g_assert_cmpint (own_data.num_lost, ==, 0); |
| |
| // Wake the thread for it to begin watch |
| g_mutex_lock (&thread_data.cond_mutex); |
| g_atomic_int_set (&thread_data.name_acquired, TRUE); |
| g_cond_signal (&thread_data.cond); |
| g_mutex_unlock (&thread_data.cond_mutex); |
| |
| // Iterate the loop until thread is waking us up |
| while (!thread_data.ended) |
| g_main_context_iteration (NULL, TRUE); |
| |
| g_thread_join (watcher); |
| |
| g_bus_unown_name (id); |
| while (own_data.num_free_func == 0) |
| g_main_context_iteration (NULL, TRUE); |
| g_assert_cmpint (own_data.num_free_func, ==, 1); |
| |
| g_mutex_clear (&thread_data.mutex); |
| g_mutex_clear (&thread_data.cond_mutex); |
| g_cond_clear (&thread_data.cond); |
| |
| session_bus_stop (); |
| g_assert_true (g_dbus_connection_is_closed (connection)); |
| g_object_unref (connection); |
| session_bus_down (); |
| } |
| |
| static void |
| test_bus_watch_different_context (void) |
| { |
| watch_with_different_context (FALSE); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| static void |
| test_bus_unwatch_early (void) |
| { |
| g_test_bug ("https://gitlab.gnome.org/GNOME/glib/-/issues/604"); |
| watch_with_different_context (TRUE); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| static void |
| test_validate_names (void) |
| { |
| guint n; |
| static const struct |
| { |
| gboolean name; |
| gboolean unique; |
| gboolean interface; |
| const gchar *string; |
| } names[] = { |
| { 1, 0, 1, "valid.well_known.name"}, |
| { 1, 0, 0, "valid.well-known.name"}, |
| { 1, 1, 0, ":valid.unique.name"}, |
| { 0, 0, 0, "invalid.5well_known.name"}, |
| { 0, 0, 0, "4invalid.5well_known.name"}, |
| { 1, 1, 0, ":4valid.5unique.name"}, |
| { 0, 0, 0, ""}, |
| { 1, 0, 1, "very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.name1"}, /* 255 */ |
| { 0, 0, 0, "very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.very.long.name12"}, /* 256 - too long! */ |
| { 0, 0, 0, ".starts.with.a.dot"}, |
| { 0, 0, 0, "contains.invalid;.characters"}, |
| { 0, 0, 0, "contains.inva/lid.characters"}, |
| { 0, 0, 0, "contains.inva[lid.characters"}, |
| { 0, 0, 0, "contains.inva]lid.characters"}, |
| { 0, 0, 0, "contains.inva_æøå_lid.characters"}, |
| { 1, 1, 0, ":1.1"}, |
| }; |
| |
| for (n = 0; n < G_N_ELEMENTS (names); n++) |
| { |
| if (names[n].name) |
| g_assert (g_dbus_is_name (names[n].string)); |
| else |
| g_assert (!g_dbus_is_name (names[n].string)); |
| |
| if (names[n].unique) |
| g_assert (g_dbus_is_unique_name (names[n].string)); |
| else |
| g_assert (!g_dbus_is_unique_name (names[n].string)); |
| |
| if (names[n].interface) |
| { |
| g_assert (g_dbus_is_interface_name (names[n].string)); |
| g_assert (g_dbus_is_error_name (names[n].string)); |
| } |
| else |
| { |
| g_assert (!g_dbus_is_interface_name (names[n].string)); |
| g_assert (!g_dbus_is_error_name (names[n].string)); |
| } |
| } |
| } |
| |
| static void |
| assert_cmp_escaped_object_path (const gchar *s, |
| const gchar *correct_escaped) |
| { |
| gchar *escaped; |
| guint8 *unescaped; |
| |
| escaped = g_dbus_escape_object_path (s); |
| g_assert_cmpstr (escaped, ==, correct_escaped); |
| |
| g_free (escaped); |
| escaped = g_dbus_escape_object_path_bytestring ((const guint8 *) s); |
| g_assert_cmpstr (escaped, ==, correct_escaped); |
| |
| unescaped = g_dbus_unescape_object_path (escaped); |
| g_assert_cmpstr ((const gchar *) unescaped, ==, s); |
| |
| g_free (escaped); |
| g_free (unescaped); |
| } |
| |
| static void |
| test_escape_object_path (void) |
| { |
| assert_cmp_escaped_object_path ("Foo42", "Foo42"); |
| assert_cmp_escaped_object_path ("foo.bar.baz", "foo_2ebar_2ebaz"); |
| assert_cmp_escaped_object_path ("foo_bar_baz", "foo_5fbar_5fbaz"); |
| assert_cmp_escaped_object_path ("_", "_5f"); |
| assert_cmp_escaped_object_path ("__", "_5f_5f"); |
| assert_cmp_escaped_object_path ("", "_"); |
| assert_cmp_escaped_object_path (":1.42", "_3a1_2e42"); |
| assert_cmp_escaped_object_path ("a/b", "a_2fb"); |
| assert_cmp_escaped_object_path (" ", "_20"); |
| assert_cmp_escaped_object_path ("\n", "_0a"); |
| |
| g_assert_null (g_dbus_unescape_object_path ("_ii")); |
| g_assert_null (g_dbus_unescape_object_path ("döner")); |
| g_assert_null (g_dbus_unescape_object_path ("_00")); |
| g_assert_null (g_dbus_unescape_object_path ("_61")); |
| g_assert_null (g_dbus_unescape_object_path ("_ga")); |
| g_assert_null (g_dbus_unescape_object_path ("_ag")); |
| } |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| int |
| main (int argc, |
| char *argv[]) |
| { |
| gint ret; |
| |
| g_test_init (&argc, &argv, NULL); |
| |
| loop = g_main_loop_new (NULL, FALSE); |
| |
| g_test_dbus_unset (); |
| |
| g_test_add_func ("/gdbus/validate-names", test_validate_names); |
| g_test_add_func ("/gdbus/bus-own-name", test_bus_own_name); |
| g_test_add_data_func ("/gdbus/bus-watch-name", |
| &watch_no_closures_no_flags, |
| test_bus_watch_name); |
| g_test_add_data_func ("/gdbus/bus-watch-name-auto-start", |
| &watch_no_closures_flags_auto_start, |
| test_bus_watch_name); |
| g_test_add_data_func ("/gdbus/bus-watch-name-auto-start-service-exist", |
| &watch_no_closures_flags_auto_start_service_exist, |
| test_bus_watch_name); |
| g_test_add_data_func ("/gdbus/bus-watch-name-closures", |
| &watch_closures_no_flags, |
| test_bus_watch_name); |
| g_test_add_data_func ("/gdbus/bus-watch-name-closures-auto-start", |
| &watch_closures_flags_auto_start, |
| test_bus_watch_name); |
| g_test_add_func ("/gdbus/bus-watch-different-context", test_bus_watch_different_context); |
| g_test_add_func ("/gdbus/bus-unwatch-early", test_bus_unwatch_early); |
| g_test_add_func ("/gdbus/escape-object-path", test_escape_object_path); |
| ret = g_test_run(); |
| |
| g_main_loop_unref (loop); |
| |
| return ret; |
| } |