glib/gio/tests/testapps.c
Colin Walters cc19922183 Rewrite apps test to ensure children are killed
Create a function run_with_application that both ensures the
app is running exactly while the test is running, which most
of the tests use.  We start it beforehand, and kill it after.
This avoids having any interdependence between the tests (and
there definitely was before, because we didn't wait for
the process to actually terminate after a kill() call).

Also, open a pipe between the two, and have the child app
monitor that pipe.  If it gets closed (e.g. because the parent
died), the child exits.  This is the most reliable way to
avoid stale children; before, if we failed an assertion, the
parent would abort, and not run kill().

https://bugzilla.gnome.org/show_bug.cgi?id=621034
2010-06-10 13:14:29 -04:00

577 lines
14 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);
g_source_unref (child_watch_source);
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_source_unref (timeout_source);
g_main_loop_run (data->loop);
g_source_destroy (child_watch_source);
g_source_destroy (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);
}
/* 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;
GDBusConnection *connection;
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);
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
g_dbus_connection_call (connection,
"org.gtk.test.app",
"/org/gtk/test/app",
"org.gtk.Application",
"Quit",
g_variant_new ("(u)", 0),
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
NULL, 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 ("(su)",
action,
0),
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;
}