Merge branch '2011-add-dbus-watch-name-tests' into 'master'

Resolve "Add additional unit tests for D-Bus name watching"

Closes #2011

See merge request GNOME/glib!1904
This commit is contained in:
Philip Withnall 2021-03-18 21:10:19 +00:00
commit 2fd21175e7

View File

@ -1,6 +1,7 @@
/* 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
@ -16,6 +17,7 @@
* 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>
@ -495,6 +497,20 @@ typedef struct
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)
{
@ -514,7 +530,7 @@ w_name_acquired_handler (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
WatchNameData *data = user_data;
OwnNameData *data = user_data;
data->num_acquired += 1;
g_main_loop_quit (loop);
}
@ -524,7 +540,7 @@ w_name_lost_handler (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
WatchNameData *data = user_data;
OwnNameData *data = user_data;
data->num_lost += 1;
g_main_loop_quit (loop);
}
@ -613,6 +629,7 @@ stop_service (GDBusConnection *connection,
{
GError *error = NULL;
GDBusProxy *proxy = NULL;
GVariant *result = NULL;
data->num_vanished = 0;
@ -626,15 +643,17 @@ stop_service (GDBusConnection *connection,
&error);
g_assert_no_error (error);
g_dbus_proxy_call_sync (proxy,
"Quit",
NULL,
G_DBUS_CALL_FLAGS_NO_AUTO_START,
100,
NULL,
&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);
}
@ -643,6 +662,7 @@ static void
test_bus_watch_name (gconstpointer d)
{
WatchNameData data;
OwnNameData own_data;
guint id;
guint owner_id;
GDBusConnection *connection;
@ -685,15 +705,16 @@ test_bus_watch_name (gconstpointer d)
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 */
data.num_free_func = 0;
data.num_acquired = 0;
data.num_lost = 0;
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,
@ -701,11 +722,11 @@ test_bus_watch_name (gconstpointer d)
w_bus_acquired_handler,
w_name_acquired_handler,
w_name_lost_handler,
&data,
(GDestroyNotify) watch_name_data_free_func);
&own_data,
(GDestroyNotify) own_name_data_free_func);
g_main_loop_run (loop);
g_assert_cmpint (data.num_acquired, ==, 1);
g_assert_cmpint (data.num_lost, ==, 0);
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);
@ -750,9 +771,9 @@ test_bus_watch_name (gconstpointer d)
/* unown the name */
g_bus_unown_name (owner_id);
g_main_loop_run (loop);
g_assert_cmpint (data.num_acquired, ==, 1);
g_assert_cmpint (data.num_free_func, ==, 2);
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.
*
@ -802,21 +823,21 @@ test_bus_watch_name (gconstpointer d)
if (!watch_name_test->existing_service)
{
/* own the name */
data.num_acquired = 0;
data.num_lost = 0;
data.expect_null_connection = FALSE;
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,
&data,
(GDestroyNotify) watch_name_data_free_func);
while (data.num_acquired == 0 || data.num_appeared == 0)
&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 (data.num_acquired, ==, 1);
g_assert_cmpint (data.num_lost, ==, 0);
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);
}
@ -835,12 +856,12 @@ test_bus_watch_name (gconstpointer d)
if (!watch_name_test->existing_service)
{
g_main_loop_run (loop);
g_assert_cmpint (data.num_lost, ==, 1);
g_assert_cmpint (own_data.num_lost, ==, 1);
g_assert_cmpint (data.num_vanished, ==, 2);
}
else
{
g_assert_cmpint (data.num_lost, ==, 0);
g_assert_cmpint (own_data.num_lost, ==, 0);
g_assert_cmpint (data.num_vanished, ==, 1);
}
g_bus_unwatch_name (id);
@ -850,13 +871,255 @@ test_bus_watch_name (gconstpointer d)
{
g_bus_unown_name (owner_id);
g_main_loop_run (loop);
g_assert_cmpint (data.num_free_func, ==, 2);
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)
{
@ -979,6 +1242,8 @@ main (int argc,
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();