glib/gio/tests/dbus-appinfo.c
Philip Withnall 8c3fda5c8d
tests: Skip unsupported dbus-appinfo test on GNU/Hurd for the moment
Parts of the `dbus-appinfo` test need support for converting an FD to a
path, and Hurd doesn’t currently allow that (see
https://gitlab.gnome.org/GNOME/glib/-/merge_requests/4396#note_2279923).

Since there’s no fix for that visible in the medium term (new kernel
APIs will need to be added), skip parts of the `dbus-appinfo` test which
require that functionality for now.

This prevents the whole test from failing, and means we can usefully get
results from the parts of it which don’t depend on converting FDs to
paths.

Signed-off-by: Philip Withnall <pwithnall@gnome.org>

Helps: #3538
2024-12-10 11:47:43 +00:00

716 lines
22 KiB
C

/*
* Copyright © 2013 Canonical Limited
* Copyright © 2024 GNOME Foundation Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* Authors: Ryan Lortie <desrt@desrt.ca>
* Julian Sparber <jsparber@gnome.org>
*/
#include <gio/gio.h>
#include <gio/gdesktopappinfo.h>
#include "gdbus-sessionbus.h"
#include "fake-desktop-portal.h"
#include "fake-document-portal.h"
static GDesktopAppInfo *appinfo;
static int current_state;
static gboolean saw_startup_id;
static gboolean requested_startup_id;
static GType test_app_launch_context_get_type (void);
typedef GAppLaunchContext TestAppLaunchContext;
typedef GAppLaunchContextClass TestAppLaunchContextClass;
G_DEFINE_TYPE (TestAppLaunchContext, test_app_launch_context, G_TYPE_APP_LAUNCH_CONTEXT)
static gchar *
test_app_launch_context_get_startup_notify_id (GAppLaunchContext *context,
GAppInfo *info,
GList *uris)
{
requested_startup_id = TRUE;
return g_strdup ("expected startup id");
}
static void
test_app_launch_context_init (TestAppLaunchContext *ctx)
{
}
static void
test_app_launch_context_class_init (GAppLaunchContextClass *class)
{
class->get_startup_notify_id = test_app_launch_context_get_startup_notify_id;
}
static GType test_application_get_type (void);
typedef GApplication TestApplication;
typedef GApplicationClass TestApplicationClass;
G_DEFINE_TYPE (TestApplication, test_application, G_TYPE_APPLICATION)
static void
saw_action (const gchar *action)
{
/* This is the main driver of the test. It's a bit of a state
* machine.
*
* Each time some event arrives on the app, it calls here to report
* which event it was. The initial activation of the app is what
* starts everything in motion (starting from state 0). At each
* state, we assert that we receive the expected event, send the next
* event, then update the current_state variable so we do the correct
* thing next time.
*/
switch (current_state)
{
case 0: g_assert_cmpstr (action, ==, "activate");
/* Let's try another activation... */
g_app_info_launch (G_APP_INFO (appinfo), NULL, NULL, NULL);
current_state = 1; return; case 1: g_assert_cmpstr (action, ==, "activate");
/* Now let's try opening some files... */
{
GList *files;
files = g_list_prepend (NULL, g_file_new_for_uri ("file:///a/b"));
files = g_list_append (files, g_file_new_for_uri ("file:///c/d"));
g_app_info_launch (G_APP_INFO (appinfo), files, NULL, NULL);
g_list_free_full (files, g_object_unref);
}
current_state = 2; return; case 2: g_assert_cmpstr (action, ==, "open");
/* Now action activations... */
g_desktop_app_info_launch_action (appinfo, "frob", NULL);
current_state = 3; return; case 3: g_assert_cmpstr (action, ==, "frob");
g_desktop_app_info_launch_action (appinfo, "tweak", NULL);
current_state = 4; return; case 4: g_assert_cmpstr (action, ==, "tweak");
g_desktop_app_info_launch_action (appinfo, "twiddle", NULL);
current_state = 5; return; case 5: g_assert_cmpstr (action, ==, "twiddle");
/* Now launch the app with startup notification */
{
GAppLaunchContext *ctx;
g_assert (saw_startup_id == FALSE);
ctx = g_object_new (test_app_launch_context_get_type (), NULL);
g_app_info_launch (G_APP_INFO (appinfo), NULL, ctx, NULL);
g_assert (requested_startup_id);
requested_startup_id = FALSE;
g_object_unref (ctx);
}
current_state = 6; return; case 6: g_assert_cmpstr (action, ==, "activate"); g_assert (saw_startup_id);
saw_startup_id = FALSE;
/* Now do the same for an action */
{
GAppLaunchContext *ctx;
g_assert (saw_startup_id == FALSE);
ctx = g_object_new (test_app_launch_context_get_type (), NULL);
g_desktop_app_info_launch_action (appinfo, "frob", ctx);
g_assert (requested_startup_id);
requested_startup_id = FALSE;
g_object_unref (ctx);
}
current_state = 7; return; case 7: g_assert_cmpstr (action, ==, "frob"); g_assert (saw_startup_id);
saw_startup_id = FALSE;
/* Now quit... */
g_desktop_app_info_launch_action (appinfo, "quit", NULL);
current_state = 8; return; case 8: g_assert_not_reached ();
}
}
static void
test_application_frob (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
g_assert (parameter == NULL);
saw_action ("frob");
}
static void
test_application_tweak (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
g_assert (parameter == NULL);
saw_action ("tweak");
}
static void
test_application_twiddle (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
g_assert (parameter == NULL);
saw_action ("twiddle");
}
static void
test_application_quit (GSimpleAction *action,
GVariant *parameter,
gpointer user_data)
{
GApplication *application = user_data;
g_application_quit (application);
}
static const GActionEntry app_actions[] = {
{ "frob", test_application_frob, NULL, NULL, NULL, { 0 } },
{ "tweak", test_application_tweak, NULL, NULL, NULL, { 0 } },
{ "twiddle", test_application_twiddle, NULL, NULL, NULL, { 0 } },
{ "quit", test_application_quit, NULL, NULL, NULL, { 0 } }
};
static void
test_application_activate (GApplication *application)
{
/* Unbalanced, but that's OK because we will quit() */
g_application_hold (application);
saw_action ("activate");
}
static void
test_application_open (GApplication *application,
GFile **files,
gint n_files,
const gchar *hint)
{
GFile *f;
g_assert_cmpstr (hint, ==, "");
g_assert_cmpint (n_files, ==, 2);
f = g_file_new_for_uri ("file:///a/b");
g_assert (g_file_equal (files[0], f));
g_object_unref (f);
f = g_file_new_for_uri ("file:///c/d");
g_assert (g_file_equal (files[1], f));
g_object_unref (f);
saw_action ("open");
}
static void
test_application_startup (GApplication *application)
{
G_APPLICATION_CLASS (test_application_parent_class)
->startup (application);
g_action_map_add_action_entries (G_ACTION_MAP (application), app_actions, G_N_ELEMENTS (app_actions), application);
}
static void
test_application_before_emit (GApplication *application,
GVariant *platform_data)
{
const gchar *startup_id;
gsize i;
g_assert (!saw_startup_id);
const gchar *startup_id_keys[] = {
"desktop-startup-id",
"activation-token",
NULL,
};
for (i = 0; startup_id_keys[i] != NULL; i++)
{
if (!g_variant_lookup (platform_data, startup_id_keys[i], "&s", &startup_id))
return;
g_assert_cmpstr (startup_id, ==, "expected startup id");
}
saw_startup_id = TRUE;
}
static void
test_application_init (TestApplication *app)
{
}
static void
test_application_class_init (GApplicationClass *class)
{
class->before_emit = test_application_before_emit;
class->startup = test_application_startup;
class->activate = test_application_activate;
class->open = test_application_open;
}
static void
test_dbus_appinfo (void)
{
const gchar *argv[] = { "myapp", NULL };
TestApplication *app;
int status;
gchar *desktop_file = NULL;
desktop_file = g_test_build_filename (G_TEST_DIST,
"org.gtk.test.dbusappinfo.desktop",
NULL);
appinfo = g_desktop_app_info_new_from_filename (desktop_file);
g_assert (appinfo != NULL);
g_free (desktop_file);
app = g_object_new (test_application_get_type (),
"application-id", "org.gtk.test.dbusappinfo",
"flags", G_APPLICATION_HANDLES_OPEN,
NULL);
status = g_application_run (app, 1, (gchar **) argv);
g_assert_cmpint (status, ==, 0);
g_assert_cmpint (current_state, ==, 8);
g_object_unref (appinfo);
g_object_unref (app);
}
static GType test_flatpak_application_get_type (void);
typedef GApplication TestFlatpakApplication;
typedef GApplicationClass TestFlatpakApplicationClass;
G_DEFINE_TYPE (TestFlatpakApplication, test_flatpak_application, G_TYPE_APPLICATION)
static void
on_flatpak_launch_uris_finish (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GApplication *app = user_data;
GError *error = NULL;
g_app_info_launch_uris_finish (G_APP_INFO (object), result, &error);
g_assert_no_error (error);
g_assert_true (requested_startup_id);
g_assert_true (saw_startup_id);
g_application_release (app);
}
static void
on_flatpak_activate (GApplication *app,
gpointer user_data)
{
GAppLaunchContext *ctx;
GDesktopAppInfo *flatpak_appinfo = user_data;
char *uri;
GList *uris;
/* The app will be released in on_flatpak_launch_uris_finish */
g_application_hold (app);
uri = g_filename_to_uri (g_desktop_app_info_get_filename (flatpak_appinfo), NULL, NULL);
g_assert_nonnull (uri);
uris = g_list_prepend (NULL, uri);
ctx = g_object_new (test_app_launch_context_get_type (), NULL);
requested_startup_id = FALSE;
saw_startup_id = FALSE;
g_app_info_launch_uris_async (G_APP_INFO (flatpak_appinfo), uris, ctx,
NULL, on_flatpak_launch_uris_finish, app);
g_object_unref (ctx);
g_list_free (uris);
g_free (uri);
}
static void
on_flatpak_open (GApplication *app,
GFile **files,
gint n_files,
const char *hint)
{
GFile *f;
g_assert_cmpint (n_files, ==, 1);
g_test_message ("on_flatpak_open received file '%s'", g_file_peek_path (files[0]));
/* The file has been exported via the document portal */
f = g_file_new_for_uri ("file:///document-portal/document-id/org.gtk.test.dbusappinfo.flatpak.desktop");
g_assert_true (g_file_equal (files[0], f));
g_object_unref (f);
}
static void
test_flatpak_application_init (TestApplication *app)
{
}
static void
test_flatpak_application_class_init (GApplicationClass *class)
{
class->before_emit = test_application_before_emit;
}
static void
test_flatpak_doc_export (void)
{
const gchar *argv[] = { "myapp", NULL };
gchar *desktop_file = NULL;
GDesktopAppInfo *flatpak_appinfo;
GApplication *app;
int status;
GFakeDocumentPortalThread *thread = NULL;
g_test_summary ("Test that files launched via Flatpak apps are made available via the document portal.");
/* Run a fake-document-portal */
thread = g_fake_document_portal_thread_new (session_bus_get_address ());
g_fake_document_portal_thread_run (thread);
desktop_file = g_test_build_filename (G_TEST_DIST,
"org.gtk.test.dbusappinfo.flatpak.desktop",
NULL);
flatpak_appinfo = g_desktop_app_info_new_from_filename (desktop_file);
g_assert_nonnull (flatpak_appinfo);
g_free (desktop_file);
app = g_object_new (test_flatpak_application_get_type (),
"application-id", "org.gtk.test.dbusappinfo.flatpak",
"flags", G_APPLICATION_HANDLES_OPEN,
NULL);
g_signal_connect (app, "activate", G_CALLBACK (on_flatpak_activate),
flatpak_appinfo);
g_signal_connect (app, "open", G_CALLBACK (on_flatpak_open), NULL);
status = g_application_run (app, 1, (gchar **) argv);
g_assert_cmpint (status, ==, 0);
g_object_unref (app);
g_object_unref (flatpak_appinfo);
g_fake_document_portal_thread_stop (thread);
g_clear_object (&thread);
}
static void
on_flatpak_launch_invalid_uri_finish (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GApplication *app = user_data;
GError *error = NULL;
g_app_info_launch_uris_finish (G_APP_INFO (object), result, &error);
g_assert_no_error (error);
g_assert_true (requested_startup_id);
g_assert_true (saw_startup_id);
g_application_release (app);
}
static void
on_flatpak_activate_invalid_uri (GApplication *app,
gpointer user_data)
{
GAppLaunchContext *ctx;
GDesktopAppInfo *flatpak_appinfo = user_data;
GList *uris;
/* The app will be released in on_flatpak_launch_uris_finish */
g_application_hold (app);
uris = g_list_prepend (NULL, "file:///hopefully/an/invalid/path.desktop");
ctx = g_object_new (test_app_launch_context_get_type (), NULL);
requested_startup_id = FALSE;
saw_startup_id = FALSE;
g_app_info_launch_uris_async (G_APP_INFO (flatpak_appinfo), uris, ctx,
NULL, on_flatpak_launch_invalid_uri_finish, app);
g_object_unref (ctx);
g_list_free (uris);
}
static void
on_flatpak_open_invalid_uri (GApplication *app,
GFile **files,
gint n_files,
const char *hint)
{
GFile *f;
g_assert_cmpint (n_files, ==, 1);
g_test_message ("on_flatpak_open received file '%s'", g_file_peek_path (files[0]));
/* The file has been exported via the document portal */
f = g_file_new_for_uri ("file:///hopefully/an/invalid/path.desktop");
g_assert_true (g_file_equal (files[0], f));
g_object_unref (f);
}
static void
test_flatpak_missing_doc_export (void)
{
const gchar *argv[] = { "myapp", NULL };
gchar *desktop_file = NULL;
GDesktopAppInfo *flatpak_appinfo;
GApplication *app;
int status;
GFakeDocumentPortalThread *thread = NULL;
g_test_summary ("Test that files launched via Flatpak apps are made available via the document portal.");
/* Run a fake-document-portal */
thread = g_fake_document_portal_thread_new (session_bus_get_address ());
g_fake_document_portal_thread_run (thread);
desktop_file = g_test_build_filename (G_TEST_DIST,
"org.gtk.test.dbusappinfo.flatpak.desktop",
NULL);
flatpak_appinfo = g_desktop_app_info_new_from_filename (desktop_file);
g_assert_nonnull (flatpak_appinfo);
app = g_object_new (test_flatpak_application_get_type (),
"application-id", "org.gtk.test.dbusappinfo.flatpak",
"flags", G_APPLICATION_HANDLES_OPEN,
NULL);
g_signal_connect (app, "activate", G_CALLBACK (on_flatpak_activate_invalid_uri),
flatpak_appinfo);
g_signal_connect (app, "open", G_CALLBACK (on_flatpak_open_invalid_uri), NULL);
status = g_application_run (app, 1, (gchar **) argv);
g_assert_cmpint (status, ==, 0);
g_object_unref (app);
g_object_unref (flatpak_appinfo);
g_free (desktop_file);
g_fake_document_portal_thread_stop (thread);
g_clear_object (&thread);
}
static void
check_portal_openuri_call (const char *expected_uri,
GFakeDesktopPortalThread *thread)
{
const char *activation_token = NULL;
GFile *expected_file = NULL;
GFile *file = NULL;
const char *uri = NULL;
activation_token = g_fake_desktop_portal_thread_get_last_request_activation_token (thread);
uri = g_fake_desktop_portal_thread_get_last_request_uri (thread);
g_assert_cmpstr (activation_token, ==, "expected startup id");
g_assert_nonnull (uri);
expected_file = g_file_new_for_uri (expected_uri);
file = g_file_new_for_uri (uri);
g_assert_true (g_file_equal (expected_file, file));
g_object_unref (file);
g_object_unref (expected_file);
}
static void
test_portal_open_file (void)
{
GAppLaunchContext *ctx;
GError *error = NULL;
char *uri;
GFakeDesktopPortalThread *thread = NULL;
if (!g_fake_desktop_portal_is_supported ())
{
g_test_skip ("fake-desktop-portal not currently supported on this platform");
return;
}
/* Run a fake-desktop-portal */
thread = g_fake_desktop_portal_thread_new (session_bus_get_address ());
g_fake_desktop_portal_thread_run (thread);
uri = g_filename_to_uri (g_test_get_filename (G_TEST_DIST,
"org.gtk.test.dbusappinfo.flatpak.desktop",
NULL),
NULL,
NULL);
ctx = g_object_new (test_app_launch_context_get_type (), NULL);
requested_startup_id = FALSE;
g_app_info_launch_default_for_uri (uri, ctx, &error);
g_assert_no_error (error);
g_assert_true (requested_startup_id);
g_fake_desktop_portal_thread_stop (thread);
check_portal_openuri_call (uri, thread);
g_clear_object (&ctx);
g_clear_object (&thread);
g_clear_pointer (&uri, g_free);
}
static void
test_portal_open_uri (void)
{
GAppLaunchContext *ctx;
GError *error = NULL;
const char *uri = "http://example.com";
GFakeDesktopPortalThread *thread = NULL;
if (!g_fake_desktop_portal_is_supported ())
{
g_test_skip ("fake-desktop-portal not currently supported on this platform");
return;
}
/* Run a fake-desktop-portal */
thread = g_fake_desktop_portal_thread_new (session_bus_get_address ());
g_fake_desktop_portal_thread_run (thread);
ctx = g_object_new (test_app_launch_context_get_type (), NULL);
requested_startup_id = FALSE;
g_app_info_launch_default_for_uri (uri, ctx, &error);
g_assert_no_error (error);
g_assert_true (requested_startup_id);
g_fake_desktop_portal_thread_stop (thread);
check_portal_openuri_call (uri, thread);
g_clear_object (&ctx);
g_clear_object (&thread);
}
static void
on_launch_default_for_uri_finished (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
GError *error = NULL;
gboolean *called = user_data;
g_app_info_launch_default_for_uri_finish (result, &error);
g_assert_no_error (error);
*called = TRUE;
g_main_context_wakeup (NULL);
}
static void
test_portal_open_file_async (void)
{
GAppLaunchContext *ctx;
gboolean called = FALSE;
char *uri;
GFakeDesktopPortalThread *thread = NULL;
if (!g_fake_desktop_portal_is_supported ())
{
g_test_skip ("fake-desktop-portal not currently supported on this platform");
return;
}
/* Run a fake-desktop-portal */
thread = g_fake_desktop_portal_thread_new (session_bus_get_address ());
g_fake_desktop_portal_thread_run (thread);
uri = g_filename_to_uri (g_test_get_filename (G_TEST_DIST,
"org.gtk.test.dbusappinfo.flatpak.desktop",
NULL),
NULL,
NULL);
ctx = g_object_new (test_app_launch_context_get_type (), NULL);
requested_startup_id = FALSE;
g_app_info_launch_default_for_uri_async (uri, ctx,
NULL, on_launch_default_for_uri_finished, &called);
while (!called)
g_main_context_iteration (NULL, TRUE);
g_assert_true (requested_startup_id);
g_fake_desktop_portal_thread_stop (thread);
check_portal_openuri_call (uri, thread);
g_clear_pointer (&uri, g_free);
g_clear_object (&ctx);
g_clear_object (&thread);
}
static void
test_portal_open_uri_async (void)
{
GAppLaunchContext *ctx;
gboolean called = FALSE;
const char *uri = "http://example.com";
GFakeDesktopPortalThread *thread = NULL;
if (!g_fake_desktop_portal_is_supported ())
{
g_test_skip ("fake-desktop-portal not currently supported on this platform");
return;
}
/* Run a fake-desktop-portal */
thread = g_fake_desktop_portal_thread_new (session_bus_get_address ());
g_fake_desktop_portal_thread_run (thread);
ctx = g_object_new (test_app_launch_context_get_type (), NULL);
requested_startup_id = FALSE;
g_app_info_launch_default_for_uri_async (uri, ctx,
NULL, on_launch_default_for_uri_finished, &called);
while (!called)
g_main_context_iteration (NULL, TRUE);
g_assert_true (requested_startup_id);
g_fake_desktop_portal_thread_stop (thread);
check_portal_openuri_call (uri, thread);
g_clear_object (&ctx);
g_clear_object (&thread);
}
int
main (int argc, char **argv)
{
g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
g_setenv ("GIO_USE_PORTALS", "1", TRUE);
g_test_add_func ("/appinfo/dbusappinfo", test_dbus_appinfo);
g_test_add_func ("/appinfo/flatpak-doc-export", test_flatpak_doc_export);
g_test_add_func ("/appinfo/flatpak-missing-doc-export", test_flatpak_missing_doc_export);
g_test_add_func ("/appinfo/portal-open-file", test_portal_open_file);
g_test_add_func ("/appinfo/portal-open-uri", test_portal_open_uri);
g_test_add_func ("/appinfo/portal-open-file-async", test_portal_open_file_async);
g_test_add_func ("/appinfo/portal-open-uri-async", test_portal_open_uri_async);
return session_bus_run ();
}