#include #include #include #include #include "gdbus-tests.h" #include "gdbus-sessionbus.h" #if 0 /* These tests are racy -- there is no guarantee about the order of data * arriving over D-Bus. * * They're also a bit ridiculous -- GApplication was never meant to be * abused in this way... * * We need new tests. */ static gint outstanding_watches; static GMainLoop *main_loop; typedef struct { gchar *expected_stdout; gint stdout_pipe; gchar *expected_stderr; gint stderr_pipe; } ChildData; static void check_data (gint fd, const gchar *expected) { gssize len, actual; gchar *buffer; len = strlen (expected); buffer = g_alloca (len + 100); actual = read (fd, buffer, len + 100); g_assert_cmpint (actual, >=, 0); if (actual != len || memcmp (buffer, expected, len) != 0) { buffer[MIN(len + 100, actual)] = '\0'; g_error ("\nExpected\n-----\n%s-----\nGot (%s)\n-----\n%s-----\n", expected, (actual > len) ? "truncated" : "full", buffer); } } static void child_quit (GPid pid, gint status, gpointer data) { ChildData *child = data; g_assert_cmpint (status, ==, 0); if (--outstanding_watches == 0) g_main_loop_quit (main_loop); check_data (child->stdout_pipe, child->expected_stdout); close (child->stdout_pipe); g_free (child->expected_stdout); if (child->expected_stderr) { check_data (child->stderr_pipe, child->expected_stderr); close (child->stderr_pipe); g_free (child->expected_stderr); } g_slice_free (ChildData, child); } static void spawn (const gchar *expected_stdout, const gchar *expected_stderr, const gchar *first_arg, ...) { GError *error = NULL; const gchar *arg; GPtrArray *array; ChildData *data; gchar **args; va_list ap; GPid pid; GPollFD fd; gchar **env; va_start (ap, first_arg); array = g_ptr_array_new (); g_ptr_array_add (array, g_test_build_filename (G_TEST_BUILT, "basic-application", NULL)); for (arg = first_arg; arg; arg = va_arg (ap, const gchar *)) g_ptr_array_add (array, g_strdup (arg)); g_ptr_array_add (array, NULL); args = (gchar **) g_ptr_array_free (array, FALSE); va_end (ap); env = g_environ_setenv (g_get_environ (), "TEST", "1", TRUE); data = g_slice_new (ChildData); data->expected_stdout = g_strdup (expected_stdout); data->expected_stderr = g_strdup (expected_stderr); g_spawn_async_with_pipes (NULL, args, env, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL, &data->stdout_pipe, expected_stderr ? &data->stderr_pipe : NULL, &error); g_assert_no_error (error); g_strfreev (env); g_child_watch_add (pid, child_quit, data); outstanding_watches++; /* we block until the children write to stdout to make sure * they have started, as they need to be executed in order; * see https://bugzilla.gnome.org/show_bug.cgi?id=664627 */ fd.fd = data->stdout_pipe; fd.events = G_IO_IN | G_IO_HUP | G_IO_ERR; g_poll (&fd, 1, -1); } static void basic (void) { GDBusConnection *c; g_assert (outstanding_watches == 0); session_bus_up (); c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); main_loop = g_main_loop_new (NULL, 0); /* spawn the main instance */ spawn ("activated\n" "open file:///a file:///b\n" "exit status: 0\n", NULL, "./app", NULL); /* send it some files */ spawn ("exit status: 0\n", NULL, "./app", "/a", "/b", NULL); g_main_loop_run (main_loop); g_object_unref (c); session_bus_down (); g_main_loop_unref (main_loop); } static void test_remote_command_line (void) { GDBusConnection *c; GFile *file; gchar *replies; gchar *cwd; g_assert (outstanding_watches == 0); session_bus_up (); c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); main_loop = g_main_loop_new (NULL, 0); file = g_file_new_for_commandline_arg ("foo"); cwd = g_get_current_dir (); replies = g_strconcat ("got ./cmd 0\n", "got ./cmd 1\n", "cmdline ./cmd echo --abc -d\n", "environment TEST=1\n", "getenv TEST=1\n", "file ", g_file_get_path (file), "\n", "properties ok\n", "cwd ", cwd, "\n", "busy\n", "idle\n", "stdin ok\n", "exit status: 0\n", NULL); g_object_unref (file); /* spawn the main instance */ spawn (replies, NULL, "./cmd", NULL); g_free (replies); /* send it a few commandlines */ spawn ("exit status: 0\n", NULL, "./cmd", NULL); spawn ("exit status: 0\n", NULL, "./cmd", "echo", "--abc", "-d", NULL); spawn ("exit status: 0\n", NULL, "./cmd", "env", NULL); spawn ("exit status: 0\n", NULL, "./cmd", "getenv", NULL); spawn ("print test\n" "exit status: 0\n", NULL, "./cmd", "print", "test", NULL); spawn ("exit status: 0\n", "printerr test\n", "./cmd", "printerr", "test", NULL); spawn ("exit status: 0\n", NULL, "./cmd", "file", "foo", NULL); spawn ("exit status: 0\n", NULL, "./cmd", "properties", NULL); spawn ("exit status: 0\n", NULL, "./cmd", "cwd", NULL); spawn ("exit status: 0\n", NULL, "./cmd", "busy", NULL); spawn ("exit status: 0\n", NULL, "./cmd", "idle", NULL); spawn ("exit status: 0\n", NULL, "./cmd", "stdin", NULL); g_main_loop_run (main_loop); g_object_unref (c); session_bus_down (); g_main_loop_unref (main_loop); } static void test_remote_actions (void) { GDBusConnection *c; g_assert (outstanding_watches == 0); session_bus_up (); c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); main_loop = g_main_loop_new (NULL, 0); /* spawn the main instance */ spawn ("got ./cmd 0\n" "activate action1\n" "change action2 1\n" "exit status: 0\n", NULL, "./cmd", NULL); spawn ("actions quit new action1 action2\n" "exit status: 0\n", NULL, "./actions", "list", NULL); spawn ("exit status: 0\n", NULL, "./actions", "activate", NULL); spawn ("exit status: 0\n", NULL, "./actions", "set-state", NULL); g_main_loop_run (main_loop); g_object_unref (c); session_bus_down (); g_main_loop_unref (main_loop); } #endif #if 0 /* Now that we register non-unique apps on the bus we need to fix the * following test not to assume that it's safe to create multiple instances * of the same app in one process. * * See https://bugzilla.gnome.org/show_bug.cgi?id=647986 for the patch that * introduced this problem. */ static GApplication *recently_activated; static GMainLoop *loop; static void nonunique_activate (GApplication *application) { recently_activated = application; if (loop != NULL) g_main_loop_quit (loop); } static GApplication * make_app (gboolean non_unique) { GApplication *app; gboolean ok; app = g_application_new ("org.gtk.Test-Application", non_unique ? G_APPLICATION_NON_UNIQUE : 0); g_signal_connect (app, "activate", G_CALLBACK (nonunique_activate), NULL); ok = g_application_register (app, NULL, NULL); if (!ok) { g_object_unref (app); return NULL; } g_application_activate (app); return app; } static void test_nonunique (void) { GApplication *first, *second, *third, *fourth; session_bus_up (); first = make_app (TRUE); /* non-remote because it is non-unique */ g_assert (!g_application_get_is_remote (first)); g_assert (recently_activated == first); recently_activated = NULL; second = make_app (FALSE); /* non-remote because it is first */ g_assert (!g_application_get_is_remote (second)); g_assert (recently_activated == second); recently_activated = NULL; third = make_app (TRUE); /* non-remote because it is non-unique */ g_assert (!g_application_get_is_remote (third)); g_assert (recently_activated == third); recently_activated = NULL; fourth = make_app (FALSE); /* should have failed to register due to being * unable to register the object paths */ g_assert (fourth == NULL); g_assert (recently_activated == NULL); g_object_unref (first); g_object_unref (second); g_object_unref (third); session_bus_down (); } #endif static void properties (void) { GDBusConnection *c; GObject *app; gchar *id; GApplicationFlags flags; gboolean registered; guint timeout; gboolean remote; gboolean ret; GError *error = NULL; session_bus_up (); c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); app = g_object_new (G_TYPE_APPLICATION, "application-id", "org.gtk.TestApplication", NULL); g_object_get (app, "application-id", &id, "flags", &flags, "is-registered", ®istered, "inactivity-timeout", &timeout, NULL); g_assert_cmpstr (id, ==, "org.gtk.TestApplication"); g_assert_cmpint (flags, ==, G_APPLICATION_DEFAULT_FLAGS); g_assert (!registered); g_assert_cmpint (timeout, ==, 0); ret = g_application_register (G_APPLICATION (app), NULL, &error); g_assert (ret); g_assert_no_error (error); g_object_get (app, "is-registered", ®istered, "is-remote", &remote, NULL); g_assert (registered); g_assert (!remote); g_object_set (app, "inactivity-timeout", 1000, NULL); g_application_quit (G_APPLICATION (app)); g_object_unref (c); g_object_unref (app); g_free (id); session_bus_down (); } static void appid (void) { gchar *id; g_assert_false (g_application_id_is_valid ("")); g_assert_false (g_application_id_is_valid (".")); g_assert_false (g_application_id_is_valid ("a")); g_assert_false (g_application_id_is_valid ("abc")); g_assert_false (g_application_id_is_valid (".abc")); g_assert_false (g_application_id_is_valid ("abc.")); g_assert_false (g_application_id_is_valid ("a..b")); g_assert_false (g_application_id_is_valid ("a/b")); g_assert_false (g_application_id_is_valid ("a\nb")); g_assert_false (g_application_id_is_valid ("a\nb")); g_assert_false (g_application_id_is_valid ("emoji_picker")); g_assert_false (g_application_id_is_valid ("emoji-picker")); g_assert_false (g_application_id_is_valid ("emojipicker")); g_assert_false (g_application_id_is_valid ("my.Terminal.0123")); id = g_new0 (gchar, 261); memset (id, 'a', 260); id[1] = '.'; id[260] = 0; g_assert_false (g_application_id_is_valid (id)); g_free (id); g_assert_true (g_application_id_is_valid ("a.b")); g_assert_true (g_application_id_is_valid ("A.B")); g_assert_true (g_application_id_is_valid ("A-.B")); g_assert_true (g_application_id_is_valid ("a_b.c-d")); g_assert_true (g_application_id_is_valid ("_a.b")); g_assert_true (g_application_id_is_valid ("-a.b")); g_assert_true (g_application_id_is_valid ("org.gnome.SessionManager")); g_assert_true (g_application_id_is_valid ("my.Terminal._0123")); g_assert_true (g_application_id_is_valid ("com.example.MyApp")); g_assert_true (g_application_id_is_valid ("com.example.internal_apps.Calculator")); g_assert_true (g_application_id_is_valid ("org._7_zip.Archiver")); } static gboolean nodbus_activated; static gboolean release_app (gpointer user_data) { g_application_release (user_data); return G_SOURCE_REMOVE; } static void nodbus_activate (GApplication *app) { nodbus_activated = TRUE; g_application_hold (app); g_assert (g_application_get_dbus_connection (app) == NULL); g_assert (g_application_get_dbus_object_path (app) == NULL); g_idle_add (release_app, app); } static void test_nodbus (void) { char *binpath = g_test_build_filename (G_TEST_BUILT, "unimportant", NULL); gchar *argv[] = { binpath, NULL }; GApplication *app; app = g_application_new ("org.gtk.Unimportant", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect (app, "activate", G_CALLBACK (nodbus_activate), NULL); g_application_run (app, 1, argv); g_object_unref (app); g_assert (nodbus_activated); g_free (binpath); } static gboolean noappid_activated; static void noappid_activate (GApplication *app) { noappid_activated = TRUE; g_application_hold (app); g_assert (g_application_get_flags (app) & G_APPLICATION_NON_UNIQUE); g_idle_add (release_app, app); } /* test that no appid -> non-unique */ static void test_noappid (void) { char *binpath = g_test_build_filename (G_TEST_BUILT, "unimportant", NULL); gchar *argv[] = { binpath, NULL }; GApplication *app; app = g_application_new (NULL, G_APPLICATION_DEFAULT_FLAGS); g_signal_connect (app, "activate", G_CALLBACK (noappid_activate), NULL); g_application_run (app, 1, argv); g_object_unref (app); g_assert (noappid_activated); g_free (binpath); } static gboolean activated; static gboolean quitted; static gboolean quit_app (gpointer user_data) { quitted = TRUE; g_application_quit (user_data); return G_SOURCE_REMOVE; } static void quit_activate (GApplication *app) { activated = TRUE; g_application_hold (app); g_assert (g_application_get_dbus_connection (app) != NULL); g_assert (g_application_get_dbus_object_path (app) != NULL); g_idle_add (quit_app, app); } static void test_quit (void) { GDBusConnection *c; char *binpath = g_test_build_filename (G_TEST_BUILT, "unimportant", NULL); gchar *argv[] = { binpath, NULL }; GApplication *app; session_bus_up (); c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); app = g_application_new ("org.gtk.Unimportant", G_APPLICATION_DEFAULT_FLAGS); activated = FALSE; quitted = FALSE; g_signal_connect (app, "activate", G_CALLBACK (quit_activate), NULL); g_application_run (app, 1, argv); g_object_unref (app); g_object_unref (c); g_assert (activated); g_assert (quitted); session_bus_down (); g_free (binpath); } typedef struct { gboolean shutdown; GParamSpec *notify_spec; /* (owned) (nullable) */ } RegisteredData; static void on_registered_shutdown (GApplication *app, gpointer user_data) { RegisteredData *registered_data = user_data; registered_data->shutdown = TRUE; } static void on_registered_notify (GApplication *app, GParamSpec *spec, gpointer user_data) { RegisteredData *registered_data = user_data; registered_data->notify_spec = g_param_spec_ref (spec); if (g_application_get_is_registered (app)) g_assert_false (registered_data->shutdown); else g_assert_true (registered_data->shutdown); } static void test_registered (void) { char *binpath = g_test_build_filename (G_TEST_BUILT, "unimportant", NULL); gchar *argv[] = { binpath, NULL }; RegisteredData registered_data = { FALSE, NULL }; GApplication *app; app = g_application_new (NULL, G_APPLICATION_DEFAULT_FLAGS); g_signal_connect (app, "activate", G_CALLBACK (noappid_activate), NULL); g_signal_connect (app, "shutdown", G_CALLBACK (on_registered_shutdown), ®istered_data); g_signal_connect (app, "notify::is-registered", G_CALLBACK (on_registered_notify), ®istered_data); g_assert_null (registered_data.notify_spec); g_assert_true (g_application_register (app, NULL, NULL)); g_assert_true (g_application_get_is_registered (app)); g_assert_nonnull (registered_data.notify_spec); g_assert_cmpstr (registered_data.notify_spec->name, ==, "is-registered"); g_clear_pointer (®istered_data.notify_spec, g_param_spec_unref); g_assert_false (registered_data.shutdown); g_application_run (app, 1, argv); g_assert_true (registered_data.shutdown); g_assert_false (g_application_get_is_registered (app)); g_assert_nonnull (registered_data.notify_spec); g_assert_cmpstr (registered_data.notify_spec->name, ==, "is-registered"); g_clear_pointer (®istered_data.notify_spec, g_param_spec_unref); /* Register it again */ registered_data.shutdown = FALSE; g_assert_true (g_application_register (app, NULL, NULL)); g_assert_true (g_application_get_is_registered (app)); g_assert_nonnull (registered_data.notify_spec); g_assert_cmpstr (registered_data.notify_spec->name, ==, "is-registered"); g_clear_pointer (®istered_data.notify_spec, g_param_spec_unref); g_assert_false (registered_data.shutdown); g_object_unref (app); g_free (binpath); } static void on_activate (GApplication *app) { gchar **actions; GAction *action; GVariant *state; g_assert (!g_application_get_is_remote (app)); actions = g_action_group_list_actions (G_ACTION_GROUP (app)); g_assert (g_strv_length (actions) == 0); g_strfreev (actions); action = (GAction*)g_simple_action_new_stateful ("test", G_VARIANT_TYPE_BOOLEAN, g_variant_new_boolean (FALSE)); g_action_map_add_action (G_ACTION_MAP (app), action); actions = g_action_group_list_actions (G_ACTION_GROUP (app)); g_assert (g_strv_length (actions) == 1); g_strfreev (actions); g_action_group_change_action_state (G_ACTION_GROUP (app), "test", g_variant_new_boolean (TRUE)); state = g_action_group_get_action_state (G_ACTION_GROUP (app), "test"); g_assert (g_variant_get_boolean (state) == TRUE); action = g_action_map_lookup_action (G_ACTION_MAP (app), "test"); g_assert (action != NULL); g_action_map_remove_action (G_ACTION_MAP (app), "test"); actions = g_action_group_list_actions (G_ACTION_GROUP (app)); g_assert (g_strv_length (actions) == 0); g_strfreev (actions); } static void test_local_actions (void) { char *binpath = g_test_build_filename (G_TEST_BUILT, "unimportant", NULL); gchar *argv[] = { binpath, NULL }; GApplication *app; app = g_application_new ("org.gtk.Unimportant", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL); g_application_run (app, 1, argv); g_object_unref (app); g_free (binpath); } typedef GApplication TestLocCmdApp; typedef GApplicationClass TestLocCmdAppClass; static GType test_loc_cmd_app_get_type (void); G_DEFINE_TYPE (TestLocCmdApp, test_loc_cmd_app, G_TYPE_APPLICATION) static void test_loc_cmd_app_init (TestLocCmdApp *app) { } static void test_loc_cmd_app_startup (GApplication *app) { g_assert_not_reached (); } static void test_loc_cmd_app_shutdown (GApplication *app) { g_assert_not_reached (); } static gboolean test_loc_cmd_app_local_command_line (GApplication *application, gchar ***arguments, gint *exit_status) { return TRUE; } static void test_loc_cmd_app_class_init (TestLocCmdAppClass *klass) { G_APPLICATION_CLASS (klass)->startup = test_loc_cmd_app_startup; G_APPLICATION_CLASS (klass)->shutdown = test_loc_cmd_app_shutdown; G_APPLICATION_CLASS (klass)->local_command_line = test_loc_cmd_app_local_command_line; } static void test_local_command_line (void) { char *binpath = g_test_build_filename (G_TEST_BUILT, "unimportant", NULL); gchar *argv[] = { binpath, "-invalid", NULL }; GApplication *app; app = g_object_new (test_loc_cmd_app_get_type (), "application-id", "org.gtk.Unimportant", "flags", G_APPLICATION_DEFAULT_FLAGS, NULL); g_application_run (app, 1, argv); g_object_unref (app); g_free (binpath); } static void test_resource_path (void) { GApplication *app; app = g_application_new ("x.y.z", 0); g_assert_cmpstr (g_application_get_resource_base_path (app), ==, "/x/y/z"); /* this should not change anything */ g_application_set_application_id (app, "a.b.c"); g_assert_cmpstr (g_application_get_resource_base_path (app), ==, "/x/y/z"); /* but this should... */ g_application_set_resource_base_path (app, "/x"); g_assert_cmpstr (g_application_get_resource_base_path (app), ==, "/x"); /* ... and this */ g_application_set_resource_base_path (app, NULL); g_assert_cmpstr (g_application_get_resource_base_path (app), ==, NULL); g_object_unref (app); /* Make sure that overriding at construction time works properly */ app = g_object_new (G_TYPE_APPLICATION, "application-id", "x.y.z", "resource-base-path", "/a", NULL); g_assert_cmpstr (g_application_get_resource_base_path (app), ==, "/a"); g_object_unref (app); /* ... particularly if we override to NULL */ app = g_object_new (G_TYPE_APPLICATION, "application-id", "x.y.z", "resource-base-path", NULL, NULL); g_assert_cmpstr (g_application_get_resource_base_path (app), ==, NULL); g_object_unref (app); } static gint test_help_command_line (GApplication *app, GApplicationCommandLine *command_line, gpointer user_data) { gboolean *called = user_data; *called = TRUE; return 0; } /* Test whether --help is handled when HANDLES_COMMND_LINE is set and * options have been added. */ static void test_help (void) { if (g_test_subprocess ()) { char *binpath = g_test_build_filename (G_TEST_BUILT, "unimportant", NULL); gchar *argv[] = { binpath, "--help", NULL }; GApplication *app; gboolean called = FALSE; int status; app = g_application_new ("org.gtk.TestApplication", G_APPLICATION_HANDLES_COMMAND_LINE); g_application_add_main_option (app, "foo", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, "", ""); g_signal_connect (app, "command-line", G_CALLBACK (test_help_command_line), &called); status = g_application_run (app, G_N_ELEMENTS (argv) -1, argv); g_assert (called == TRUE); g_assert_cmpint (status, ==, 0); g_object_unref (app); g_free (binpath); return; } g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_DEFAULT); g_test_trap_assert_passed (); g_test_trap_assert_stdout ("*Application options*"); } static void test_busy (void) { GApplication *app; /* use GSimpleAction to bind to the busy state, because it's easy to * create and has an easily modifiable boolean property */ GSimpleAction *action1; GSimpleAction *action2; session_bus_up (); app = g_application_new ("org.gtk.TestApplication", G_APPLICATION_NON_UNIQUE); g_assert (g_application_register (app, NULL, NULL)); g_assert (!g_application_get_is_busy (app)); g_application_mark_busy (app); g_assert (g_application_get_is_busy (app)); g_application_unmark_busy (app); g_assert (!g_application_get_is_busy (app)); action1 = g_simple_action_new ("action", NULL); g_application_bind_busy_property (app, action1, "enabled"); g_assert (g_application_get_is_busy (app)); g_simple_action_set_enabled (action1, FALSE); g_assert (!g_application_get_is_busy (app)); g_application_mark_busy (app); g_assert (g_application_get_is_busy (app)); action2 = g_simple_action_new ("action", NULL); g_application_bind_busy_property (app, action2, "enabled"); g_assert (g_application_get_is_busy (app)); g_application_unmark_busy (app); g_assert (g_application_get_is_busy (app)); g_object_unref (action2); g_assert (!g_application_get_is_busy (app)); g_simple_action_set_enabled (action1, TRUE); g_assert (g_application_get_is_busy (app)); g_application_mark_busy (app); g_assert (g_application_get_is_busy (app)); g_application_unbind_busy_property (app, action1, "enabled"); g_assert (g_application_get_is_busy (app)); g_application_unmark_busy (app); g_assert (!g_application_get_is_busy (app)); g_object_unref (action1); g_object_unref (app); session_bus_down (); } /* * Test that handle-local-options works as expected */ static gint test_local_options (GApplication *app, GVariantDict *options, gpointer data) { gboolean *called = data; *called = TRUE; if (g_variant_dict_contains (options, "success")) return 0; else if (g_variant_dict_contains (options, "failure")) return 1; else return -1; } static gint second_handler (GApplication *app, GVariantDict *options, gpointer data) { gboolean *called = data; *called = TRUE; return 2; } static void test_handle_local_options_success (void) { if (g_test_subprocess ()) { char *binpath = g_test_build_filename (G_TEST_BUILT, "unimportant", NULL); gchar *argv[] = { binpath, "--success", NULL }; GApplication *app; gboolean called = FALSE; gboolean called2 = FALSE; int status; app = g_application_new ("org.gtk.TestApplication", 0); g_application_add_main_option (app, "success", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, "", ""); g_application_add_main_option (app, "failure", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, "", ""); g_signal_connect (app, "handle-local-options", G_CALLBACK (test_local_options), &called); g_signal_connect (app, "handle-local-options", G_CALLBACK (second_handler), &called2); status = g_application_run (app, G_N_ELEMENTS (argv) -1, argv); g_assert (called); g_assert (!called2); g_assert_cmpint (status, ==, 0); g_object_unref (app); g_free (binpath); return; } g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_INHERIT_STDOUT | G_TEST_SUBPROCESS_INHERIT_STDERR); g_test_trap_assert_passed (); } static void test_handle_local_options_failure (void) { if (g_test_subprocess ()) { char *binpath = g_test_build_filename (G_TEST_BUILT, "unimportant", NULL); gchar *argv[] = { binpath, "--failure", NULL }; GApplication *app; gboolean called = FALSE; gboolean called2 = FALSE; int status; app = g_application_new ("org.gtk.TestApplication", 0); g_application_add_main_option (app, "success", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, "", ""); g_application_add_main_option (app, "failure", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, "", ""); g_signal_connect (app, "handle-local-options", G_CALLBACK (test_local_options), &called); g_signal_connect (app, "handle-local-options", G_CALLBACK (second_handler), &called2); status = g_application_run (app, G_N_ELEMENTS (argv) -1, argv); g_assert (called); g_assert (!called2); g_assert_cmpint (status, ==, 1); g_object_unref (app); g_free (binpath); return; } g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_INHERIT_STDOUT | G_TEST_SUBPROCESS_INHERIT_STDERR); g_test_trap_assert_passed (); } static void test_handle_local_options_passthrough (void) { if (g_test_subprocess ()) { char *binpath = g_test_build_filename (G_TEST_BUILT, "unimportant", NULL); gchar *argv[] = { binpath, NULL }; GApplication *app; gboolean called = FALSE; gboolean called2 = FALSE; int status; app = g_application_new ("org.gtk.TestApplication", 0); g_application_add_main_option (app, "success", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, "", ""); g_application_add_main_option (app, "failure", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, "", ""); g_signal_connect (app, "handle-local-options", G_CALLBACK (test_local_options), &called); g_signal_connect (app, "handle-local-options", G_CALLBACK (second_handler), &called2); status = g_application_run (app, G_N_ELEMENTS (argv) -1, argv); g_assert (called); g_assert (called2); g_assert_cmpint (status, ==, 2); g_object_unref (app); g_free (binpath); return; } g_test_trap_subprocess (NULL, 0, G_TEST_SUBPROCESS_INHERIT_STDOUT | G_TEST_SUBPROCESS_INHERIT_STDERR); g_test_trap_assert_passed (); } static void test_api (void) { GApplication *app; GSimpleAction *action; app = g_application_new ("org.gtk.TestApplication", 0); /* add an action without a name */ g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*assertion*failed*"); action = g_simple_action_new (NULL, NULL); g_assert (action == NULL); g_test_assert_expected_messages (); /* also, gapplication shouldn't accept actions without names */ action = g_object_new (G_TYPE_SIMPLE_ACTION, NULL); g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, "*action has no name*"); g_action_map_add_action (G_ACTION_MAP (app), G_ACTION (action)); g_test_assert_expected_messages (); g_object_unref (action); g_object_unref (app); } /* Check that G_APPLICATION_ALLOW_REPLACEMENT works. To do so, we launch * a GApplication in this process that allows replacement, and then * launch a subprocess with --gapplication-replace. We have to do our * own async version of g_test_trap_subprocess() here since we need * the main process to keep spinning its mainloop. */ static gboolean name_was_lost (GApplication *app, gboolean *called) { *called = TRUE; g_application_quit (app); return TRUE; } static void startup_in_subprocess (GApplication *app, gboolean *called) { *called = TRUE; } typedef struct { gboolean allow_replacement; GSubprocess *subprocess; } TestReplaceData; static void startup_cb (GApplication *app, TestReplaceData *data) { const char *argv[] = { NULL, "--verbose", "--quiet", "-p", NULL, "--GTestSubprocess", NULL }; GSubprocessLauncher *launcher; GError *local_error = NULL; g_application_hold (app); argv[0] = g_get_prgname (); if (data->allow_replacement) argv[4] = "/gapplication/replace"; else argv[4] = "/gapplication/no-replace"; /* Now that we are the primary instance, launch our replacement. * We inherit the environment to share the test session bus. */ g_test_message ("launching subprocess"); launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); g_subprocess_launcher_set_environ (launcher, NULL); data->subprocess = g_subprocess_launcher_spawnv (launcher, argv, &local_error); g_assert_no_error (local_error); g_object_unref (launcher); if (!data->allow_replacement) { /* make sure we exit after a bit, if the subprocess is not replacing us */ g_application_set_inactivity_timeout (app, 500); g_application_release (app); } } static void activate (gpointer data) { /* GApplication complains if we don't connect to ::activate */ } static gboolean quit_already (gpointer data) { GApplication *app = data; g_application_quit (app); return G_SOURCE_REMOVE; } static void test_replace (gconstpointer data) { gboolean allow = GPOINTER_TO_INT (data); if (g_test_subprocess ()) { char *binpath = g_test_build_filename (G_TEST_BUILT, "unimportant", NULL); char *argv[] = { binpath, "--gapplication-replace", NULL }; GApplication *app; gboolean startup = FALSE; app = g_application_new ("org.gtk.TestApplication.Replace", G_APPLICATION_ALLOW_REPLACEMENT); g_signal_connect (app, "startup", G_CALLBACK (startup_in_subprocess), &startup); g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); g_application_run (app, G_N_ELEMENTS (argv) - 1, argv); if (allow) g_assert_true (startup); else g_assert_false (startup); g_object_unref (app); g_free (binpath); } else { char *binpath = g_test_build_filename (G_TEST_BUILT, "unimportant", NULL); gchar *argv[] = { binpath, NULL }; GApplication *app; gboolean name_lost = FALSE; TestReplaceData data; GTestDBus *bus; data.allow_replacement = allow; data.subprocess = NULL; bus = g_test_dbus_new (0); g_test_dbus_up (bus); app = g_application_new ("org.gtk.TestApplication.Replace", allow ? G_APPLICATION_ALLOW_REPLACEMENT : G_APPLICATION_DEFAULT_FLAGS); g_application_set_inactivity_timeout (app, 500); g_signal_connect (app, "name-lost", G_CALLBACK (name_was_lost), &name_lost); g_signal_connect (app, "startup", G_CALLBACK (startup_cb), &data); g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); if (!allow) g_timeout_add_seconds (1, quit_already, app); g_application_run (app, G_N_ELEMENTS (argv) - 1, argv); g_assert_nonnull (data.subprocess); if (allow) g_assert_true (name_lost); else g_assert_false (name_lost); g_object_unref (app); g_free (binpath); g_subprocess_wait (data.subprocess, NULL, NULL); g_clear_object (&data.subprocess); g_test_dbus_down (bus); g_object_unref (bus); } } static void dbus_activate_cb (GApplication *app, gpointer user_data) { guint *n_activations = user_data; *n_activations = *n_activations + 1; g_main_context_wakeup (NULL); } static void dbus_startup_reply_cb (GObject *source_object, GAsyncResult *result, gpointer user_data); static void dbus_startup_cb (GApplication *app, gpointer user_data) { GDBusConnection *connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); GDBusMessage *message = G_DBUS_MESSAGE (user_data); g_assert_nonnull (connection); g_dbus_connection_send_message_with_reply (connection, message, G_DBUS_SEND_MESSAGE_FLAGS_NONE, -1, NULL, NULL, dbus_startup_reply_cb, g_object_ref (app)); g_clear_object (&connection); } static void dbus_startup_reply_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GDBusConnection *connection = G_DBUS_CONNECTION (source_object); GApplication *app = G_APPLICATION (user_data); GDBusMessage *reply = NULL; GError *local_error = NULL; reply = g_dbus_connection_send_message_with_reply_finish (connection, result, &local_error); g_assert_no_error (local_error); /* Nothing to check on the reply for now. */ g_clear_object (&reply); g_application_release (app); g_clear_object (&app); } static void test_dbus_activate (void) { GTestDBus *bus = NULL; GVariantBuilder builder; GDBusMessage *message = NULL; GPtrArray *messages = NULL; /* (element-type GDBusMessage) (owned) */ gsize i; g_test_summary ("Test that calling the Activate D-Bus method works"); /* Try various different messages */ messages = g_ptr_array_new_with_free_func (g_object_unref); /* Via org.gtk.Application */ message = g_dbus_message_new_method_call ("org.gtk.TestApplication.Activate", "/org/gtk/TestApplication/Activate", "org.gtk.Application", "Activate"); g_dbus_message_set_body (message, g_variant_new ("(a{sv})", NULL)); g_ptr_array_add (messages, g_steal_pointer (&message)); /* Via org.freedesktop.Application */ message = g_dbus_message_new_method_call ("org.gtk.TestApplication.Activate", "/org/gtk/TestApplication/Activate", "org.freedesktop.Application", "Activate"); g_dbus_message_set_body (message, g_variant_new ("(a{sv})", NULL)); g_ptr_array_add (messages, g_steal_pointer (&message)); /* With some platform data */ g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (&builder, "{sv}", "cwd", g_variant_new_bytestring ("/home/henry")); message = g_dbus_message_new_method_call ("org.gtk.TestApplication.Activate", "/org/gtk/TestApplication/Activate", "org.gtk.Application", "Activate"); g_dbus_message_set_body (message, g_variant_new ("(a{sv})", &builder)); g_ptr_array_add (messages, g_steal_pointer (&message)); /* Try each message */ bus = g_test_dbus_new (G_TEST_DBUS_NONE); g_test_dbus_up (bus); for (i = 0; i < messages->len; i++) { GApplication *app = NULL; gulong activate_id, startup_id; guint n_activations = 0; g_test_message ("Message %" G_GSIZE_FORMAT, i); app = g_application_new ("org.gtk.TestApplication.Activate", G_APPLICATION_DEFAULT_FLAGS); activate_id = g_signal_connect (app, "activate", G_CALLBACK (dbus_activate_cb), &n_activations); startup_id = g_signal_connect (app, "startup", G_CALLBACK (dbus_startup_cb), messages->pdata[i]); g_application_hold (app); g_application_run (app, 0, NULL); /* It’ll be activated once as normal, and once due to the D-Bus call */ g_assert_cmpuint (n_activations, ==, 2); g_signal_handler_disconnect (app, startup_id); g_signal_handler_disconnect (app, activate_id); g_clear_object (&app); } g_ptr_array_unref (messages); g_test_dbus_down (bus); g_clear_object (&bus); } static void dbus_activate_noop_cb (GApplication *app, gpointer user_data) { /* noop */ } static void dbus_open_cb (GApplication *app, gpointer files, int n_files, char *hint, gpointer user_data) { guint *n_opens = user_data; *n_opens = *n_opens + 1; g_main_context_wakeup (NULL); } static void test_dbus_open (void) { GTestDBus *bus = NULL; GVariantBuilder builder, builder2; GDBusMessage *message = NULL; GPtrArray *messages = NULL; /* (element-type GDBusMessage) (owned) */ gsize i; g_test_summary ("Test that calling the Open D-Bus method works"); /* Try various different messages */ messages = g_ptr_array_new_with_free_func (g_object_unref); /* Via org.gtk.Application */ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); g_variant_builder_add (&builder, "s", "file:///home/henry/test"); message = g_dbus_message_new_method_call ("org.gtk.TestApplication.Open", "/org/gtk/TestApplication/Open", "org.gtk.Application", "Open"); g_dbus_message_set_body (message, g_variant_new ("(assa{sv})", &builder, "hint", NULL)); g_ptr_array_add (messages, g_steal_pointer (&message)); /* Via org.freedesktop.Application (which has no hint parameter) */ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); g_variant_builder_add (&builder, "s", "file:///home/henry/test"); message = g_dbus_message_new_method_call ("org.gtk.TestApplication.Open", "/org/gtk/TestApplication/Open", "org.freedesktop.Application", "Open"); g_dbus_message_set_body (message, g_variant_new ("(asa{sv})", &builder, NULL)); g_ptr_array_add (messages, g_steal_pointer (&message)); /* With some platform data and more than one file */ g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); g_variant_builder_add (&builder, "s", "file:///home/henry/test"); g_variant_builder_add (&builder, "s", "file:///home/henry/test2"); g_variant_builder_init (&builder2, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (&builder2, "{sv}", "cwd", g_variant_new_bytestring ("/home/henry")); message = g_dbus_message_new_method_call ("org.gtk.TestApplication.Open", "/org/gtk/TestApplication/Open", "org.gtk.Application", "Open"); g_dbus_message_set_body (message, g_variant_new ("(assa{sv})", &builder, "", &builder2)); g_ptr_array_add (messages, g_steal_pointer (&message)); /* No files */ message = g_dbus_message_new_method_call ("org.gtk.TestApplication.Open", "/org/gtk/TestApplication/Open", "org.gtk.Application", "Open"); g_dbus_message_set_body (message, g_variant_new ("(assa{sv})", NULL, "", NULL)); g_ptr_array_add (messages, g_steal_pointer (&message)); /* Try each message */ bus = g_test_dbus_new (G_TEST_DBUS_NONE); g_test_dbus_up (bus); for (i = 0; i < messages->len; i++) { GApplication *app = NULL; gulong activate_id, open_id, startup_id; guint n_opens = 0; g_test_message ("Message %" G_GSIZE_FORMAT, i); app = g_application_new ("org.gtk.TestApplication.Open", G_APPLICATION_HANDLES_OPEN); activate_id = g_signal_connect (app, "activate", G_CALLBACK (dbus_activate_noop_cb), NULL); open_id = g_signal_connect (app, "open", G_CALLBACK (dbus_open_cb), &n_opens); startup_id = g_signal_connect (app, "startup", G_CALLBACK (dbus_startup_cb), messages->pdata[i]); g_application_hold (app); g_application_run (app, 0, NULL); g_assert_cmpuint (n_opens, ==, 1); g_signal_handler_disconnect (app, startup_id); g_signal_handler_disconnect (app, open_id); g_signal_handler_disconnect (app, activate_id); g_clear_object (&app); } g_ptr_array_unref (messages); g_test_dbus_down (bus); g_clear_object (&bus); } static void dbus_command_line_cb (GApplication *app, GApplicationCommandLine *command_line, gpointer user_data) { guint *n_command_lines = user_data; *n_command_lines = *n_command_lines + 1; g_main_context_wakeup (NULL); } static void test_dbus_command_line (void) { GTestDBus *bus = NULL; GVariantBuilder builder; GDBusMessage *message = NULL; GPtrArray *messages = NULL; /* (element-type GDBusMessage) (owned) */ gsize i; g_test_summary ("Test that calling the CommandLine D-Bus method works"); /* Try various different messages */ messages = g_ptr_array_new_with_free_func (g_object_unref); /* Via org.gtk.Application */ g_variant_builder_init (&builder, G_VARIANT_TYPE ("aay")); g_variant_builder_add (&builder, "^ay", "test-program"); g_variant_builder_add (&builder, "^ay", "--open"); g_variant_builder_add (&builder, "^ay", "/path/to/something"); message = g_dbus_message_new_method_call ("org.gtk.TestApplication.CommandLine", "/org/gtk/TestApplication/CommandLine", "org.gtk.Application", "CommandLine"); g_dbus_message_set_body (message, g_variant_new ("(oaaya{sv})", "/my/org/gtk/private/CommandLine", &builder, NULL)); g_ptr_array_add (messages, g_steal_pointer (&message)); /* Try each message */ bus = g_test_dbus_new (G_TEST_DBUS_NONE); g_test_dbus_up (bus); for (i = 0; i < messages->len; i++) { GApplication *app = NULL; gulong activate_id, command_line_id, startup_id; guint n_command_lines = 0; g_test_message ("Message %" G_GSIZE_FORMAT, i); app = g_application_new ("org.gtk.TestApplication.CommandLine", G_APPLICATION_HANDLES_COMMAND_LINE); activate_id = g_signal_connect (app, "activate", G_CALLBACK (dbus_activate_noop_cb), NULL); command_line_id = g_signal_connect (app, "command-line", G_CALLBACK (dbus_command_line_cb), &n_command_lines); startup_id = g_signal_connect (app, "startup", G_CALLBACK (dbus_startup_cb), messages->pdata[i]); g_application_hold (app); g_application_run (app, 0, NULL); /* It’s called once for handling the local command line on startup, and again * for the D-Bus call */ g_assert_cmpuint (n_command_lines, ==, 2); g_signal_handler_disconnect (app, startup_id); g_signal_handler_disconnect (app, command_line_id); g_signal_handler_disconnect (app, activate_id); g_clear_object (&app); } g_ptr_array_unref (messages); g_test_dbus_down (bus); g_clear_object (&bus); } static void dbus_activate_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data) { guint *n_activations = user_data; *n_activations = *n_activations + 1; g_main_context_wakeup (NULL); } static void test_dbus_activate_action (void) { GTestDBus *bus = NULL; GVariantBuilder builder; struct { GDBusMessage *message; /* (not nullable) (owned) */ guint n_expected_activations; } messages[6]; gsize i; g_test_summary ("Test that calling the ActivateAction D-Bus method works"); /* Action without parameter */ messages[0].message = g_dbus_message_new_method_call ("org.gtk.TestApplication.ActivateAction", "/org/gtk/TestApplication/ActivateAction", "org.freedesktop.Application", "ActivateAction"); g_dbus_message_set_body (messages[0].message, g_variant_new ("(sava{sv})", "undo", NULL, NULL)); messages[0].n_expected_activations = 1; /* Action with parameter */ g_variant_builder_init (&builder, G_VARIANT_TYPE ("av")); g_variant_builder_add (&builder, "v", g_variant_new_string ("spanish")); messages[1].message = g_dbus_message_new_method_call ("org.gtk.TestApplication.ActivateAction", "/org/gtk/TestApplication/ActivateAction", "org.freedesktop.Application", "ActivateAction"); g_dbus_message_set_body (messages[1].message, g_variant_new ("(sava{sv})", "lang", &builder, NULL)); messages[1].n_expected_activations = 1; /* Action with unexpected parameter */ g_variant_builder_init (&builder, G_VARIANT_TYPE ("av")); g_variant_builder_add (&builder, "v", g_variant_new_string ("should not be passed")); messages[2].message = g_dbus_message_new_method_call ("org.gtk.TestApplication.ActivateAction", "/org/gtk/TestApplication/ActivateAction", "org.freedesktop.Application", "ActivateAction"); g_dbus_message_set_body (messages[2].message, g_variant_new ("(sava{sv})", "undo", &builder, NULL)); messages[2].n_expected_activations = 0; /* Action without required parameter */ messages[3].message = g_dbus_message_new_method_call ("org.gtk.TestApplication.ActivateAction", "/org/gtk/TestApplication/ActivateAction", "org.freedesktop.Application", "ActivateAction"); g_dbus_message_set_body (messages[3].message, g_variant_new ("(sava{sv})", "lang", NULL, NULL)); messages[3].n_expected_activations = 0; /* Action with wrong parameter type */ g_variant_builder_init (&builder, G_VARIANT_TYPE ("av")); g_variant_builder_add (&builder, "v", g_variant_new_uint32 (42)); messages[4].message = g_dbus_message_new_method_call ("org.gtk.TestApplication.ActivateAction", "/org/gtk/TestApplication/ActivateAction", "org.freedesktop.Application", "ActivateAction"); g_dbus_message_set_body (messages[4].message, g_variant_new ("(sava{sv})", "lang", &builder, NULL)); messages[4].n_expected_activations = 0; /* Nonexistent action */ messages[5].message = g_dbus_message_new_method_call ("org.gtk.TestApplication.ActivateAction", "/org/gtk/TestApplication/ActivateAction", "org.freedesktop.Application", "ActivateAction"); g_dbus_message_set_body (messages[5].message, g_variant_new ("(sava{sv})", "nonexistent", NULL, NULL)); messages[5].n_expected_activations = 0; /* Try each message */ bus = g_test_dbus_new (G_TEST_DBUS_NONE); g_test_dbus_up (bus); for (i = 0; i < G_N_ELEMENTS (messages); i++) { GApplication *app = NULL; gulong activate_id, startup_id; const GActionEntry entries[] = { { "undo", dbus_activate_action_cb, NULL, NULL, NULL, { 0 } }, { "lang", dbus_activate_action_cb, "s", "'latin'", NULL, { 0 } }, }; guint n_activations = 0; g_test_message ("Message %" G_GSIZE_FORMAT, i); app = g_application_new ("org.gtk.TestApplication.ActivateAction", G_APPLICATION_DEFAULT_FLAGS); activate_id = g_signal_connect (app, "activate", G_CALLBACK (dbus_activate_noop_cb), NULL); startup_id = g_signal_connect (app, "startup", G_CALLBACK (dbus_startup_cb), messages[i].message); /* Export some actions. */ g_action_map_add_action_entries (G_ACTION_MAP (app), entries, G_N_ELEMENTS (entries), &n_activations); g_application_hold (app); g_application_run (app, 0, NULL); g_assert_cmpuint (n_activations, ==, messages[i].n_expected_activations); g_signal_handler_disconnect (app, startup_id); g_signal_handler_disconnect (app, activate_id); g_clear_object (&app); g_clear_object (&messages[i].message); } g_test_dbus_down (bus); g_clear_object (&bus); } int main (int argc, char **argv) { g_setenv ("LC_ALL", "C", TRUE); g_test_init (&argc, &argv, NULL); if (!g_test_subprocess ()) g_test_dbus_unset (); g_test_add_func ("/gapplication/no-dbus", test_nodbus); /* g_test_add_func ("/gapplication/basic", basic); */ g_test_add_func ("/gapplication/no-appid", test_noappid); /* g_test_add_func ("/gapplication/non-unique", test_nonunique); */ g_test_add_func ("/gapplication/properties", properties); g_test_add_func ("/gapplication/app-id", appid); g_test_add_func ("/gapplication/quit", test_quit); g_test_add_func ("/gapplication/registered", test_registered); g_test_add_func ("/gapplication/local-actions", test_local_actions); /* g_test_add_func ("/gapplication/remote-actions", test_remote_actions); */ g_test_add_func ("/gapplication/local-command-line", test_local_command_line); /* g_test_add_func ("/gapplication/remote-command-line", test_remote_command_line); */ g_test_add_func ("/gapplication/resource-path", test_resource_path); g_test_add_func ("/gapplication/test-help", test_help); g_test_add_func ("/gapplication/test-busy", test_busy); g_test_add_func ("/gapplication/test-handle-local-options1", test_handle_local_options_success); g_test_add_func ("/gapplication/test-handle-local-options2", test_handle_local_options_failure); g_test_add_func ("/gapplication/test-handle-local-options3", test_handle_local_options_passthrough); g_test_add_func ("/gapplication/api", test_api); g_test_add_data_func ("/gapplication/replace", GINT_TO_POINTER (TRUE), test_replace); g_test_add_data_func ("/gapplication/no-replace", GINT_TO_POINTER (FALSE), test_replace); g_test_add_func ("/gapplication/dbus/activate", test_dbus_activate); g_test_add_func ("/gapplication/dbus/open", test_dbus_open); g_test_add_func ("/gapplication/dbus/command-line", test_dbus_command_line); g_test_add_func ("/gapplication/dbus/activate-action", test_dbus_activate_action); return g_test_run (); }