mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-30 17:36:16 +01:00
d68f8012b2
Either child_watch_source or timeout_source will already have been destroyed after we finish the loop, and it's not safe to call g_source_destroy() on it a second time unless we're still holding a ref on it.
604 lines
15 KiB
C
604 lines
15 KiB
C
#include <gio/gio.h>
|
|
#ifdef G_OS_UNIX
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <signal.h>
|
|
#endif
|
|
|
|
#include "gdbus-sessionbus.h"
|
|
|
|
static gint appeared;
|
|
static gint disappeared;
|
|
static gint changed;
|
|
|
|
static void
|
|
name_appeared (GDBusConnection *connection,
|
|
const gchar *name,
|
|
const gchar *name_owner,
|
|
gpointer user_data)
|
|
{
|
|
GMainLoop *loop = user_data;
|
|
|
|
appeared++;
|
|
|
|
if (loop)
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static void
|
|
name_disappeared (GDBusConnection *connection,
|
|
const gchar *name,
|
|
gpointer user_data)
|
|
{
|
|
GMainLoop *loop = user_data;
|
|
|
|
disappeared++;
|
|
|
|
if (loop)
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
#ifdef G_OS_UNIX
|
|
void
|
|
child_setup_pipe (gpointer user_data)
|
|
{
|
|
int *fds = user_data;
|
|
|
|
close (fds[0]);
|
|
dup2 (fds[1], 3);
|
|
g_setenv ("_G_TEST_SLAVE_FD", "3", TRUE);
|
|
close (fds[1]);
|
|
}
|
|
#endif
|
|
|
|
static gboolean
|
|
spawn_async_with_monitor_pipe (const gchar *argv[], GPid *pid, int *fd)
|
|
{
|
|
#ifdef G_OS_UNIX
|
|
int fds[2];
|
|
gboolean result;
|
|
|
|
pipe (fds);
|
|
|
|
result = g_spawn_async (NULL, (char**)argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, child_setup_pipe, &fds, pid, NULL);
|
|
close (fds[1]);
|
|
*fd = fds[0];
|
|
return result;
|
|
#else
|
|
*fd = -1;
|
|
return g_spawn_async (NULL, argv, 0, NULL, NULL, pid, NULL);
|
|
#endif
|
|
}
|
|
|
|
static gboolean
|
|
start_application (GPid *pid, int *fd)
|
|
{
|
|
const gchar *argv[] = { "./testapp", NULL };
|
|
|
|
g_assert (spawn_async_with_monitor_pipe (argv, pid, fd));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
typedef struct {
|
|
GMainContext *context;
|
|
GSource *child_watch;
|
|
GSource *timeout;
|
|
GPid pid;
|
|
int fd;
|
|
|
|
gboolean child_exited;
|
|
GMainLoop *loop;
|
|
} AwaitChildTerminationData;
|
|
|
|
static void
|
|
on_child_termination_exited (GPid pid,
|
|
gint status,
|
|
gpointer user_data)
|
|
{
|
|
AwaitChildTerminationData *data = user_data;
|
|
data->child_exited = TRUE;
|
|
g_spawn_close_pid (data->pid);
|
|
g_main_loop_quit (data->loop);
|
|
}
|
|
|
|
static gboolean
|
|
on_child_termination_timeout (gpointer user_data)
|
|
{
|
|
AwaitChildTerminationData *data = user_data;
|
|
g_main_loop_quit (data->loop);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
await_child_termination_init (AwaitChildTerminationData *data,
|
|
GPid pid,
|
|
int fd)
|
|
{
|
|
data->context = g_main_context_get_thread_default ();
|
|
data->child_exited = FALSE;
|
|
data->pid = pid;
|
|
data->fd = fd;
|
|
}
|
|
|
|
static void
|
|
await_child_termination_terminate (AwaitChildTerminationData *data)
|
|
{
|
|
#ifdef G_OS_UNIX
|
|
kill (data->pid, SIGTERM);
|
|
close (data->fd);
|
|
#endif
|
|
}
|
|
|
|
static gboolean
|
|
await_child_termination_run (AwaitChildTerminationData *data)
|
|
{
|
|
GSource *timeout_source;
|
|
GSource *child_watch_source;
|
|
|
|
data->loop = g_main_loop_new (data->context, FALSE);
|
|
|
|
child_watch_source = g_child_watch_source_new (data->pid);
|
|
g_source_set_callback (child_watch_source, (GSourceFunc) on_child_termination_exited, data, NULL);
|
|
g_source_attach (child_watch_source, data->context);
|
|
|
|
timeout_source = g_timeout_source_new_seconds (5);
|
|
g_source_set_callback (timeout_source, on_child_termination_timeout, data, NULL);
|
|
g_source_attach (timeout_source, data->context);
|
|
|
|
g_main_loop_run (data->loop);
|
|
|
|
g_source_destroy (child_watch_source);
|
|
g_source_unref (child_watch_source);
|
|
g_source_destroy (timeout_source);
|
|
g_source_unref (timeout_source);
|
|
|
|
g_main_loop_unref (data->loop);
|
|
|
|
return data->child_exited;
|
|
}
|
|
|
|
static void
|
|
terminate_child_sync (GPid pid, int fd)
|
|
{
|
|
AwaitChildTerminationData data;
|
|
|
|
await_child_termination_init (&data, pid, fd);
|
|
await_child_termination_terminate (&data);
|
|
await_child_termination_run (&data);
|
|
}
|
|
|
|
typedef void (*RunWithApplicationFunc) (void);
|
|
|
|
typedef struct {
|
|
GMainLoop *loop;
|
|
RunWithApplicationFunc func;
|
|
guint timeout_id;
|
|
} RunWithAppNameAppearedData;
|
|
|
|
static void
|
|
on_run_with_application_name_appeared (GDBusConnection *connection,
|
|
const gchar *name,
|
|
const gchar *name_owner,
|
|
gpointer user_data)
|
|
{
|
|
RunWithAppNameAppearedData *data = user_data;
|
|
|
|
data->func ();
|
|
|
|
g_main_loop_quit (data->loop);
|
|
}
|
|
|
|
static gboolean
|
|
on_run_with_application_timeout (gpointer user_data)
|
|
{
|
|
RunWithAppNameAppearedData *data = user_data;
|
|
data->timeout_id = 0;
|
|
g_error ("Timed out starting testapp");
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
run_with_application (RunWithApplicationFunc test_func)
|
|
{
|
|
GMainLoop *loop;
|
|
RunWithAppNameAppearedData data;
|
|
gint watch;
|
|
GPid main_pid;
|
|
gint main_fd;
|
|
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
|
|
data.timeout_id = 0;
|
|
data.func = test_func;
|
|
data.loop = loop;
|
|
|
|
watch = g_bus_watch_name (G_BUS_TYPE_SESSION,
|
|
"org.gtk.test.app",
|
|
0,
|
|
on_run_with_application_name_appeared,
|
|
NULL,
|
|
&data,
|
|
NULL);
|
|
|
|
data.timeout_id = g_timeout_add_seconds (5, on_run_with_application_timeout, &data);
|
|
|
|
start_application (&main_pid, &main_fd);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
if (data.timeout_id)
|
|
{
|
|
g_source_remove (data.timeout_id);
|
|
data.timeout_id = 0;
|
|
}
|
|
|
|
g_main_loop_unref (loop);
|
|
|
|
g_bus_unwatch_name (watch);
|
|
|
|
terminate_child_sync (main_pid, main_fd);
|
|
}
|
|
|
|
/* This test starts an application, checks that its name appears
|
|
* on the bus, then starts it again and checks that the second
|
|
* instance exits right away.
|
|
*/
|
|
static void
|
|
test_unique_on_app_appeared (void)
|
|
{
|
|
GPid sub_pid;
|
|
int sub_fd;
|
|
int watch;
|
|
AwaitChildTerminationData data;
|
|
|
|
appeared = 0;
|
|
disappeared = 0;
|
|
|
|
watch = g_bus_watch_name (G_BUS_TYPE_SESSION,
|
|
"org.gtk.test.app",
|
|
0,
|
|
name_appeared,
|
|
name_disappeared,
|
|
NULL,
|
|
NULL);
|
|
|
|
start_application (&sub_pid, &sub_fd);
|
|
await_child_termination_init (&data, sub_pid, sub_fd);
|
|
await_child_termination_run (&data);
|
|
|
|
g_bus_unwatch_name (watch);
|
|
|
|
g_assert_cmpint (appeared, ==, 1);
|
|
g_assert_cmpint (disappeared, ==, 0);
|
|
}
|
|
|
|
static void
|
|
test_unique (void)
|
|
{
|
|
run_with_application (test_unique_on_app_appeared);
|
|
}
|
|
|
|
static void
|
|
on_name_disappeared_quit (GDBusConnection *connection,
|
|
const gchar *name,
|
|
gpointer user_data)
|
|
{
|
|
GMainLoop *loop = user_data;
|
|
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static GVariant *
|
|
create_empty_vardict ()
|
|
{
|
|
GVariantBuilder builder;
|
|
|
|
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
|
|
return g_variant_builder_end (&builder);
|
|
}
|
|
|
|
static gboolean
|
|
call_quit (gpointer data)
|
|
{
|
|
GDBusConnection *connection;
|
|
GError *error = NULL;
|
|
GVariant *res;
|
|
|
|
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
|
|
res = g_dbus_connection_call_sync (connection,
|
|
"org.gtk.test.app",
|
|
"/org/gtk/test/app",
|
|
"org.gtk.Application",
|
|
"Quit",
|
|
g_variant_new ("(@a{sv})", create_empty_vardict ()),
|
|
NULL,
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
-1,
|
|
NULL,
|
|
&error);
|
|
if (error)
|
|
{
|
|
g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_NO_REPLY);
|
|
g_error_free (error);
|
|
}
|
|
|
|
if (res)
|
|
g_variant_unref (res);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* This test starts an application, checks that its name appears on
|
|
* the bus, then calls Quit, and verifies that the name disappears and
|
|
* the application exits.
|
|
*/
|
|
static void
|
|
test_quit_on_app_appeared (void)
|
|
{
|
|
GMainLoop *loop;
|
|
int quit_disappeared_watch;
|
|
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
quit_disappeared_watch = g_bus_watch_name (G_BUS_TYPE_SESSION,
|
|
"org.gtk.test.app",
|
|
0,
|
|
NULL,
|
|
on_name_disappeared_quit,
|
|
loop,
|
|
NULL);
|
|
call_quit (NULL);
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_bus_unwatch_name (quit_disappeared_watch);
|
|
|
|
g_main_loop_unref (loop);
|
|
}
|
|
|
|
static void
|
|
test_quit (void)
|
|
{
|
|
run_with_application (test_quit_on_app_appeared);
|
|
}
|
|
|
|
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 **
|
|
list_actions (void)
|
|
{
|
|
GDBusConnection *connection;
|
|
GVariant *res;
|
|
gchar **strv;
|
|
gchar *str;
|
|
GVariantIter *iter;
|
|
gint i;
|
|
|
|
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
|
|
res = g_dbus_connection_call_sync (connection,
|
|
"org.gtk.test.app",
|
|
"/org/gtk/test/app",
|
|
"org.gtk.Application",
|
|
"ListActions",
|
|
NULL,
|
|
NULL,
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
-1,
|
|
NULL,
|
|
NULL);
|
|
|
|
strv = g_new0 (gchar *, 32);
|
|
g_variant_get (res, "(a{s(sb)})", &iter);
|
|
i = 0;
|
|
while (g_variant_iter_loop (iter, "{s(sb)}", &str, NULL, NULL))
|
|
{
|
|
strv[i] = g_strdup (str);
|
|
i++;
|
|
g_assert (i < 32);
|
|
}
|
|
g_variant_iter_free (iter);
|
|
|
|
strv[i] = NULL;
|
|
|
|
g_variant_unref (res);
|
|
g_object_unref (connection);
|
|
|
|
return strv;
|
|
}
|
|
|
|
static void
|
|
test_list_actions_on_app_appeared (void)
|
|
{
|
|
gchar **actions;
|
|
|
|
actions = list_actions ();
|
|
|
|
g_assert (g_strv_length (actions) == 2);
|
|
g_assert (_g_strv_has_string ((const char *const *)actions, "action1"));
|
|
g_assert (_g_strv_has_string ((const char *const *)actions, "action2"));
|
|
|
|
g_strfreev (actions);
|
|
}
|
|
|
|
/* This test start an application, waits for its name to appear on
|
|
* the bus, then calls ListActions, and verifies that it gets the expected
|
|
* actions back.
|
|
*/
|
|
static void
|
|
test_list_actions (void)
|
|
{
|
|
run_with_application (test_list_actions_on_app_appeared);
|
|
}
|
|
|
|
static gboolean
|
|
invoke_action (gpointer user_data)
|
|
{
|
|
const gchar *action = user_data;
|
|
GDBusConnection *connection;
|
|
GVariant *res;
|
|
|
|
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
|
|
res = g_dbus_connection_call_sync (connection,
|
|
"org.gtk.test.app",
|
|
"/org/gtk/test/app",
|
|
"org.gtk.Application",
|
|
"InvokeAction",
|
|
g_variant_new ("(s@a{sv})",
|
|
action,
|
|
create_empty_vardict ()),
|
|
NULL,
|
|
G_DBUS_CALL_FLAGS_NONE,
|
|
-1,
|
|
NULL,
|
|
NULL);
|
|
if (res)
|
|
g_variant_unref (res);
|
|
g_object_unref (connection);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* This test starts an application, waits for it to appear,
|
|
* then invokes 'action1' and checks that it causes the application
|
|
* to exit with an exit code of 1.
|
|
*/
|
|
static void
|
|
test_invoke (void)
|
|
{
|
|
GMainLoop *loop;
|
|
int quit_disappeared_watch;
|
|
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
|
|
quit_disappeared_watch = g_bus_watch_name (G_BUS_TYPE_SESSION,
|
|
"org.gtk.test.app",
|
|
0,
|
|
NULL,
|
|
on_name_disappeared_quit,
|
|
loop,
|
|
NULL);
|
|
|
|
g_timeout_add (0, invoke_action, "action1");
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_bus_unwatch_name (quit_disappeared_watch);
|
|
}
|
|
|
|
static void
|
|
test_remote_on_application_appeared (void)
|
|
{
|
|
GPid sub_pid;
|
|
int sub_fd;
|
|
AwaitChildTerminationData data;
|
|
gchar *argv[] = { "./testapp", "--non-unique", NULL };
|
|
|
|
spawn_async_with_monitor_pipe ((const char **) argv, &sub_pid, &sub_fd);
|
|
|
|
await_child_termination_init (&data, sub_pid, sub_fd);
|
|
await_child_termination_run (&data);
|
|
}
|
|
|
|
static void
|
|
test_remote (void)
|
|
{
|
|
run_with_application (test_remote_on_application_appeared);
|
|
}
|
|
|
|
static void
|
|
actions_changed (GDBusConnection *connection,
|
|
const gchar *sender_name,
|
|
const gchar *object_path,
|
|
const gchar *interface_name,
|
|
const gchar *signal_name,
|
|
GVariant *parameters,
|
|
gpointer user_data)
|
|
{
|
|
GMainLoop *loop = user_data;
|
|
|
|
g_assert_cmpstr (interface_name, ==, "org.gtk.Application");
|
|
g_assert_cmpstr (signal_name, ==, "ActionsChanged");
|
|
|
|
changed++;
|
|
|
|
g_main_loop_quit (loop);
|
|
}
|
|
|
|
static void
|
|
test_change_action_on_application_appeared (void)
|
|
{
|
|
GMainLoop *loop;
|
|
guint id;
|
|
GDBusConnection *connection;
|
|
|
|
loop = g_main_loop_new (NULL, FALSE);
|
|
|
|
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
|
|
id = g_dbus_connection_signal_subscribe (connection,
|
|
NULL,
|
|
"org.gtk.Application",
|
|
"ActionsChanged",
|
|
"/org/gtk/test/app",
|
|
NULL,
|
|
actions_changed,
|
|
loop,
|
|
NULL);
|
|
|
|
g_timeout_add (0, invoke_action, "action2");
|
|
|
|
g_main_loop_run (loop);
|
|
|
|
g_assert_cmpint (changed, >, 0);
|
|
|
|
g_dbus_connection_signal_unsubscribe (connection, id);
|
|
g_object_unref (connection);
|
|
g_main_loop_unref (loop);
|
|
}
|
|
|
|
static void
|
|
test_change_action (void)
|
|
{
|
|
run_with_application (test_change_action_on_application_appeared);
|
|
}
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
gint ret;
|
|
|
|
g_type_init ();
|
|
g_test_init (&argc, &argv, NULL);
|
|
|
|
g_unsetenv ("DISPLAY");
|
|
g_setenv ("DBUS_SESSION_BUS_ADDRESS", session_bus_get_temporary_address (), TRUE);
|
|
|
|
session_bus_up ();
|
|
|
|
g_test_add_func ("/application/unique", test_unique);
|
|
g_test_add_func ("/application/quit", test_quit);
|
|
g_test_add_func ("/application/list-actions", test_list_actions);
|
|
g_test_add_func ("/application/invoke", test_invoke);
|
|
g_test_add_func ("/application/remote", test_remote);
|
|
g_test_add_func ("/application/change-action", test_change_action);
|
|
|
|
ret = g_test_run ();
|
|
|
|
session_bus_down ();
|
|
|
|
return ret;
|
|
}
|
|
|