diff options
author | Simon McVittie <smcv@collabora.com> | 2020-08-26 17:07:28 +0100 |
---|---|---|
committer | Alexander Larsson <alexander.larsson@gmail.com> | 2020-08-27 17:48:50 +0200 |
commit | c0faab35fabb469e3945ad99c32f041d2d7a0dab (patch) | |
tree | 8426a16257ee7a86e36ad8c52cee09fe717eb23e | |
parent | 55b27b1393a3880b79dfe108b6f13f1a2fa1888b (diff) | |
download | flatpak-c0faab35fabb469e3945ad99c32f041d2d7a0dab.tar.gz |
tests: Add basic unit tests for FlatpakExports, FlatpakContext
There's a limit to how many assertions we can make here right now,
because what we do here is very dependent on the "shape" of the host
filesystem. This could be extended in future by using a mock home
directory whose contents we control.
Signed-off-by: Simon McVittie <smcv@collabora.com>
-rw-r--r-- | tests/Makefile.am.inc | 6 | ||||
-rw-r--r-- | tests/test-exports.c | 481 |
2 files changed, 486 insertions, 1 deletions
diff --git a/tests/Makefile.am.inc b/tests/Makefile.am.inc index cefd9822..e43e8972 100644 --- a/tests/Makefile.am.inc +++ b/tests/Makefile.am.inc @@ -60,6 +60,10 @@ testcommon_LDADD = \ $(NULL) testcommon_SOURCES = tests/testcommon.c +test_exports_CFLAGS = $(testcommon_CFLAGS) +test_exports_LDADD = $(testcommon_LDADD) +test_exports_SOURCES = tests/test-exports.c + tests_httpcache_CFLAGS = $(AM_CFLAGS) $(BASE_CFLAGS) $(OSTREE_CFLAGS) $(SOUP_CFLAGS) $(JSON_CFLAGS) $(APPSTREAM_GLIB_CFLAGS) \ -DFLATPAK_COMPILATION \ -DLOCALEDIR=\"$(localedir)\" @@ -216,7 +220,7 @@ test_scripts = ${TEST_MATRIX} dist_test_scripts = ${TEST_MATRIX_DIST} dist_installed_test_extra_scripts += ${TEST_MATRIX_EXTRA_DIST} -test_programs = testlibrary testcommon +test_programs = testlibrary testcommon test-exports test_extra_programs = tests/httpcache tests/test-update-portal tests/test-portal-impl tests/test-authenticator @VALGRIND_CHECK_RULES@ diff --git a/tests/test-exports.c b/tests/test-exports.c new file mode 100644 index 00000000..21d8329e --- /dev/null +++ b/tests/test-exports.c @@ -0,0 +1,481 @@ +/* + * Copyright © 2020 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 "config.h" + +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <glib.h> +#include "flatpak.h" +#include "flatpak-bwrap-private.h" +#include "flatpak-context-private.h" +#include "flatpak-exports-private.h" +#include "flatpak-run-private.h" + +/* This differs from g_file_test (path, G_FILE_TEST_IS_DIR) which + returns true if the path is a symlink to a dir */ +static gboolean +path_is_dir (const char *path) +{ + struct stat s; + + if (lstat (path, &s) != 0) + return FALSE; + + return S_ISDIR (s.st_mode); +} + +/* + * Assert that the next few arguments starting from @i are setting up + * /run/host/os-release. Return the next argument that hasn't been used. + */ +G_GNUC_WARN_UNUSED_RESULT static gsize +assert_next_is_os_release (FlatpakBwrap *bwrap, + gsize i) +{ + if (g_file_test ("/etc/os-release", G_FILE_TEST_EXISTS)) + { + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "--ro-bind"); + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "/etc/os-release"); + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "/run/host/os-release"); + } + else if (g_file_test ("/usr/lib/os-release", G_FILE_TEST_EXISTS)) + { + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "--ro-bind"); + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "/usr/lib/os-release"); + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "/run/host/os-release"); + } + + return i; +} + +/* Assert that arguments starting from @i are --dir @dir. + * Return the new @i. */ +G_GNUC_WARN_UNUSED_RESULT static gsize +assert_next_is_dir (FlatpakBwrap *bwrap, + gsize i, + const char *dir) +{ + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "--dir"); + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, dir); + return i; +} + +/* Assert that arguments starting from @i are --tmpfs @dir. + * Return the new @i. */ +G_GNUC_WARN_UNUSED_RESULT static gsize +assert_next_is_tmpfs (FlatpakBwrap *bwrap, + gsize i, + const char *dir) +{ + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "--tmpfs"); + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, dir); + return i; +} + +/* Assert that arguments starting from @i are @how @path @path. + * Return the new @i. */ +G_GNUC_WARN_UNUSED_RESULT static gsize +assert_next_is_bind (FlatpakBwrap *bwrap, + gsize i, + const char *how, + const char *path) +{ + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, how); + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, path); + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, path); + return i; +} + +/* Print the arguments of a call to bwrap. */ +static void +print_bwrap (FlatpakBwrap *bwrap) +{ + guint i; + + for (i = 0; i < bwrap->argv->len && bwrap->argv->pdata[i] != NULL; i++) + g_test_message ("%s", (const char *) bwrap->argv->pdata[i]); + + g_test_message ("--"); +} + +static void +test_empty_context (void) +{ + g_autoptr(FlatpakBwrap) bwrap = flatpak_bwrap_new (NULL); + g_autoptr(FlatpakContext) context = flatpak_context_new (); + g_autoptr(FlatpakExports) exports = NULL; + + g_assert_cmpuint (g_hash_table_size (context->env_vars), ==, 0); + g_assert_cmpuint (g_hash_table_size (context->persistent), ==, 0); + g_assert_cmpuint (g_hash_table_size (context->filesystems), ==, 0); + g_assert_cmpuint (g_hash_table_size (context->session_bus_policy), ==, 0); + g_assert_cmpuint (g_hash_table_size (context->system_bus_policy), ==, 0); + g_assert_cmpuint (g_hash_table_size (context->generic_policy), ==, 0); + g_assert_cmpuint (context->shares, ==, 0); + g_assert_cmpuint (context->shares_valid, ==, 0); + g_assert_cmpuint (context->sockets, ==, 0); + g_assert_cmpuint (context->sockets_valid, ==, 0); + g_assert_cmpuint (context->devices, ==, 0); + g_assert_cmpuint (context->devices_valid, ==, 0); + g_assert_cmpuint (context->features, ==, 0); + g_assert_cmpuint (context->features_valid, ==, 0); + g_assert_cmpuint (flatpak_context_get_run_flags (context), ==, 0); + + exports = flatpak_context_get_exports (context, "com.example.App"); + g_assert_nonnull (exports); + + g_clear_pointer (&exports, flatpak_exports_free); + flatpak_context_append_bwrap_filesystem (context, bwrap, + "com.example.App", + NULL, + NULL, + &exports); + print_bwrap (bwrap); + g_assert_nonnull (exports); +} + +static void +test_full_context (void) +{ + g_autoptr(FlatpakBwrap) bwrap = flatpak_bwrap_new (NULL); + g_autoptr(FlatpakContext) context = flatpak_context_new (); + g_autoptr(FlatpakExports) exports = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GKeyFile) keyfile = g_key_file_new (); + + g_key_file_set_value (keyfile, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_SHARED, + "network;ipc;"); + g_key_file_set_value (keyfile, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_SOCKETS, + "x11;wayland;pulseaudio;session-bus;system-bus;" + "fallback-x11;ssh-auth;pcsc;cups;"); + g_key_file_set_value (keyfile, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_DEVICES, + "dri;all;kvm;shm;"); + g_key_file_set_value (keyfile, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_FEATURES, + "devel;multiarch;bluetooth;canbus;"); + g_key_file_set_value (keyfile, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_FILESYSTEMS, + "host;/home;"); + g_key_file_set_value (keyfile, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_PERSISTENT, + ".openarena;"); + g_key_file_set_value (keyfile, + FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, + "org.example.SessionService", + "own"); + g_key_file_set_value (keyfile, + FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, + "net.example.SystemService", + "talk"); + g_key_file_set_value (keyfile, + FLATPAK_METADATA_GROUP_ENVIRONMENT, + "HYPOTHETICAL_PATH", "/foo:/bar"); + g_key_file_set_value (keyfile, + FLATPAK_METADATA_GROUP_PREFIX_POLICY "MyPolicy", + "Colours", "blue;green;"); + + flatpak_context_load_metadata (context, keyfile, &error); + g_assert_no_error (error); + + g_assert_cmpuint (context->shares, ==, + (FLATPAK_CONTEXT_SHARED_NETWORK | + FLATPAK_CONTEXT_SHARED_IPC)); + g_assert_cmpuint (context->shares_valid, ==, context->shares); + g_assert_cmpuint (context->devices, ==, + (FLATPAK_CONTEXT_DEVICE_DRI | + FLATPAK_CONTEXT_DEVICE_ALL | + FLATPAK_CONTEXT_DEVICE_KVM | + FLATPAK_CONTEXT_DEVICE_SHM)); + g_assert_cmpuint (context->devices_valid, ==, context->devices); + g_assert_cmpuint (context->sockets, ==, + (FLATPAK_CONTEXT_SOCKET_X11 | + FLATPAK_CONTEXT_SOCKET_WAYLAND | + FLATPAK_CONTEXT_SOCKET_PULSEAUDIO | + FLATPAK_CONTEXT_SOCKET_SESSION_BUS | + FLATPAK_CONTEXT_SOCKET_SYSTEM_BUS | + FLATPAK_CONTEXT_SOCKET_FALLBACK_X11 | + FLATPAK_CONTEXT_SOCKET_SSH_AUTH | + FLATPAK_CONTEXT_SOCKET_PCSC | + FLATPAK_CONTEXT_SOCKET_CUPS)); + g_assert_cmpuint (context->sockets_valid, ==, context->sockets); + g_assert_cmpuint (context->features, ==, + (FLATPAK_CONTEXT_FEATURE_DEVEL | + FLATPAK_CONTEXT_FEATURE_MULTIARCH | + FLATPAK_CONTEXT_FEATURE_BLUETOOTH | + FLATPAK_CONTEXT_FEATURE_CANBUS)); + g_assert_cmpuint (context->features_valid, ==, context->features); + + g_assert_cmpuint (flatpak_context_get_run_flags (context), ==, + (FLATPAK_RUN_FLAG_DEVEL | + FLATPAK_RUN_FLAG_MULTIARCH | + FLATPAK_RUN_FLAG_BLUETOOTH | + FLATPAK_RUN_FLAG_CANBUS)); + + exports = flatpak_context_get_exports (context, "com.example.App"); + g_assert_nonnull (exports); + + g_clear_pointer (&exports, flatpak_exports_free); + flatpak_context_append_bwrap_filesystem (context, bwrap, + "com.example.App", + NULL, + NULL, + &exports); + print_bwrap (bwrap); + g_assert_nonnull (exports); +} + +typedef struct +{ + const char *input; + GOptionError code; +} NotFilesystem; + +static const NotFilesystem not_filesystems[] = +{ + { "homework", G_OPTION_ERROR_FAILED }, + { "xdg-run", G_OPTION_ERROR_FAILED }, +}; + +typedef struct +{ + const char *input; + FlatpakFilesystemMode mode; + const char *fs; +} Filesystem; + +static const Filesystem filesystems[] = +{ + { "home", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "host", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "host-etc", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "host-os", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "host:ro", FLATPAK_FILESYSTEM_MODE_READ_ONLY, "host" }, + { "home:rw", FLATPAK_FILESYSTEM_MODE_READ_WRITE, "home" }, + { "~/Music", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "/srv/obs/debian\\:sid\\:main:create", FLATPAK_FILESYSTEM_MODE_CREATE, + "/srv/obs/debian:sid:main" }, + { "/srv/c\\:\\\\Program Files\\\\Steam", FLATPAK_FILESYSTEM_MODE_READ_WRITE, + "/srv/c:\\Program Files\\Steam" }, + { "/srv/escaped\\unnecessarily", FLATPAK_FILESYSTEM_MODE_READ_WRITE, + "/srv/escapedunnecessarily" }, + { "xdg-desktop", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-desktop/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-documents", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-documents/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-download", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-download/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-music", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-music/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-pictures", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-pictures/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-public-share", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-public-share/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-templates", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-templates/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-videos", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-videos/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-data", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-data/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-cache", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-cache/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-config", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-config/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, + { "xdg-run/dbus", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, +}; + +static void +test_filesystems (void) +{ + gsize i; + + for (i = 0; i < G_N_ELEMENTS (filesystems); i++) + { + const Filesystem *fs = &filesystems[i]; + g_autoptr(GError) error = NULL; + g_autofree char *normalized; + FlatpakFilesystemMode mode; + gboolean ret; + + g_test_message ("%s", fs->input); + ret = flatpak_context_parse_filesystem (fs->input, &normalized, &mode, + &error); + g_assert_no_error (error); + g_assert_true (ret); + + if (fs->fs == NULL) + g_assert_cmpstr (normalized, ==, fs->input); + else + g_assert_cmpstr (normalized, ==, fs->fs); + + g_assert_cmpuint (mode, ==, fs->mode); + } + + for (i = 0; i < G_N_ELEMENTS (not_filesystems); i++) + { + const NotFilesystem *not = ¬_filesystems[i]; + g_autoptr(GError) error = NULL; + char *normalized = NULL; + FlatpakFilesystemMode mode; + gboolean ret; + + g_test_message ("%s", not->input); + ret = flatpak_context_parse_filesystem (not->input, &normalized, &mode, + &error); + g_test_message ("-> %s", error ? error->message : "(no error)"); + g_assert_error (error, G_OPTION_ERROR, not->code); + g_assert_false (ret); + g_assert_null (normalized); + } +} + +static void +test_empty (void) +{ + g_autoptr(FlatpakBwrap) bwrap = flatpak_bwrap_new (NULL); + g_autoptr(FlatpakExports) exports = flatpak_exports_new (); + gsize i; + + g_assert_false (flatpak_exports_path_is_visible (exports, "/run")); + g_assert_cmpint (flatpak_exports_path_get_mode (exports, "/tmp"), ==, + FLATPAK_FILESYSTEM_MODE_NONE); + + flatpak_bwrap_add_arg (bwrap, "bwrap"); + flatpak_exports_append_bwrap_args (exports, bwrap); + flatpak_bwrap_finish (bwrap); + print_bwrap (bwrap); + + i = 0; + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "bwrap"); + + i = assert_next_is_os_release (bwrap, i); + + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, NULL); + g_assert_cmpuint (i, ==, bwrap->argv->len); +} + +static void +test_full (void) +{ + g_autoptr(FlatpakBwrap) bwrap = flatpak_bwrap_new (NULL); + g_autoptr(FlatpakExports) exports = flatpak_exports_new (); + gsize i; + + flatpak_exports_add_host_etc_expose (exports, + FLATPAK_FILESYSTEM_MODE_READ_WRITE); + flatpak_exports_add_host_os_expose (exports, + FLATPAK_FILESYSTEM_MODE_READ_ONLY); + flatpak_exports_add_path_expose (exports, + FLATPAK_FILESYSTEM_MODE_READ_WRITE, + "/tmp"); + flatpak_exports_add_path_expose (exports, + FLATPAK_FILESYSTEM_MODE_READ_ONLY, + "/var"); + flatpak_exports_add_path_tmpfs (exports, "/var/tmp"); + flatpak_exports_add_path_expose_or_hide (exports, + FLATPAK_FILESYSTEM_MODE_NONE, + "/home"); + flatpak_exports_add_path_expose_or_hide (exports, + FLATPAK_FILESYSTEM_MODE_READ_ONLY, + "/srv"); + + flatpak_bwrap_add_arg (bwrap, "bwrap"); + flatpak_exports_append_bwrap_args (exports, bwrap); + flatpak_bwrap_finish (bwrap); + print_bwrap (bwrap); + + i = 0; + g_assert_cmpuint (i, <, bwrap->argv->len); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "bwrap"); + + /* Hiding /home just uses --dir because / is not exposed. */ + if (path_is_dir ("/home")) + i = assert_next_is_dir (bwrap, i, "/home"); + + if (path_is_dir ("/srv")) + i = assert_next_is_bind (bwrap, i, "--ro-bind", "/srv"); + + if (path_is_dir ("/tmp")) + i = assert_next_is_bind (bwrap, i, "--bind", "/tmp"); + + if (path_is_dir ("/var")) + i = assert_next_is_bind (bwrap, i, "--ro-bind", "/var"); + + /* We don't create a FAKE_MODE_TMPFS in the container unless there is + * a directory on the host to mount it on. + * Hiding /var/tmp has to use --tmpfs because /var *is* exposed. */ + if (path_is_dir ("/var") && path_is_dir ("/var/tmp")) + i = assert_next_is_tmpfs (bwrap, i, "/var/tmp"); + + while (i < bwrap->argv->len && bwrap->argv->pdata[i] != NULL) + { + /* An unknown number of --bind, --ro-bind and --symlink, + * depending how your /usr and /etc are set up. + * About the only thing we can say is that they are in threes. */ + g_assert_cmpuint (i++, <, bwrap->argv->len); + g_assert_cmpuint (i++, <, bwrap->argv->len); + g_assert_cmpuint (i++, <, bwrap->argv->len); + } + + g_assert_cmpuint (i, ==, bwrap->argv->len - 1); + g_assert_cmpstr (bwrap->argv->pdata[i++], ==, NULL); + g_assert_cmpuint (i, ==, bwrap->argv->len); +} + +int +main (int argc, char *argv[]) +{ + int res; + + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/context/empty", test_empty_context); + g_test_add_func ("/context/filesystems", test_filesystems); + g_test_add_func ("/context/full", test_full_context); + g_test_add_func ("/exports/empty", test_empty); + g_test_add_func ("/exports/full", test_full); + + res = g_test_run (); + + return res; +} |