diff options
Diffstat (limited to 'gio/tests/desktop-app-info.c')
-rw-r--r-- | gio/tests/desktop-app-info.c | 1192 |
1 files changed, 1148 insertions, 44 deletions
diff --git a/gio/tests/desktop-app-info.c b/gio/tests/desktop-app-info.c index 65815fe70..594ec945d 100644 --- a/gio/tests/desktop-app-info.c +++ b/gio/tests/desktop-app-info.c @@ -25,26 +25,99 @@ #include <glib/gstdio.h> #include <gio/gio.h> #include <gio/gdesktopappinfo.h> +#include <gio/gunixinputstream.h> +#include <glib-unix.h> #include <stdlib.h> #include <string.h> #include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> + +G_DECLARE_FINAL_TYPE (TestLaunchContext, test_launch_context, TEST, + LAUNCH_CONTEXT, GAppLaunchContext); + +struct _TestLaunchContext { + GAppLaunchContext parent; + + char *overriden_startup_notify_id; +}; + +struct _TestLaunchContextClass { + GAppLaunchContextClass parent; +}; + +G_DEFINE_FINAL_TYPE (TestLaunchContext, test_launch_context, + G_TYPE_APP_LAUNCH_CONTEXT); + +static void +test_launch_context_init (TestLaunchContext *test_context) +{ +} + +static char * +test_launch_context_get_startup_notify_id (GAppLaunchContext *context, + GAppInfo *app_info, + GList *files) +{ + TestLaunchContext *test_context = TEST_LAUNCH_CONTEXT (context); + + if (test_context->overriden_startup_notify_id) + return g_strdup (test_context->overriden_startup_notify_id); + + if (g_app_info_get_id (app_info)) + return g_strdup (g_app_info_get_id (app_info)); + + if (g_app_info_get_display_name (app_info)) + return g_strdup (g_app_info_get_display_name (app_info)); + + return g_strdup (g_app_info_get_commandline (app_info)); +} + +static void +test_launch_context_get_startup_notify_dispose (GObject *object) +{ + TestLaunchContext *test_context = TEST_LAUNCH_CONTEXT (object); + + g_clear_pointer (&test_context->overriden_startup_notify_id, g_free); + G_OBJECT_CLASS (test_launch_context_parent_class)->dispose (object); +} + +static void +test_launch_context_class_init (TestLaunchContextClass *klass) +{ + G_APP_LAUNCH_CONTEXT_CLASS (klass)->get_startup_notify_id = test_launch_context_get_startup_notify_id; + G_OBJECT_CLASS (klass)->dispose = test_launch_context_get_startup_notify_dispose; +} static GAppInfo * -create_app_info (const char *name) +create_command_line_app_info (const char *name, + const char *command_line, + const char *default_for_type) { - GError *error; GAppInfo *info; + GError *error = NULL; - error = NULL; - info = g_app_info_create_from_commandline ("true blah", + info = g_app_info_create_from_commandline (command_line, name, G_APP_INFO_CREATE_NONE, &error); g_assert_no_error (error); - /* this is necessary to ensure that the info is saved */ - g_app_info_set_as_default_for_type (info, "application/x-blah", &error); + g_app_info_set_as_default_for_type (info, default_for_type, &error); g_assert_no_error (error); + + return g_steal_pointer (&info); +} + +static GAppInfo * +create_app_info (const char *name) +{ + GError *error = NULL; + GAppInfo *info; + + info = create_command_line_app_info (name, "true blah", "application/x-blah"); + + /* this is necessary to ensure that the info is saved */ g_app_info_remove_supports_type (info, "application/x-blah", &error); g_assert_no_error (error); g_app_info_reset_type_associations ("application/x-blah"); @@ -119,6 +192,23 @@ test_default (void) g_assert_cmpstr (g_app_info_get_id (info), ==, g_app_info_get_id (info2)); g_object_unref (info); + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, + "*assertion*uri_scheme*failed*"); + g_assert_null (g_app_info_get_default_for_uri_scheme (NULL)); + g_test_assert_expected_messages (); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, + "*assertion*uri_scheme*failed*"); + g_assert_null (g_app_info_get_default_for_uri_scheme ("")); + g_test_assert_expected_messages (); + + g_app_info_set_as_default_for_type (info3, "x-scheme-handler/glib", &error); + g_assert_no_error (error); + info = g_app_info_get_default_for_uri_scheme ("glib"); + g_assert_nonnull (info); + g_assert_true (g_app_info_equal (info, info3)); + g_object_unref (info); + /* now try adding something, but not setting as default */ g_app_info_add_supports_type (info3, "application/x-test", &error); g_assert_no_error (error); @@ -141,10 +231,14 @@ test_default (void) /* now clean it all up */ g_app_info_reset_type_associations ("application/x-test"); + g_app_info_reset_type_associations ("x-scheme-handler/glib"); list = g_app_info_get_all_for_type ("application/x-test"); g_assert_null (list); + list = g_app_info_get_all_for_type ("x-scheme-handler/glib"); + g_assert_null (list); + g_app_info_delete (info1); g_app_info_delete (info2); g_app_info_delete (info3); @@ -154,6 +248,168 @@ test_default (void) g_object_unref (info3); } +typedef struct +{ + GAppInfo *expected_info; + GMainLoop *loop; +} DefaultForTypeData; + +static void +ensure_default_type_result (GAppInfo *info, + DefaultForTypeData *data, + GError *error) +{ + if (data->expected_info) + { + g_assert_nonnull (info); + g_assert_no_error (error); + g_assert_true (g_app_info_equal (info, data->expected_info)); + } + else + { + g_assert_null (info); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND); + } + + g_main_loop_quit (data->loop); + g_clear_object (&info); + g_clear_error (&error); +} + +static void +on_default_for_type_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GAppInfo *info; + GError *error = NULL; + DefaultForTypeData *data = user_data; + + g_assert_null (object); + + info = g_app_info_get_default_for_type_finish (result, &error); + + ensure_default_type_result (info, data, error); +} + +static void +on_default_for_uri_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GAppInfo *info; + GError *error = NULL; + DefaultForTypeData *data = user_data; + + g_assert_null (object); + + info = g_app_info_get_default_for_uri_scheme_finish (result, &error); + + ensure_default_type_result (info, data, error); +} + +static void +test_default_async (void) +{ + DefaultForTypeData data; + GAppInfo *info1, *info2, *info3; + GList *list; + GError *error = NULL; + + data.loop = g_main_loop_new (NULL, TRUE); + + info1 = create_app_info ("Blah1"); + info2 = create_app_info ("Blah2"); + info3 = create_app_info ("Blah3"); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, + "*assertion*content_type*failed*"); + g_app_info_get_default_for_type_async (NULL, FALSE, NULL, NULL, NULL); + g_test_assert_expected_messages (); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, + "*assertion*content_type*failed*"); + g_app_info_get_default_for_type_async ("", FALSE, NULL, NULL, NULL); + g_test_assert_expected_messages (); + + g_app_info_set_as_default_for_type (info1, "application/x-test", &error); + g_assert_no_error (error); + + g_app_info_set_as_default_for_type (info2, "application/x-test", &error); + g_assert_no_error (error); + + data.expected_info = info2; + g_app_info_get_default_for_type_async ("application/x-test", FALSE, + NULL, on_default_for_type_cb, &data); + g_main_loop_run (data.loop); + + /* now try adding something, but not setting as default */ + g_app_info_add_supports_type (info3, "application/x-test", &error); + g_assert_no_error (error); + + /* check that info2 is still default */ + data.expected_info = info2; + g_app_info_get_default_for_type_async ("application/x-test", FALSE, + NULL, on_default_for_type_cb, &data); + g_main_loop_run (data.loop); + + /* now remove info1 again */ + g_app_info_remove_supports_type (info1, "application/x-test", &error); + g_assert_no_error (error); + + /* and make sure info2 is still default */ + data.expected_info = info2; + g_app_info_get_default_for_type_async ("application/x-test", FALSE, + NULL, on_default_for_type_cb, &data); + g_main_loop_run (data.loop); + + g_app_info_set_as_default_for_type (info3, "x-scheme-handler/glib-async", &error); + g_assert_no_error (error); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, + "*assertion*uri_scheme*failed*"); + g_assert_null (g_app_info_get_default_for_uri_scheme (NULL)); + g_test_assert_expected_messages (); + + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, + "*assertion*uri_scheme*failed*"); + g_assert_null (g_app_info_get_default_for_uri_scheme ("")); + g_test_assert_expected_messages (); + + data.expected_info = info3; + g_app_info_get_default_for_uri_scheme_async ("glib-async", NULL, + on_default_for_uri_cb, &data); + g_main_loop_run (data.loop); + + /* now clean it all up */ + g_app_info_reset_type_associations ("application/x-test"); + + data.expected_info = NULL; + g_app_info_get_default_for_type_async ("application/x-test", FALSE, + NULL, on_default_for_type_cb, &data); + g_main_loop_run (data.loop); + + g_app_info_reset_type_associations ("x-scheme-handler/glib-async"); + + data.expected_info = NULL; + g_app_info_get_default_for_uri_scheme_async ("glib-async", NULL, + on_default_for_uri_cb, &data); + g_main_loop_run (data.loop); + + list = g_app_info_get_all_for_type ("application/x-test"); + g_assert_null (list); + + g_app_info_delete (info1); + g_app_info_delete (info2); + g_app_info_delete (info3); + + g_object_unref (info1); + g_object_unref (info2); + g_object_unref (info3); + + g_main_loop_unref (data.loop); +} + static void test_fallback (void) { @@ -326,20 +582,8 @@ wait_for_file (const gchar *want_this, const gchar *but_not_this, const gchar *or_this) { - guint retries = 600; - - /* I hate time-based conditions in tests, but this will wait up to one - * whole minute for "touch file" to finish running. I think it should - * be OK. - * - * 600 * 100ms = 60 seconds. - */ while (access (want_this, F_OK) != 0) - { - g_usleep (100000); /* 100ms */ - g_assert_cmpuint (retries, >, 0); - retries--; - } + g_usleep (100000); /* 100ms */ g_assert_cmpuint (access (but_not_this, F_OK), !=, 0); g_assert_cmpuint (access (or_this, F_OK), !=, 0); @@ -355,7 +599,11 @@ test_actions (void) const char *expected[] = { "frob", "tweak", "twiddle", "broken", NULL }; const gchar * const *actions; GDesktopAppInfo *appinfo; + const gchar *tmpdir; gchar *name; + gchar *frob_path; + gchar *tweak_path; + gchar *twiddle_path; appinfo = g_desktop_app_info_new_from_filename (g_test_get_filename (G_TEST_DIST, "appinfo-test-actions.desktop", NULL)); g_assert_nonnull (appinfo); @@ -380,17 +628,28 @@ test_actions (void) g_assert_true (g_utf8_validate (name, -1, NULL)); g_free (name); - unlink ("frob"); unlink ("tweak"); unlink ("twiddle"); + tmpdir = g_getenv ("G_TEST_TMPDIR"); + g_assert_nonnull (tmpdir); + frob_path = g_build_filename (tmpdir, "frob", NULL); + tweak_path = g_build_filename (tmpdir, "tweak", NULL); + twiddle_path = g_build_filename (tmpdir, "twiddle", NULL); + + g_assert_false (g_file_test (frob_path, G_FILE_TEST_EXISTS)); + g_assert_false (g_file_test (tweak_path, G_FILE_TEST_EXISTS)); + g_assert_false (g_file_test (twiddle_path, G_FILE_TEST_EXISTS)); g_desktop_app_info_launch_action (appinfo, "frob", NULL); - wait_for_file ("frob", "tweak", "twiddle"); + wait_for_file (frob_path, tweak_path, twiddle_path); g_desktop_app_info_launch_action (appinfo, "tweak", NULL); - wait_for_file ("tweak", "frob", "twiddle"); + wait_for_file (tweak_path, frob_path, twiddle_path); g_desktop_app_info_launch_action (appinfo, "twiddle", NULL); - wait_for_file ("twiddle", "frob", "tweak"); + wait_for_file (twiddle_path, frob_path, tweak_path); + g_free (frob_path); + g_free (tweak_path); + g_free (twiddle_path); g_object_unref (appinfo); } @@ -416,6 +675,7 @@ run_apps (const gchar *command, argv[2] = g_strdup (arg); argv[3] = NULL; + g_assert_true (g_file_test (argv[0], G_FILE_TEST_IS_EXECUTABLE)); envp = g_get_environ (); if (with_usr) @@ -607,9 +867,12 @@ test_search (void) assert_search ("image viewer", "", TRUE, TRUE, NULL, NULL); /* There're "flatpak" apps (clocks) installed as well - they should *not* - * match the prefix command ("/bin/sh") in the Exec= line though. + * match the prefix command ("/bin/sh") in the Exec= line though. Then with + * substring matching, Image Viewer (eog) should be in next group because it + * contains "Slideshow" in its keywords. */ - assert_search ("sh", "gnome-terminal.desktop\n", TRUE, FALSE, NULL, NULL); + assert_search ("sh", "gnome-terminal.desktop\n" + "eog.desktop\n", TRUE, FALSE, NULL, NULL); /* "frobnicator.desktop" is ignored by get_all() because the binary is * missing, but search should still find it (to avoid either stale results @@ -624,11 +887,12 @@ test_search (void) assert_search ("files file fil fi f", "nautilus.desktop\n" "gedit.desktop\n", TRUE, TRUE, NULL, NULL); - /* "con" will match "connect" and "contacts" on name but dconf only on - * the "config" keyword + /* "con" will match "connect" and "contacts" on name with prefix match in + * first group, then match "Dconf Editor" and "Desktop Icons" with substring + * match in next group. */ - assert_search ("con", "nautilus-connect-server.desktop gnome-contacts.desktop\n" - "dconf-editor.desktop\n", TRUE, TRUE, NULL, NULL); + assert_search ("con", "gnome-contacts.desktop nautilus-connect-server.desktop\n" + "dconf-editor.desktop nautilus-classic.desktop\n", TRUE, TRUE, NULL, NULL); /* "gnome" will match "eye of gnome" from the user's directory, plus * matching "GNOME Clocks" X-GNOME-FullName. It's only a comment on @@ -736,9 +1000,61 @@ on_launch_started (GAppLaunchContext *context, GAppInfo *info, GVariant *platfor gboolean *invoked = data; g_assert_true (G_IS_APP_LAUNCH_CONTEXT (context)); + + if (TEST_IS_LAUNCH_CONTEXT (context)) + { + GVariantDict dict; + const char *sni; + char *expected_sni; + + g_assert_nonnull (platform_data); + g_variant_dict_init (&dict, platform_data); + g_assert_true ( + g_variant_dict_lookup (&dict, "startup-notification-id", "&s", &sni)); + expected_sni = g_app_launch_context_get_startup_notify_id (context, info, NULL); + g_assert_cmpstr (sni, ==, expected_sni); + + g_free (expected_sni); + g_variant_dict_clear (&dict); + } + else + { + /* Our default context doesn't fill in any platform data */ + g_assert_null (platform_data); + } + + g_assert_false (*invoked); + *invoked = TRUE; +} + +static void +on_launched (GAppLaunchContext *context, GAppInfo *info, GVariant *platform_data, gpointer data) +{ + gboolean *launched = data; + GVariantDict dict; + int pid; + + g_assert_true (G_IS_APP_LAUNCH_CONTEXT (context)); g_assert_true (G_IS_APP_INFO (info)); - /* Our default context doesn't fill in any platform data */ - g_assert_null (platform_data); + g_assert_nonnull (platform_data); + g_variant_dict_init (&dict, platform_data); + g_assert_true (g_variant_dict_lookup (&dict, "pid", "i", &pid, NULL)); + g_assert_cmpint (pid, >, 1); + + g_assert_false (*launched); + *launched = TRUE; + + g_variant_dict_clear (&dict); +} + +static void +on_launch_failed (GAppLaunchContext *context, const char *startup_notify_id, gpointer data) +{ + gboolean *invoked = data; + + g_assert_true (G_IS_APP_LAUNCH_CONTEXT (context)); + g_assert_nonnull (startup_notify_id); + g_test_message ("Application launch failed: %s", startup_notify_id); g_assert_false (*invoked); *invoked = TRUE; @@ -755,27 +1071,24 @@ test_launch_as_manager (void) gboolean retval; const gchar *path; gboolean invoked = FALSE; + gboolean launched = FALSE; + gboolean failed = FALSE; GAppLaunchContext *context; - if (g_getenv ("DISPLAY") == NULL || g_getenv ("DISPLAY")[0] == '\0') - { - g_test_skip ("No DISPLAY. Skipping test."); - return; - } - path = g_test_get_filename (G_TEST_BUILT, "appinfo-test.desktop", NULL); appinfo = g_desktop_app_info_new_from_filename (path); + g_assert_true (G_IS_APP_INFO (appinfo)); - if (appinfo == NULL) - { - g_test_skip ("appinfo-test binary not installed"); - return; - } - - context = g_app_launch_context_new (); + context = g_object_new (test_launch_context_get_type (), NULL); g_signal_connect (context, "launch-started", G_CALLBACK (on_launch_started), &invoked); + g_signal_connect (context, "launched", + G_CALLBACK (on_launched), + &launched); + g_signal_connect (context, "launch-failed", + G_CALLBACK (on_launch_failed), + &failed); retval = g_desktop_app_info_launch_uris_as_manager (appinfo, NULL, context, 0, NULL, NULL, NULL, NULL, @@ -783,8 +1096,12 @@ test_launch_as_manager (void) g_assert_no_error (error); g_assert_true (retval); g_assert_true (invoked); + g_assert_true (launched); + g_assert_false (failed); invoked = FALSE; + launched = FALSE; + failed = FALSE; retval = g_desktop_app_info_launch_uris_as_manager_with_fds (appinfo, NULL, context, 0, NULL, NULL, @@ -794,11 +1111,223 @@ test_launch_as_manager (void) g_assert_no_error (error); g_assert_true (retval); g_assert_true (invoked); + g_assert_true (launched); + g_assert_false (failed); g_object_unref (appinfo); g_assert_finalize_object (context); } +static void +test_launch_as_manager_fail (void) +{ + GAppLaunchContext *context; + GDesktopAppInfo *appinfo; + GError *error = NULL; + gboolean retval; + const gchar *path; + gboolean launch_started = FALSE; + gboolean launched = FALSE; + gboolean failed = FALSE; + + g_test_summary ("Tests that launch-errors are properly handled, we force " \ + "this by using invalid FD's values when launching as manager"); + + path = g_test_get_filename (G_TEST_BUILT, "appinfo-test.desktop", NULL); + appinfo = g_desktop_app_info_new_from_filename (path); + g_assert_true (G_IS_APP_INFO (appinfo)); + + context = g_object_new (test_launch_context_get_type (), NULL); + g_signal_connect (context, "launch-started", + G_CALLBACK (on_launch_started), + &launch_started); + g_signal_connect (context, "launched", + G_CALLBACK (on_launched), + &launched); + g_signal_connect (context, "launch-failed", + G_CALLBACK (on_launch_failed), + &failed); + + retval = g_desktop_app_info_launch_uris_as_manager_with_fds (appinfo, + NULL, context, 0, + NULL, NULL, + NULL, NULL, + 3000, 3001, 3002, + &error); + g_assert_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_FAILED); + g_assert_false (retval); + g_assert_true (launch_started); + g_assert_false (launched); + g_assert_true (failed); + + g_clear_error (&error); + g_object_unref (appinfo); + g_assert_finalize_object (context); +} + +static GAppInfo * +create_app_info_toucher (const char *name, + const char *touched_file_name, + const char *handled_type, + char **out_file_path) +{ + GError *error = NULL; + GAppInfo *info; + gchar *command_line; + gchar *file_path; + gchar *tmpdir; + + g_assert_nonnull (out_file_path); + + tmpdir = g_dir_make_tmp ("desktop-app-info-launch-XXXXXX", &error); + g_assert_no_error (error); + + file_path = g_build_filename (tmpdir, touched_file_name, NULL); + command_line = g_strdup_printf ("touch %s", file_path); + + info = create_command_line_app_info (name, command_line, handled_type); + *out_file_path = g_steal_pointer (&file_path); + + g_free (tmpdir); + g_free (command_line); + + return info; +} + +static void +test_default_uri_handler (void) +{ + GError *error = NULL; + gchar *file_path = NULL; + GAppInfo *info; + + info = create_app_info_toucher ("Touch Handled", "handled", + "x-scheme-handler/glib-touch", + &file_path); + g_assert_true (G_IS_APP_INFO (info)); + g_assert_nonnull (file_path); + + g_assert_true (g_app_info_launch_default_for_uri ("glib-touch://touch-me", + NULL, &error)); + g_assert_no_error (error); + + while (!g_file_test (file_path, G_FILE_TEST_IS_REGULAR)); + g_assert_true (g_file_test (file_path, G_FILE_TEST_IS_REGULAR)); + + g_assert_false (g_app_info_launch_default_for_uri ("glib-INVALID-touch://touch-me", + NULL, &error)); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_clear_error (&error); + + g_object_unref (info); + g_free (file_path); +} + +static void +on_launch_default_for_uri_success_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GError *error = NULL; + gboolean *called = user_data; + + g_assert_true (g_app_info_launch_default_for_uri_finish (result, &error)); + g_assert_no_error (error); + + *called = TRUE; +} + +static void +on_launch_default_for_uri_not_found_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GError *error = NULL; + GMainLoop *loop = user_data; + + g_assert_false (g_app_info_launch_default_for_uri_finish (result, &error)); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED); + g_clear_error (&error); + + g_main_loop_quit (loop); +} + +static void +on_launch_default_for_uri_cancelled_cb (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GError *error = NULL; + GMainLoop *loop = user_data; + + g_assert_false (g_app_info_launch_default_for_uri_finish (result, &error)); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_CANCELLED); + g_clear_error (&error); + + g_main_loop_quit (loop); +} + +static void +test_default_uri_handler_async (void) +{ + GCancellable *cancellable; + gchar *file_path = NULL; + GAppInfo *info; + GMainLoop *loop; + gboolean called = FALSE; + gint64 start_time, touch_time; + + loop = g_main_loop_new (NULL, FALSE); + info = create_app_info_toucher ("Touch Handled", "handled-async", + "x-scheme-handler/glib-async-touch", + &file_path); + g_assert_true (G_IS_APP_INFO (info)); + g_assert_nonnull (file_path); + + start_time = g_get_real_time (); + g_app_info_launch_default_for_uri_async ("glib-async-touch://touch-me", NULL, + NULL, + on_launch_default_for_uri_success_cb, + &called); + + while (!g_file_test (file_path, G_FILE_TEST_IS_REGULAR) || !called) + g_main_context_iteration (NULL, FALSE); + + touch_time = g_get_real_time () - start_time; + g_assert_true (called); + g_assert_true (g_file_test (file_path, G_FILE_TEST_IS_REGULAR)); + + g_unlink (file_path); + g_assert_false (g_file_test (file_path, G_FILE_TEST_IS_REGULAR)); + + g_app_info_launch_default_for_uri_async ("glib-async-INVALID-touch://touch-me", + NULL, NULL, + on_launch_default_for_uri_not_found_cb, + loop); + g_main_loop_run (loop); + + cancellable = g_cancellable_new (); + g_app_info_launch_default_for_uri_async ("glib-async-touch://touch-me", NULL, + cancellable, + on_launch_default_for_uri_cancelled_cb, + loop); + g_cancellable_cancel (cancellable); + g_main_loop_run (loop); + + /* If started, our touch app would take some time to actually write the + * file to disk, so let's wait a bit here to ensure that the file isn't + * inadvertently getting created when a launch operation is canceled up + * front. Give it 3× as long as the successful case took, to allow for + * some variance. + */ + g_usleep (touch_time * 3); + g_assert_false (g_file_test (file_path, G_FILE_TEST_IS_REGULAR)); + + g_object_unref (info); + g_main_loop_unref (loop); + g_free (file_path); +} + /* Test if Desktop-File Id is correctly formed */ static void test_id (void) @@ -811,10 +1340,543 @@ test_id (void) g_free (result); } +static const char * +get_terminal_divider (const char *terminal_name) +{ + if (g_str_equal (terminal_name, "xdg-terminal-exec")) + return NULL; + if (g_str_equal (terminal_name, "kgx")) + return "-e"; + if (g_str_equal (terminal_name, "gnome-terminal")) + return "--"; + if (g_str_equal (terminal_name, "tilix")) + return "-e"; + if (g_str_equal (terminal_name, "konsole")) + return "-e"; + if (g_str_equal (terminal_name, "nxterm")) + return "-e"; + if (g_str_equal (terminal_name, "color-xterm")) + return "-e"; + if (g_str_equal (terminal_name, "rxvt")) + return "-e"; + if (g_str_equal (terminal_name, "dtterm")) + return "-e"; + if (g_str_equal (terminal_name, "xterm")) + return "-e"; + if (g_str_equal (terminal_name, "mate-terminal")) + return "-x"; + if (g_str_equal (terminal_name, "xfce4-terminal")) + return "-x"; + + g_return_val_if_reached (NULL); +} + +typedef enum { + TERMINAL_LAUNCH_TYPE_COMMAND_LINE_WITH_PATH_OVERRIDE, + TERMINAL_LAUNCH_TYPE_COMMAND_LINE_WITH_CONTEXT, + TERMINAL_LAUNCH_TYPE_KEY_FILE_WITH_PATH, +} TerminalLaunchType; + +typedef struct { + const char *exec; + TerminalLaunchType type; +} TerminalLaunchData; + +static TerminalLaunchData * +terminal_launch_data_new (const char *exec, TerminalLaunchType type) +{ + TerminalLaunchData *d = NULL; + + d = g_new0 (TerminalLaunchData, 1); + d->exec = exec; + d->type = type; + + return d; +} + +static void +test_launch_uris_with_terminal (gconstpointer data) +{ + int fd; + int ret; + int flags; + int terminal_divider_arg_length; + const TerminalLaunchData *launch_data = data; + const char *terminal_exec = launch_data->exec; + char *old_path = NULL; + char *command_line; + char *bin_path; + char *terminal_path; + char *output_fd_path; + char *script_contents; + char *output_contents = NULL; + char *sh; + GAppInfo *app_info; + GList *uris; + GList *paths; + GStrv output_args; + GError *error = NULL; + GInputStream *input_stream; + GDataInputStream *data_input_stream; + GAppLaunchContext *launch_context; + + sh = g_find_program_in_path ("sh"); + g_assert_nonnull (sh); + + bin_path = g_dir_make_tmp ("bin-path-XXXXXX", &error); + g_assert_no_error (error); + + launch_context = g_object_new (test_launch_context_get_type (), NULL); + + switch (launch_data->type) + { + case TERMINAL_LAUNCH_TYPE_COMMAND_LINE_WITH_PATH_OVERRIDE: + old_path = g_strdup (g_getenv ("PATH")); + g_assert_true (g_setenv ("PATH", bin_path, TRUE)); + break; + + case TERMINAL_LAUNCH_TYPE_COMMAND_LINE_WITH_CONTEXT: + g_app_launch_context_setenv (launch_context, "PATH", bin_path); + break; + + case TERMINAL_LAUNCH_TYPE_KEY_FILE_WITH_PATH: + g_app_launch_context_setenv (launch_context, "PATH", "/not/valid"); + break; + + default: + g_assert_not_reached (); + } + + terminal_path = g_build_filename (bin_path, terminal_exec, NULL); + output_fd_path = g_build_filename (bin_path, "fifo", NULL); + + ret = mkfifo (output_fd_path, 0600); + g_assert_cmpint (ret, ==, 0); + + fd = g_open (output_fd_path, O_RDONLY | O_CLOEXEC | O_NONBLOCK, 0); + g_assert_cmpint (fd, >=, 0); + + flags = fcntl (fd, F_GETFL); + g_assert_cmpint (flags, >=, 0); + + ret = fcntl (fd, F_SETFL, flags & ~O_NONBLOCK); + g_assert_cmpint (ret, ==, 0); + + input_stream = g_unix_input_stream_new (fd, TRUE); + data_input_stream = g_data_input_stream_new (input_stream); + script_contents = g_strdup_printf ("#!%s\n" \ + "out='%s'\n" + "printf '%%s\\n' \"$*\" > \"$out\"\n", + sh, + output_fd_path); + g_file_set_contents (terminal_path, script_contents, -1, &error); + g_assert_no_error (error); + g_assert_cmpint (g_chmod (terminal_path, 0500), ==, 0); + + g_test_message ("Fake '%s' terminal created as: %s", terminal_exec, terminal_path); + + command_line = g_strdup_printf ("true %s-argument", terminal_exec); + + if (launch_data->type == TERMINAL_LAUNCH_TYPE_KEY_FILE_WITH_PATH) + { + GKeyFile *key_file; + char *key_file_contents; + const char base_file[] = + "[Desktop Entry]\n" + "Type=Application\n" + "Name=terminal launched app\n" + "Terminal=true\n" + "Path=%s\n" + "Exec=%s\n"; + + key_file = g_key_file_new (); + key_file_contents = g_strdup_printf (base_file, bin_path, command_line); + + g_assert_true ( + g_key_file_load_from_data (key_file, key_file_contents, -1, + G_KEY_FILE_NONE, NULL)); + + app_info = (GAppInfo*) g_desktop_app_info_new_from_keyfile (key_file); + g_assert_true (G_IS_DESKTOP_APP_INFO (app_info)); + g_assert_true ( + g_desktop_app_info_get_boolean (G_DESKTOP_APP_INFO (app_info), "Terminal")); + + g_key_file_unref (key_file); + g_free (key_file_contents); + } + else + { + app_info = g_app_info_create_from_commandline (command_line, + "Test App on Terminal", + G_APP_INFO_CREATE_NEEDS_TERMINAL | + G_APP_INFO_CREATE_SUPPORTS_URIS, + &error); + g_assert_no_error (error); + } + + paths = g_list_prepend (NULL, bin_path); + uris = g_list_prepend (NULL, g_filename_to_uri (bin_path, NULL, &error)); + g_assert_no_error (error); + + paths = g_list_prepend (paths, (gpointer) g_get_user_data_dir ()); + uris = g_list_append (uris, g_filename_to_uri (g_get_user_data_dir (), NULL, &error)); + g_assert_no_error (error); + + g_assert_cmpint (g_list_length (paths), ==, 2); + g_app_info_launch_uris (app_info, uris, launch_context, &error); + g_assert_no_error (error); + + while (output_contents == NULL) + { + output_contents = + g_data_input_stream_read_upto (data_input_stream, "\n", 1, NULL, NULL, &error); + g_assert_no_error (error); + + if (output_contents == NULL) + g_usleep (G_USEC_PER_SEC / 10); + } + g_test_message ("'%s' called with arguments: '%s'", terminal_exec, output_contents); + + g_data_input_stream_read_byte (data_input_stream, NULL, &error); + g_assert_no_error (error); + + output_args = g_strsplit (output_contents, " ", -1); + g_clear_pointer (&output_contents, g_free); + + terminal_divider_arg_length = (get_terminal_divider (terminal_exec) != NULL) ? 1 : 0; + g_assert_cmpuint (g_strv_length (output_args), ==, 3 + terminal_divider_arg_length); + if (terminal_divider_arg_length == 1) + { + g_assert_cmpstr (output_args[0], ==, get_terminal_divider (terminal_exec)); + g_assert_cmpstr (output_args[1], ==, "true"); + g_assert_cmpstr (output_args[2], ==, command_line + 5); + } + else + { + g_assert_cmpstr (output_args[0], ==, "true"); + g_assert_cmpstr (output_args[1], ==, command_line + 5); + } + paths = g_list_delete_link (paths, + g_list_find_custom (paths, output_args[2 + terminal_divider_arg_length], g_str_equal)); + g_assert_cmpint (g_list_length (paths), ==, 1); + g_clear_pointer (&output_args, g_strfreev); + + while (output_contents == NULL) + { + output_contents = + g_data_input_stream_read_upto (data_input_stream, "\n", 1, NULL, NULL, &error); + g_assert_no_error (error); + + if (output_contents == NULL) + g_usleep (G_USEC_PER_SEC / 10); + } + g_test_message ("'%s' called with arguments: '%s'", terminal_exec, output_contents); + + g_data_input_stream_read_byte (data_input_stream, NULL, &error); + g_assert_no_error (error); + + output_args = g_strsplit (output_contents, " ", -1); + g_clear_pointer (&output_contents, g_free); + g_assert_cmpuint (g_strv_length (output_args), ==, 3 + terminal_divider_arg_length); + if (terminal_divider_arg_length > 0) + { + g_assert_cmpstr (output_args[0], ==, get_terminal_divider (terminal_exec)); + g_assert_cmpstr (output_args[1], ==, "true"); + g_assert_cmpstr (output_args[2], ==, command_line + 5); + } + else + { + g_assert_cmpstr (output_args[0], ==, "true"); + g_assert_cmpstr (output_args[1], ==, command_line + 5); + } + paths = g_list_delete_link (paths, + g_list_find_custom (paths, output_args[2 + terminal_divider_arg_length], g_str_equal)); + g_assert_cmpint (g_list_length (paths), ==, 0); + g_clear_pointer (&output_args, g_strfreev); + + g_assert_null (paths); + + if (launch_data->type == TERMINAL_LAUNCH_TYPE_COMMAND_LINE_WITH_PATH_OVERRIDE) + g_assert_true (g_setenv ("PATH", old_path, TRUE)); + + g_close (fd, &error); + g_assert_no_error (error); + + g_free (sh); + g_free (command_line); + g_free (bin_path); + g_free (terminal_path); + g_free (output_fd_path); + g_free (script_contents); + g_free (old_path); + g_clear_pointer (&output_args, g_strfreev); + g_clear_pointer (&output_contents, g_free); + g_clear_object (&data_input_stream); + g_clear_object (&input_stream); + g_clear_object (&app_info); + g_clear_object (&launch_context); + g_clear_error (&error); + g_clear_list (&paths, NULL); + g_clear_list (&uris, g_free); +} + +static void +test_launch_uris_with_invalid_terminal (void) +{ + char *old_path; + char *bin_path; + GAppInfo *app_info; + GError *error = NULL; + + bin_path = g_dir_make_tmp ("bin-path-XXXXXX", &error); + g_assert_no_error (error); + + old_path = g_strdup (g_getenv ("PATH")); + g_assert_true (g_setenv ("PATH", bin_path, TRUE)); + + app_info = g_app_info_create_from_commandline ("true invalid-glib-terminal", + "Test App on Invalid Terminal", + G_APP_INFO_CREATE_NEEDS_TERMINAL | + G_APP_INFO_CREATE_SUPPORTS_URIS, + &error); + g_assert_no_error (error); + + g_app_info_launch_uris (app_info, NULL, NULL, &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); + g_clear_error (&error); + + g_assert_true (g_setenv ("PATH", old_path, TRUE)); + + g_clear_object (&app_info); + g_clear_error (&error); + g_free (bin_path); + g_free (old_path); +} + +static void +test_app_path (void) +{ + GDesktopAppInfo *appinfo; + const char *desktop_path; + + desktop_path = g_test_get_filename (G_TEST_BUILT, "appinfo-test-path.desktop", NULL); + appinfo = g_desktop_app_info_new_from_filename (desktop_path); + + g_assert_true (G_IS_DESKTOP_APP_INFO (appinfo)); + + g_clear_object (&appinfo); +} + +static void +test_app_path_wrong (void) +{ + GKeyFile *key_file; + GDesktopAppInfo *appinfo; + const gchar bad_try_exec_file_contents[] = + "[Desktop Entry]\n" + "Type=Application\n" + "Name=appinfo-test\n" + "TryExec=appinfo-test\n" + "Path=this-must-not-exist‼\n" + "Exec=true\n"; + const gchar bad_exec_file_contents[] = + "[Desktop Entry]\n" + "Type=Application\n" + "Name=appinfo-test\n" + "TryExec=true\n" + "Path=this-must-not-exist‼\n" + "Exec=appinfo-test\n"; + + g_assert_true ( + g_file_test (g_test_get_filename (G_TEST_BUILT, "appinfo-test", NULL), + G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_EXECUTABLE)); + + key_file = g_key_file_new (); + + g_assert_true ( + g_key_file_load_from_data (key_file, bad_try_exec_file_contents, -1, + G_KEY_FILE_NONE, NULL)); + + appinfo = g_desktop_app_info_new_from_keyfile (key_file); + g_assert_false (G_IS_DESKTOP_APP_INFO (appinfo)); + + g_assert_true ( + g_key_file_load_from_data (key_file, bad_exec_file_contents, -1, + G_KEY_FILE_NONE, NULL)); + + appinfo = g_desktop_app_info_new_from_keyfile (key_file); + g_assert_false (G_IS_DESKTOP_APP_INFO (appinfo)); + + g_clear_pointer (&key_file, g_key_file_unref); + g_clear_object (&appinfo); +} + +static void +test_launch_startup_notify_fail (void) +{ + GAppInfo *app_info; + GAppLaunchContext *context; + GError *error = NULL; + gboolean launch_started; + gboolean launch_failed; + gboolean launched; + GList *uris; + + app_info = g_app_info_create_from_commandline ("this-must-not-exist‼", + "failing app", + G_APP_INFO_CREATE_NONE | + G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION, + &error); + g_assert_no_error (error); + + context = g_object_new (test_launch_context_get_type (), NULL); + g_signal_connect (context, "launch-started", + G_CALLBACK (on_launch_started), + &launch_started); + g_signal_connect (context, "launched", + G_CALLBACK (on_launch_started), + &launched); + g_signal_connect (context, "launch-failed", + G_CALLBACK (on_launch_failed), + &launch_failed); + + launch_started = FALSE; + launch_failed = FALSE; + launched = FALSE; + uris = g_list_prepend (NULL, g_file_new_for_uri ("foo://bar")); + uris = g_list_prepend (uris, g_file_new_for_uri ("bar://foo")); + g_assert_false (g_app_info_launch (app_info, uris, context, &error)); + g_assert_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT); + g_assert_true (launch_started); + g_assert_true (launch_failed); + g_assert_false (launched); + + g_clear_error (&error); + g_clear_object (&app_info); + g_clear_object (&context); + g_clear_list (&uris, g_object_unref); +} + +static void +test_launch_fail (void) +{ + GAppInfo *app_info; + GError *error = NULL; + + app_info = g_app_info_create_from_commandline ("this-must-not-exist‼", + "failing app", + G_APP_INFO_CREATE_NONE, + &error); + g_assert_no_error (error); + + g_assert_false (g_app_info_launch (app_info, NULL, NULL, &error)); + g_assert_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT); + + g_clear_error (&error); + g_clear_object (&app_info); +} + +static void +test_launch_fail_absolute_path (void) +{ + GAppInfo *app_info; + GError *error = NULL; + + app_info = g_app_info_create_from_commandline ("/nothing/of/this-must-exist‼", + NULL, + G_APP_INFO_CREATE_NONE, + &error); + g_assert_no_error (error); + + g_assert_false (g_app_info_launch (app_info, NULL, NULL, &error)); + g_assert_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT); + + g_clear_error (&error); + g_clear_object (&app_info); + + app_info = g_app_info_create_from_commandline ("/", + NULL, + G_APP_INFO_CREATE_NONE, + &error); + g_assert_no_error (error); + + g_assert_false (g_app_info_launch (app_info, NULL, NULL, &error)); + g_assert_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT); + + g_clear_error (&error); + g_clear_object (&app_info); +} + +static void +async_result_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GAsyncResult **result_out = user_data; + + g_assert (*result_out == NULL); + *result_out = g_object_ref (result); + g_main_context_wakeup (g_main_context_get_thread_default ()); +} + +static void +test_launch_fail_dbus (void) +{ + GTestDBus *bus = NULL; + GDesktopAppInfo *app_info = NULL; + GAppLaunchContext *context = NULL; + GAsyncResult *result = NULL; + GError *error = NULL; + + /* Set up a test session bus to ensure that launching the app happens using + * D-Bus rather than spawning. */ + bus = g_test_dbus_new (G_TEST_DBUS_NONE); + g_test_dbus_up (bus); + + app_info = g_desktop_app_info_new_from_filename (g_test_get_filename (G_TEST_DIST, "org.gtk.test.dbusappinfo.desktop", NULL)); + g_assert_nonnull (app_info); + + g_assert_true (g_desktop_app_info_has_key (app_info, "DBusActivatable")); + + context = g_app_launch_context_new (); + + g_app_info_launch_uris_async (G_APP_INFO (app_info), NULL, context, NULL, async_result_cb, &result); + + while (result == NULL) + g_main_context_iteration (NULL, TRUE); + + g_assert_false (g_app_info_launch_uris_finish (G_APP_INFO (app_info), result, &error)); + g_assert_error (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN); + + g_test_dbus_down (bus); + g_clear_object (&bus); + + g_clear_error (&error); + g_clear_object (&result); + g_clear_object (&context); + g_clear_object (&app_info); +} + int main (int argc, char *argv[]) { + guint i; + const gchar *supported_terminals[] = { + "xdg-terminal-exec", + "kgx", + "gnome-terminal", + "mate-terminal", + "xfce4-terminal", + "tilix", + "konsole", + "nxterm", + "color-xterm", + "rxvt", + "dtterm", + "xterm", + }; + /* While we use %G_TEST_OPTION_ISOLATE_DIRS to create temporary directories * for each of the tests, we want to use the system MIME registry, assuming * that it exists and correctly has shared-mime-info installed. */ @@ -824,6 +1886,7 @@ main (int argc, g_test_add_func ("/desktop-app-info/delete", test_delete); g_test_add_func ("/desktop-app-info/default", test_default); + g_test_add_func ("/desktop-app-info/default-async", test_default_async); g_test_add_func ("/desktop-app-info/fallback", test_fallback); g_test_add_func ("/desktop-app-info/lastused", test_last_used); g_test_add_func ("/desktop-app-info/extra-getters", test_extra_getters); @@ -831,8 +1894,49 @@ main (int argc, g_test_add_func ("/desktop-app-info/search", test_search); g_test_add_func ("/desktop-app-info/implements", test_implements); g_test_add_func ("/desktop-app-info/show-in", test_show_in); + g_test_add_func ("/desktop-app-info/app-path", test_app_path); + g_test_add_func ("/desktop-app-info/app-path/wrong", test_app_path_wrong); + g_test_add_func ("/desktop-app-info/launch/fail", test_launch_fail); + g_test_add_func ("/desktop-app-info/launch/fail-absolute-path", test_launch_fail_absolute_path); + g_test_add_func ("/desktop-app-info/launch/fail-startup-notify", test_launch_startup_notify_fail); + g_test_add_func ("/desktop-app-info/launch/fail-dbus", test_launch_fail_dbus); g_test_add_func ("/desktop-app-info/launch-as-manager", test_launch_as_manager); + g_test_add_func ("/desktop-app-info/launch-as-manager/fail", test_launch_as_manager_fail); + g_test_add_func ("/desktop-app-info/launch-default-uri-handler", test_default_uri_handler); + g_test_add_func ("/desktop-app-info/launch-default-uri-handler-async", test_default_uri_handler_async); g_test_add_func ("/desktop-app-info/id", test_id); + for (i = 0; i < G_N_ELEMENTS (supported_terminals); i++) + { + char *path; + + path = g_strdup_printf ("/desktop-app-info/launch-uris-with-terminal/with-path/%s", + supported_terminals[i]); + g_test_add_data_func_full (path, + terminal_launch_data_new (supported_terminals[i], + TERMINAL_LAUNCH_TYPE_COMMAND_LINE_WITH_PATH_OVERRIDE), + test_launch_uris_with_terminal, g_free); + g_clear_pointer (&path, g_free); + + path = g_strdup_printf ("/desktop-app-info/launch-uris-with-terminal/with-context/%s", + supported_terminals[i]); + g_test_add_data_func_full (path, + terminal_launch_data_new (supported_terminals[i], + TERMINAL_LAUNCH_TYPE_COMMAND_LINE_WITH_CONTEXT), + test_launch_uris_with_terminal, g_free); + g_clear_pointer (&path, g_free); + + path = g_strdup_printf ("/desktop-app-info/launch-uris-with-terminal/with-desktop-path/%s", + supported_terminals[i]); + g_test_add_data_func_full (path, + terminal_launch_data_new (supported_terminals[i], + TERMINAL_LAUNCH_TYPE_KEY_FILE_WITH_PATH), + test_launch_uris_with_terminal, g_free); + g_clear_pointer (&path, g_free); + } + + g_test_add_func ("/desktop-app-info/launch-uris-with-terminal/invalid-glib-terminal", + test_launch_uris_with_invalid_terminal); + return g_test_run (); } |