glib/gio/tests/gdbus-names.c
Frederic Martinsons 59e999fd0a Add two test cases which covers disconnection scenario in another thread
1) Check that schedule_call_in_idle code branch of gdbusnamewatching.c
   is working to call vanished handler in the thread which had watched the name
2) Check cancellation of vanished handler if the name is unwatched before
   vanished callback is dispatched.

Closes #2011

Signed-off-by: Frederic Martinsons <frederic.martinsons@sigfox.com>
2021-03-17 08:36:47 +01:00

1254 lines
44 KiB
C

/* 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));
else
g_assert (!g_dbus_is_interface_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;
}