diff options
author | Simon McVittie <smcv@collabora.com> | 2021-05-21 15:56:31 +0100 |
---|---|---|
committer | Alexander Larsson <alexander.larsson@gmail.com> | 2021-05-25 11:11:03 +0200 |
commit | 412c15772f794fd300991a91f2e0fe51f615bf79 (patch) | |
tree | 259bd54f4f05715fe6986665c3290b0b73e16df1 | |
parent | afd0cc4d184fe1b58d06587e865dc05fcfa38da7 (diff) | |
download | flatpak-412c15772f794fd300991a91f2e0fe51f615bf79.tar.gz |
portal: Add some test coverage
This exercises Spawn() and reproduces #4286.
Signed-off-by: Simon McVittie <smcv@collabora.com>
-rw-r--r-- | tests/Makefile.am.inc | 16 | ||||
-rw-r--r-- | tests/mock-flatpak.c | 87 | ||||
-rw-r--r-- | tests/test-portal.c | 483 | ||||
-rw-r--r-- | tests/testlib.c | 146 | ||||
-rw-r--r-- | tests/testlib.h | 13 |
5 files changed, 743 insertions, 2 deletions
diff --git a/tests/Makefile.am.inc b/tests/Makefile.am.inc index c9a73dd1..8adf2b3e 100644 --- a/tests/Makefile.am.inc +++ b/tests/Makefile.am.inc @@ -1,5 +1,6 @@ AM_TESTS_ENVIRONMENT = FLATPAK_TESTS_DEBUG=1 \ FLATPAK_CONFIG_DIR=/dev/null \ + FLATPAK_PORTAL=$$(cd $(top_builddir) && pwd)/flatpak-portal \ FLATPAK_TRIGGERSDIR=$$(cd $(top_srcdir) && pwd)/triggers \ FLATPAK_VALIDATE_ICON=$$(cd $(top_builddir) && pwd)/flatpak-validate-icon \ FLATPAK_REVOKEFS_FUSE=$$(cd $(top_builddir) && pwd)/revokefs-fuse \ @@ -65,6 +66,7 @@ testcommon_CFLAGS = \ $(JSON_CFLAGS) \ $(APPSTREAM_GLIB_CFLAGS) \ -DFLATPAK_COMPILATION \ + -DLIBEXECDIR=\"$(libexecdir)\" \ -I$(srcdir)/app \ -I$(builddir)/app \ $(NULL) @@ -98,6 +100,14 @@ test_instance_SOURCES = \ tests/test-instance.c \ $(NULL) +test_portal_CFLAGS = $(testcommon_CFLAGS) +test_portal_LDADD = $(testcommon_LDADD) libtestlib.la +test_portal_SOURCES = \ + portal/flatpak-portal-dbus.c \ + portal/flatpak-portal-dbus.h \ + tests/test-portal.c \ + $(NULL) + tests_hold_lock_CFLAGS = $(AM_CFLAGS) $(BASE_CFLAGS) tests_hold_lock_LDADD = $(AM_LDADD) $(BASE_LIBS) libglnx.la tests_hold_lock_SOURCES = tests/hold-lock.c @@ -108,6 +118,10 @@ tests_httpcache_CFLAGS = $(AM_CFLAGS) $(BASE_CFLAGS) $(OSTREE_CFLAGS) $(SOUP_CFL tests_httpcache_LDADD = $(AM_LDADD) $(BASE_LIBS) $(OSTREE_LIBS) $(SOUP_LIBS) $(JSON_LIBS) $(APPSTREAM_GLIB_LIBS) \ libflatpak-common.la libflatpak-common-base.la libglnx.la +tests_mock_flatpak_CFLAGS = $(testcommon_CFLAGS) +tests_mock_flatpak_LDADD = $(testcommon_LDADD) libtestlib.la +tests_mock_flatpak_SOURCES = tests/mock-flatpak.c + tests_test_update_portal_CFLAGS = $(AM_CFLAGS) $(BASE_CFLAGS) \ -DFLATPAK_COMPILATION \ -DLOCALEDIR=\"$(localedir)\" @@ -288,6 +302,7 @@ test_programs = \ test-context \ test-exports \ test-instance \ + test-portal \ testcommon \ testlibrary \ $(NULL) @@ -296,6 +311,7 @@ test_extra_programs = \ tests/hold-lock \ tests/httpcache \ tests/list-unused \ + tests/mock-flatpak \ tests/test-authenticator \ tests/test-portal-impl \ tests/test-update-portal \ diff --git a/tests/mock-flatpak.c b/tests/mock-flatpak.c new file mode 100644 index 00000000..03ea3d2b --- /dev/null +++ b/tests/mock-flatpak.c @@ -0,0 +1,87 @@ +/* + * Copyright © 2018-2021 Collabora Ltd. + * + * This program 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/>. + */ + +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "libglnx.h" + +#include <glib.h> +#include <gio/gio.h> + +#include "flatpak-context-private.h" + +int +main (int argc, + char **argv) +{ + int i; + + g_debug ("This is a mock implementation of `flatpak run` for the portal"); + + for (i = 0; i < argc; i++) + g_print ("argv[%d] = %s\n", i, argv[i]); + + for (i = 0; i < argc; i++) + { + if (g_str_has_prefix (argv[i], "--env-fd=")) + { + g_autoptr(FlatpakContext) context = flatpak_context_new (); + const char *value = argv[i] + strlen ("--env-fd="); + g_autoptr(GError) error = NULL; + guint64 fd; + gchar *endptr; + GHashTableIter iter; + gpointer k, v; + + fd = g_ascii_strtoull (value, &endptr, 10); + + if (endptr == NULL || *endptr != '\0' || fd > G_MAXINT) + g_error ("Not a valid file descriptor: %s", value); + + flatpak_context_parse_env_fd (context, (int) fd, &error); + g_assert_no_error (error); + + g_hash_table_iter_init (&iter, context->env_vars); + + while (g_hash_table_iter_next (&iter, &k, &v)) + g_print ("env[%s] = %s\n", (const char *) k, (const char *) v); + } + } + + for (i = 0; i < 256; i++) + { + struct stat stat_buf; + + if (fstat (i, &stat_buf) < 0) + { + int saved_errno = errno; + + g_assert_cmpint (saved_errno, ==, EBADF); + } + else + { + g_print ("fd[%d] = (dev=%" G_GUINT64_FORMAT " ino=%" G_GUINT64_FORMAT ")\n", + i, + (guint64) stat_buf.st_dev, + (guint64) stat_buf.st_ino); + } + } + + return 0; +} diff --git a/tests/test-portal.c b/tests/test-portal.c new file mode 100644 index 00000000..35789061 --- /dev/null +++ b/tests/test-portal.c @@ -0,0 +1,483 @@ +/* + * Copyright © 2018-2021 Collabora Ltd. + * + * This program 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/>. + */ + +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "libglnx.h" + +#include <glib.h> +#include <gio/gio.h> +#include <gio/gunixfdlist.h> + +#include "portal/flatpak-portal.h" +#include "portal/flatpak-portal-dbus.h" +#include "testlib.h" + +typedef struct +{ + TestsDBusDaemon dbus_daemon; + GSubprocess *portal; + gchar *portal_path; + gchar *mock_flatpak; + PortalFlatpak *proxy; + GDBusConnection *conn; +} Fixture; + +typedef struct +{ + int dummy; +} Config; + +static void +setup (Fixture *f, + gconstpointer context G_GNUC_UNUSED) +{ + g_autoptr(GError) error = NULL; + + tests_dbus_daemon_setup (&f->dbus_daemon); + + f->portal_path = g_strdup (g_getenv ("FLATPAK_PORTAL")); + + if (f->portal_path == NULL) + f->portal_path = g_strdup (LIBEXECDIR "/flatpak-portal"); + + f->mock_flatpak = g_test_build_filename (G_TEST_BUILT, "mock-flatpak", NULL); + + f->conn = g_dbus_connection_new_for_address_sync (f->dbus_daemon.dbus_address, + (G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | + G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION), + NULL, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (f->conn); +} + +/* Don't corrupt TAP output if portal outputs on stdout */ +static void +launcher_stdout_to_our_stderr (GSubprocessLauncher *launcher) +{ + glnx_autofd int stderr_copy = -1; + + stderr_copy = dup (STDERR_FILENO); + g_assert_no_errno (stderr_copy); + g_assert_no_errno (fcntl (stderr_copy, F_SETFD, FD_CLOEXEC)); + g_subprocess_launcher_take_stdout_fd (launcher, glnx_steal_fd (&stderr_copy)); +} + +static GSubprocessLauncher * +fixture_make_launcher (Fixture *f) +{ + g_autoptr(GSubprocessLauncher) launcher = NULL; + + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE); + launcher_stdout_to_our_stderr (launcher); + g_subprocess_launcher_setenv (launcher, + "DBUS_SESSION_BUS_ADDRESS", + f->dbus_daemon.dbus_address, + TRUE); + g_subprocess_launcher_setenv (launcher, + "FLATPAK_PORTAL_MOCK_FLATPAK", + f->mock_flatpak, + TRUE); + + return g_steal_pointer (&launcher); +} + +static void +name_appeared_cb (GDBusConnection *conn, + const char *name, + const char *owner, + gpointer user_data) +{ + gchar **name_owner_p = user_data; + + g_clear_pointer (name_owner_p, g_free); + *name_owner_p = g_strdup (owner); +} + +static void +name_vanished_cb (GDBusConnection *conn, + const char *name, + gpointer user_data) +{ + gchar **name_owner_p = user_data; + + g_clear_pointer (name_owner_p, g_free); + *name_owner_p = g_strdup (""); +} + +static void +name_owner_watch_removed_cb (gpointer user_data) +{ + gchar **name_owner_p = user_data; + + g_clear_pointer (name_owner_p, g_free); +} + +static void +fixture_wait_for_name_to_be_owned (Fixture *f, + const char *name) +{ + g_autofree gchar *name_owner = NULL; + guint watch; + + watch = g_bus_watch_name_on_connection (f->conn, + name, + G_BUS_NAME_WATCHER_FLAGS_NONE, + name_appeared_cb, + name_vanished_cb, + &name_owner, + name_owner_watch_removed_cb); + + /* Wait for name to become owned */ + while (name_owner == NULL || name_owner[0] == '\0') + g_main_context_iteration (NULL, TRUE); + + g_bus_unwatch_name (watch); + + /* Wait for watch to be cleaned up */ + while (name_owner != NULL) + g_main_context_iteration (NULL, TRUE); +} + +static void +fixture_start_portal (Fixture *f) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GSubprocessLauncher) launcher = NULL; + + launcher = fixture_make_launcher (f); + f->portal = g_subprocess_launcher_spawn (launcher, &error, + f->portal_path, + NULL); + g_assert_no_error (error); + g_assert_nonnull (f->portal); + + fixture_wait_for_name_to_be_owned (f, FLATPAK_PORTAL_BUS_NAME); + + f->proxy = portal_flatpak_proxy_new_sync (f->conn, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + FLATPAK_PORTAL_BUS_NAME, + FLATPAK_PORTAL_PATH, + NULL, + &error); + g_assert_no_error (error); + g_assert_nonnull (f->proxy); +} + +static void +test_help (Fixture *f, + gconstpointer context G_GNUC_UNUSED) +{ + g_autoptr(GSubprocessLauncher) launcher = NULL; + g_autofree gchar *stdout_buf; + g_autofree gchar *stderr_buf; + g_autoptr(GError) error = NULL; + + /* Don't use fixture_make_launcher() here because we want to + * capture stdout */ + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE | + G_SUBPROCESS_FLAGS_STDERR_PIPE); + g_subprocess_launcher_setenv (launcher, + "DBUS_SESSION_BUS_ADDRESS", + f->dbus_daemon.dbus_address, + TRUE); + + f->portal = g_subprocess_launcher_spawn (launcher, &error, + f->portal_path, + "--help", + NULL); + g_assert_no_error (error); + g_assert_nonnull (f->portal); + + g_subprocess_communicate_utf8 (f->portal, NULL, NULL, &stdout_buf, + &stderr_buf, &error); + g_assert_nonnull (stdout_buf); + g_assert_nonnull (stderr_buf); + g_test_message ("flatpak-portal --help: %s", stdout_buf); + g_assert_true (strstr (stdout_buf, "--replace") != NULL); + + g_subprocess_wait_check (f->portal, NULL, &error); + g_assert_no_error (error); +} + +static void +count_successful_exit_cb (PortalFlatpak *proxy, + guint pid, + guint wait_status, + gpointer user_data) +{ + gsize *times_exited_p = user_data; + + g_debug ("Process %u exited with wait status %u", pid, wait_status); + g_assert_true (WIFEXITED (wait_status)); + g_assert_cmpuint (WEXITSTATUS (wait_status), ==, 0); + (*times_exited_p) += 1; +} + +static void +test_basic (Fixture *f, + gconstpointer context G_GNUC_UNUSED) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GUnixFDList) fds_out = NULL; + guint pid; + gboolean ok; + const char * const argv[] = { "hello", NULL }; + gsize times_exited = 0; + gulong handler_id; + + fixture_start_portal (f); + + /* We can't easily tell whether EXPOSE_PIDS ought to be set or not */ + g_assert_cmpuint ((portal_flatpak_get_supports (f->proxy) & + (~FLATPAK_SPAWN_SUPPORT_FLAGS_EXPOSE_PIDS)), ==, 0); + g_assert_cmpuint (portal_flatpak_get_version (f->proxy), ==, 6); + + handler_id = g_signal_connect (f->proxy, "spawn-exited", + G_CALLBACK (count_successful_exit_cb), + ×_exited); + + ok = portal_flatpak_call_spawn_sync (f->proxy, + "/", /* cwd */ + argv, /* argv */ + g_variant_new ("a{uh}", NULL), + g_variant_new ("a{ss}", NULL), + FLATPAK_SPAWN_FLAGS_NONE, + g_variant_new ("a{sv}", NULL), + NULL, /* fd list */ + &pid, + &fds_out, + NULL, + &error); + g_assert_no_error (error); + g_assert_true (ok); + g_assert_cmpuint (pid, >, 1); + + while (times_exited == 0) + g_main_context_iteration (NULL, TRUE); + + g_signal_handler_disconnect (f->proxy, handler_id); + + if (fds_out != NULL) + g_assert_cmpint (g_unix_fd_list_get_length (fds_out), ==, 0); + + g_subprocess_send_signal (f->portal, SIGTERM); + g_subprocess_wait (f->portal, NULL, &error); + g_assert_no_error (error); +} + +static void +test_fd_passing (Fixture *f, + gconstpointer context G_GNUC_UNUSED) +{ +#define SOME_FDS 16 + g_autoptr(GError) error = NULL; + char *tempfile_paths[SOME_FDS]; + int tempfile_fds[SOME_FDS]; + guint gap_size; + gsize times_exited = 0; + gulong handler_id; + gsize i; + + fixture_start_portal (f); + + handler_id = g_signal_connect (f->proxy, "spawn-exited", + G_CALLBACK (count_successful_exit_cb), + ×_exited); + + for (i = 0; i < SOME_FDS; i++) + { + tempfile_paths[i] = g_strdup ("/tmp/flatpak-portal-test.XXXXXX"); + tempfile_fds[i] = g_mkstemp (tempfile_paths[i]); + g_assert_no_errno (tempfile_fds[i]); + } + + /* Using a non-contiguous block of fds can help to tickle bugs in the + * portal. */ + for (gap_size = 0; gap_size < 128; gap_size += 16) + { + g_autoptr(GUnixFDList) fds_in = g_unix_fd_list_new (); + g_autoptr(GUnixFDList) fds_out = NULL; + g_auto(GVariantBuilder) fd_map_builder = {}; + g_auto(GVariantBuilder) env_builder = {}; + guint pid; + gboolean ok; + const char * const argv[] = { "hello", NULL }; + g_autofree char *output = NULL; + + g_variant_builder_init (&fd_map_builder, G_VARIANT_TYPE ("a{uh}")); + g_variant_builder_init (&env_builder, G_VARIANT_TYPE ("a{ss}")); + times_exited = 0; + + g_variant_builder_add (&env_builder, "{ss}", "FOO", "bar"); + + for (i = 0; i < SOME_FDS; i++) + { + int handle = g_unix_fd_list_append (fds_in, tempfile_fds[i], &error); + guint32 desired_fd; + + g_assert_no_error (error); + g_assert_cmpint (handle, >=, 0); + + if (i <= STDERR_FILENO) + desired_fd = i; + else + desired_fd = i + gap_size; + + g_variant_builder_add (&fd_map_builder, "{uh}", + desired_fd, (gint32) handle); + } + + ok = portal_flatpak_call_spawn_sync (f->proxy, + "/", /* cwd */ + argv, /* argv */ + g_variant_builder_end (&fd_map_builder), + g_variant_builder_end (&env_builder), + FLATPAK_SPAWN_FLAGS_NONE, + g_variant_new ("a{sv}", NULL), + fds_in, + &pid, + &fds_out, + NULL, + &error); + g_assert_no_error (error); + g_assert_true (ok); + g_assert_cmpuint (pid, >, 1); + + /* Wait for this one to exit */ + while (times_exited == 0) + g_main_context_iteration (NULL, TRUE); + + if (fds_out != NULL) + g_assert_cmpint (g_unix_fd_list_get_length (fds_out), ==, 0); + + /* stdout from the portal should have ended up in temp file [1] */ + g_assert_no_errno (lseek (tempfile_fds[1], 0, SEEK_SET)); + output = glnx_fd_readall_utf8 (tempfile_fds[1], NULL, NULL, &error); + g_assert_no_error (error); + g_assert_nonnull (output); + g_assert_no_errno (lseek (tempfile_fds[1], 0, SEEK_SET)); + g_assert_no_errno (ftruncate (tempfile_fds[1], 0)); + g_test_message ("Output from mock Flatpak: %s", output); + + if (strstr (output, "env[FOO] = bar") != NULL) + g_test_message ("Found env[FOO] = bar in output"); + else + g_error ("env[FOO] = bar not found in \"%s\"", output); + + for (i = 0; i < SOME_FDS; i++) + { + struct stat stat_buf; + g_autofree char *expected = NULL; + int desired_fd; + + g_assert_no_errno (fstat (tempfile_fds[i], &stat_buf)); + + if (i <= STDERR_FILENO) + desired_fd = i; + else + desired_fd = i + gap_size; + + expected = g_strdup_printf ("fd[%d] = (dev=%" G_GUINT64_FORMAT " ino=%" G_GUINT64_FORMAT ")", + desired_fd, + (guint64) stat_buf.st_dev, + (guint64) stat_buf.st_ino); + + if (strstr (output, expected) != NULL) + g_test_message ("fd %d OK: \"%s\"", desired_fd, expected); + else + g_error ("\"%s\" not found in \"%s\"", expected, output); + } + } + + g_signal_handler_disconnect (f->proxy, handler_id); + + g_subprocess_send_signal (f->portal, SIGTERM); + g_subprocess_wait (f->portal, NULL, &error); + g_assert_no_error (error); + + for (i = 0; i < SOME_FDS; i++) + { + g_assert_no_errno (unlink (tempfile_paths[i])); + glnx_close_fd (&tempfile_fds[i]); + g_free (tempfile_paths[i]); + } +} + +static void +test_replace (Fixture *f, + gconstpointer context G_GNUC_UNUSED) +{ + g_autoptr(GSubprocessLauncher) launcher = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GSubprocess) gets_replaced = NULL; + + /* Not using fixture_start_portal() here because we want to --replace */ + launcher = fixture_make_launcher (f); + gets_replaced = g_subprocess_launcher_spawn (launcher, &error, + f->portal_path, + "--replace", + NULL); + g_assert_no_error (error); + g_assert_nonnull (gets_replaced); + + fixture_wait_for_name_to_be_owned (f, FLATPAK_PORTAL_BUS_NAME); + + g_clear_object (&launcher); + launcher = fixture_make_launcher (f); + f->portal = g_subprocess_launcher_spawn (launcher, &error, + f->portal_path, + "--replace", + NULL); + g_assert_no_error (error); + g_assert_nonnull (f->portal); + + /* f->portal replaces gets_replaced, which exits 0 */ + g_subprocess_wait_check (gets_replaced, NULL, &error); + g_assert_no_error (error); + + g_subprocess_send_signal (f->portal, SIGTERM); + g_subprocess_wait (f->portal, NULL, &error); + g_assert_no_error (error); +} + +static void +teardown (Fixture *f, + gconstpointer context G_GNUC_UNUSED) +{ + tests_dbus_daemon_teardown (&f->dbus_daemon); + g_clear_object (&f->portal); + g_free (f->portal_path); +} + +int +main (int argc, + char **argv) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add ("/help", Fixture, NULL, setup, test_help, teardown); + g_test_add ("/basic", Fixture, NULL, setup, test_basic, teardown); + g_test_add ("/fd-passing", Fixture, NULL, setup, test_fd_passing, teardown); + g_test_add ("/replace", Fixture, NULL, setup, test_replace, teardown); + + return g_test_run (); +} diff --git a/tests/testlib.c b/tests/testlib.c index c8f85446..28c97a4d 100644 --- a/tests/testlib.c +++ b/tests/testlib.c @@ -1,5 +1,5 @@ /* - * Copyright © 2020-2021 Collabora Ltd. + * Copyright © 2018-2021 Collabora Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -95,3 +95,147 @@ isolated_test_dir_global_teardown (void) g_free (isolated_test_dir); isolated_test_dir = NULL; } + +static void +replace_tokens (const char *in_path, + const char *out_path) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GString) buffer = NULL; + g_autofree char *contents = NULL; + const char *iter; + + g_file_get_contents (in_path, &contents, NULL, &error); + g_assert_no_error (error); + + buffer = g_string_new (""); + iter = contents; + + while (iter[0] != '\0') + { + const char *first_at = strchr (iter, '@'); + const char *second_at; + + if (first_at == NULL) + { + /* no more @token@s, append [iter..end] and stop */ + g_string_append (buffer, iter); + break; + } + + second_at = strchr (first_at + 1, '@'); + + if (second_at == NULL) + g_error ("Unterminated @token@ in %s: %s", in_path, first_at); + + /* append the literal text [iter..first_at - 1], if non-empty */ + if (first_at != iter) + g_string_append_len (buffer, iter, first_at - iter); + + /* append the replacement for [first_at..second_at] if known */ + if (g_str_has_prefix (first_at, "@testdir@")) + { + g_autofree char *testdir = g_test_build_filename (G_TEST_DIST, ".", NULL); + + g_string_append (buffer, testdir); + } + else + { + g_error ("Unknown @token@ in %s: %.*s", + in_path, (int) (second_at - first_at) + 1, first_at); + } + + /* continue to process [second_at + 1..end] */ + iter = second_at + 1; + } + + g_file_set_contents (out_path, buffer->str, -1, &error); + g_assert_no_error (error); +} + +void +tests_dbus_daemon_setup (TestsDBusDaemon *self) +{ + g_autoptr(GSubprocessLauncher) launcher = NULL; + g_autoptr(GError) error = NULL; + g_autofree char *config_arg = NULL; + g_autofree char *session_conf = NULL; + g_autofree char *session_conf_in = NULL; + GInputStream *address_pipe; + gchar address_buffer[4096] = { 0 }; + char *newline; + + g_return_if_fail (self != NULL); + g_return_if_fail (self->dbus_daemon == NULL); + g_return_if_fail (self->dbus_address == NULL); + g_return_if_fail (self->temp_dir == NULL); + + self->temp_dir = g_dir_make_tmp ("flatpak-test.XXXXXX", &error); + g_assert_no_error (error); + g_assert_nonnull (self->temp_dir); + + session_conf_in = g_test_build_filename (G_TEST_DIST, "session.conf.in", NULL); + session_conf = g_build_filename (self->temp_dir, "test-bus.conf", NULL); + replace_tokens (session_conf_in, session_conf); + config_arg = g_strdup_printf ("--config-file=%s", session_conf); + launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE); + self->dbus_daemon = g_subprocess_launcher_spawn (launcher, &error, + "dbus-daemon", + config_arg, + "--print-address=1", + "--nofork", + NULL); + g_assert_no_error (error); + g_assert_nonnull (self->dbus_daemon); + + address_pipe = g_subprocess_get_stdout_pipe (self->dbus_daemon); + g_assert_nonnull (address_pipe); + + /* Crash if it takes too long to get the address */ + alarm (30); + + while (strchr (address_buffer, '\n') == NULL) + { + if (strlen (address_buffer) >= sizeof (address_buffer) - 1) + g_error ("Read %" G_GSIZE_FORMAT " bytes from dbus-daemon with " + "no newline", + sizeof (address_buffer) - 1); + + g_input_stream_read (address_pipe, + address_buffer + strlen (address_buffer), + sizeof (address_buffer) - strlen (address_buffer), + NULL, &error); + g_assert_no_error (error); + } + + /* Disable alarm */ + alarm (0); + + newline = strchr (address_buffer, '\n'); + g_assert_nonnull (newline); + *newline = '\0'; + self->dbus_address = g_strdup (address_buffer); +} + +void +tests_dbus_daemon_teardown (TestsDBusDaemon *self) +{ + g_autoptr(GError) error = NULL; + + if (self->dbus_daemon != NULL) + { + g_subprocess_send_signal (self->dbus_daemon, SIGTERM); + g_subprocess_wait (self->dbus_daemon, NULL, &error); + g_assert_no_error (error); + } + + if (self->temp_dir != NULL) + { + glnx_shutil_rm_rf_at (AT_FDCWD, self->temp_dir, NULL, &error); + g_assert_no_error (error); + } + + g_clear_object (&self->dbus_daemon); + g_clear_pointer (&self->dbus_address, g_free); + g_clear_pointer (&self->temp_dir, g_free); +} diff --git a/tests/testlib.h b/tests/testlib.h index 80e73994..213939a2 100644 --- a/tests/testlib.h +++ b/tests/testlib.h @@ -1,5 +1,5 @@ /* - * Copyright © 2020-2021 Collabora Ltd. + * Copyright © 2018-2021 Collabora Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,6 +19,7 @@ #define TESTLIB_H #include <glib.h> +#include <gio/gio.h> #ifndef g_assert_no_errno #define g_assert_no_errno(expr) \ @@ -31,4 +32,14 @@ extern char *isolated_test_dir; void isolated_test_dir_global_setup (void); void isolated_test_dir_global_teardown (void); +typedef struct +{ + GSubprocess *dbus_daemon; + gchar *dbus_address; + gchar *temp_dir; +} TestsDBusDaemon; + +void tests_dbus_daemon_setup (TestsDBusDaemon *self); +void tests_dbus_daemon_teardown (TestsDBusDaemon *self); + #endif |