| #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; |
| } |
| |