/*
 * Copyright © 2019 Collabora Ltd.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <libglnx.h>

#include <steam-runtime-tools/steam-runtime-tools.h>
#include "steam-runtime-tools/utils-internal.h"

#include <steam-runtime-tools/glib-backports-internal.h>

#include <glib.h>
#include <glib/gstdio.h>
#include <gio/gio.h>

#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <ftw.h>

#include "graphics-test-defines.h"
#include "test-utils.h"
#include "fake-home.h"

#if defined(__i386__) || defined(__x86_64__)
  static const char * const multiarch_tuples[] = { SRT_ABI_I386, SRT_ABI_X86_64 };
#elif defined(_SRT_MULTIARCH)
  static const char * const multiarch_tuples[] = { _SRT_MULTIARCH };
#else
#warning Unknown architecture, assuming x86
  static const char * const multiarch_tuples[] = { SRT_ABI_I386, SRT_ABI_X86_64 };
#endif

static const char *argv0;
static gchar *fake_home_path;
static gchar *global_sysroots;

typedef struct
{
  gchar *srcdir;
  gchar *builddir;
  const gchar *sysroots;
} Fixture;

typedef struct
{
  int unused;
} Config;

static void
setup (Fixture *f,
       gconstpointer context)
{
  G_GNUC_UNUSED const Config *config = context;

  f->srcdir = g_strdup (g_getenv ("G_TEST_SRCDIR"));
  f->builddir = g_strdup (g_getenv ("G_TEST_BUILDDIR"));

  if (f->srcdir == NULL)
    f->srcdir = g_path_get_dirname (argv0);

  if (f->builddir == NULL)
    f->builddir = g_path_get_dirname (argv0);

  f->sysroots = global_sysroots;
}

static void
teardown (Fixture *f,
          gconstpointer context)
{
  G_GNUC_UNUSED const Config *config = context;

  g_free (f->srcdir);
  g_free (f->builddir);

  /* We expect that fake_home already cleaned this up, but just to be sure we
   * do it too */
  _srt_rm_rf (fake_home_path);
}

/*
 * Test basic functionality of the SrtSystemInfo object.
 */
static void
test_object (Fixture *f,
             gconstpointer context)
{
  SrtSystemInfo *info;
  gchar *expectations_in = NULL;
  gchar *expectations = NULL;
  int fd;

  info = srt_system_info_new (NULL);
  g_assert_nonnull (info);
  g_object_get (info,
                "expectations", &expectations,
                NULL);
  g_assert_cmpstr (expectations, ==, NULL);

  /* We try it twice, to exercise the cached and non-cached cases */
#if defined(__x86_64__) && defined(__LP64__)
  g_assert_true (srt_system_info_can_run (info, SRT_ABI_X86_64));
  g_assert_true (srt_system_info_can_run (info, SRT_ABI_X86_64));
#endif

#if defined(__i386__)
  g_assert_true (srt_system_info_can_run (info, SRT_ABI_I386));
  g_assert_true (srt_system_info_can_run (info, SRT_ABI_I386));
#endif

  g_assert_false (srt_system_info_can_run (info, "hal9000-linux-gnu"));
  g_assert_false (srt_system_info_can_run (info, "hal9000-linux-gnu"));

  /* This is a little bit tautologous - we're using the same check
   * that the production code does. */
  fd = open ("/dev/uinput", O_WRONLY | O_NONBLOCK);

  if (fd >= 0)
    {
      g_assert_true (srt_system_info_can_write_to_uinput (info));
      g_assert_true (srt_system_info_can_write_to_uinput (info));
      close (fd);
    }
  else
    {
      g_assert_false (srt_system_info_can_write_to_uinput (info));
      g_assert_false (srt_system_info_can_write_to_uinput (info));
    }

  g_object_unref (info);

  expectations_in = g_build_filename (f->srcdir, "expectations", NULL);
  info = srt_system_info_new (expectations_in);
  g_assert_nonnull (info);
  g_object_get (info,
                "expectations", &expectations,
                NULL);
  g_assert_cmpstr (expectations, ==, expectations_in);
  g_free (expectations_in);
  g_free (expectations);
  g_object_unref (info);

  info = srt_system_info_new (NULL);
  g_assert_nonnull (info);
  srt_system_info_set_helpers_path (info, f->builddir);
  g_assert_true (srt_system_info_can_run (info, "mock"));
  /* The real helpers are not present here */
  g_assert_false (srt_system_info_can_run (info, SRT_ABI_I386));
  g_assert_false (srt_system_info_can_run (info, SRT_ABI_X86_64));
  g_object_unref (info);
}

static void
test_libdl (Fixture *f,
            gconstpointer context)
{
  g_autoptr(SrtSystemInfo) info = NULL;
  g_autofree gchar *libdl = NULL;
  g_autoptr(GError) error = NULL;

  info = srt_system_info_new (NULL);
  g_assert_nonnull (info);
  srt_system_info_set_helpers_path (info, f->builddir);
  libdl = srt_system_info_dup_libdl_lib (info, "mock-good", &error);
  g_assert_cmpstr (libdl, ==, "lib");
  g_assert_no_error (error);
  g_free (libdl);
  /* Test cache */
  libdl = srt_system_info_dup_libdl_lib (info, "mock-good", &error);
  g_assert_cmpstr (libdl, ==, "lib");
  g_assert_no_error (error);
  g_free (libdl);

  libdl = srt_system_info_dup_libdl_platform (info, "mock-good", &error);
  g_assert_cmpstr (libdl, ==, "x86_64");
  g_assert_no_error (error);
  g_free (libdl);
  /* Test cache */
  libdl = srt_system_info_dup_libdl_platform (info, "mock-good", &error);
  g_assert_cmpstr (libdl, ==, "x86_64");
  g_assert_no_error (error);
  g_free (libdl);

  libdl = srt_system_info_dup_libdl_lib (info, "mock-bad", &error);
  g_assert_cmpstr (libdl, ==, NULL);
  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
  g_assert_cmpstr (error->message, ==, "Unable to find the library: "
                                       "${ORIGIN}/i386-linux-gnu/${PLATFORM}/libidentify-lib.so: "
                                       "cannot open shared object file: No such file or directory\n");

  g_clear_error (&error);
  libdl = srt_system_info_dup_libdl_platform (info, "mock-bad", &error);
  g_assert_cmpstr (libdl, ==, NULL);
  g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
  g_assert_cmpstr (error->message, ==, "Unable to find the library: "
                                       "${ORIGIN}/i386-linux-gnu/${PLATFORM}/libidentify-platform.so: "
                                       "cannot open shared object file: No such file or directory\n");
}

static void
check_libraries_result (GList *libraries)
{
  SrtLibrary *library = NULL;
  const char * const *missing_symbols;
  const char * const *misversioned_symbols;
  const char * const *dependencies;
  gboolean seen_libc;
  GList *l;

  g_assert_nonnull (libraries);
  l = libraries;

  /* Test first library. Alphabetical order is an API guarantee, so we know
   * which one it should be. */
  library = l->data;
  g_assert_nonnull (library);
  g_assert_cmpstr (srt_library_get_requested_name (library), ==, "libgio-2.0.so.0");
  missing_symbols = srt_library_get_missing_symbols (library);
  g_assert_nonnull (missing_symbols);
  g_assert_cmpstr (missing_symbols[0], ==, NULL);

  g_assert_cmpint (srt_library_get_issues (library), ==, SRT_LIBRARY_ISSUES_NONE);

  misversioned_symbols = srt_library_get_misversioned_symbols (library);
  g_assert_nonnull (misversioned_symbols);
  g_assert_cmpstr (misversioned_symbols[0], ==, NULL);

  dependencies = srt_library_get_dependencies (library);
  g_assert_nonnull (dependencies);
  g_assert_cmpstr (dependencies[0], !=, NULL);
  seen_libc = FALSE;

  for (gsize i = 0; dependencies[i] != NULL; i++)
    {
      g_debug ("libgio-2.0.so.0 depends on %s", dependencies[i]);

      if (strstr (dependencies[i], "/libc.so.") != NULL)
        seen_libc = TRUE;
    }

  g_assert_true (seen_libc);

  /* Test second library */
  l = g_list_next (l);
  library = l->data;
  g_assert_nonnull (library);
  g_assert_cmpstr (srt_library_get_requested_name (library), ==, "libglib-2.0.so.0");
  missing_symbols = srt_library_get_missing_symbols (library);
  g_assert_nonnull (missing_symbols);
  g_assert_cmpstr (missing_symbols[0], ==, NULL);

  g_assert_cmpint (srt_library_get_issues (library), ==, SRT_LIBRARY_ISSUES_NONE);

  misversioned_symbols = srt_library_get_misversioned_symbols (library);
  g_assert_nonnull (misversioned_symbols);
  g_assert_cmpstr (misversioned_symbols[0], ==, NULL);

  dependencies = srt_library_get_dependencies (library);
  g_assert_nonnull (dependencies);
  g_assert_cmpstr (dependencies[0], !=, NULL);
  seen_libc = FALSE;

  for (gsize i = 0; dependencies[i] != NULL; i++)
    {
      g_debug ("libglib-2.0.so.0 depends on %s", dependencies[i]);

      if (strstr (dependencies[i], "/libc.so.") != NULL)
        seen_libc = TRUE;
    }

  g_assert_true (seen_libc);

  /* Test third library */
  l = g_list_next (l);
  library = l->data;
  g_assert_nonnull (library);
  g_assert_cmpstr (srt_library_get_requested_name (library), ==, "libtheoraenc.so.1");
  missing_symbols = srt_library_get_missing_symbols (library);
  g_assert_nonnull (missing_symbols);
  g_assert_cmpstr (missing_symbols[0], ==, NULL);

  g_assert_cmpint (srt_library_get_issues (library), ==, SRT_LIBRARY_ISSUES_NONE);

  misversioned_symbols = srt_library_get_misversioned_symbols (library);
  g_assert_nonnull (misversioned_symbols);
  g_assert_cmpstr (misversioned_symbols[0], ==, NULL);

  dependencies = srt_library_get_dependencies (library);
  g_assert_nonnull (dependencies);
  g_assert_cmpstr (dependencies[0], !=, NULL);
  seen_libc = FALSE;

  for (gsize i = 0; dependencies[i] != NULL; i++)
    {
      g_debug ("libtheoraenc.so.1 depends on %s", dependencies[i]);

      if (strstr (dependencies[i], "/libc.so.") != NULL)
        seen_libc = TRUE;
    }

  g_assert_true (seen_libc);

  /* Test last library */
  l = g_list_next (l);
  library = l->data;
  g_assert_nonnull (library);
  g_assert_cmpstr (srt_library_get_requested_name (library), ==, "libz.so.1");
  missing_symbols = srt_library_get_missing_symbols (library);
  g_assert_nonnull (missing_symbols);
  g_assert_cmpstr (missing_symbols[0], ==, NULL);

  g_assert_cmpint (srt_library_get_issues (library), ==, SRT_LIBRARY_ISSUES_NONE);

  misversioned_symbols = srt_library_get_misversioned_symbols (library);
  g_assert_nonnull (misversioned_symbols);
  g_assert_cmpstr (misversioned_symbols[0], ==, NULL);

  dependencies = srt_library_get_dependencies (library);
  g_assert_nonnull (dependencies);
  g_assert_cmpstr (dependencies[0], !=, NULL);
  seen_libc = FALSE;

  for (gsize i = 0; dependencies[i] != NULL; i++)
    {
      g_debug ("libz.so.1 depends on %s", dependencies[i]);

      if (strstr (dependencies[i], "/libc.so.") != NULL)
        seen_libc = TRUE;
    }

  g_assert_true (seen_libc);
  g_assert_null (g_list_next (l));
}

/*
 * Test if the expected libraries are available in the
 * running system.
 */
static void
libraries_presence (Fixture *f,
                    gconstpointer context)
{
  SrtSystemInfo *info;
  gchar *expectations_in = NULL;
  GList *libraries = NULL;
  SrtLibraryIssues issues;
  const char *multiarch_tuple = NULL;

#ifndef _SRT_MULTIARCH
  g_test_skip ("Unsupported architecture");
  return;
#else
  multiarch_tuple = _SRT_MULTIARCH;
#endif

  expectations_in = g_build_filename (f->srcdir, "expectations", NULL);
  info = srt_system_info_new (expectations_in);
  issues = srt_system_info_check_libraries (info,
                                            multiarch_tuple,
                                            &libraries);
  g_assert_cmpint (issues, ==, SRT_LIBRARY_ISSUES_NONE);
  check_libraries_result (libraries);
  g_list_free_full (libraries, g_object_unref);
  libraries = NULL;

  /* Do the check again, this time using the cache */
  issues = srt_system_info_check_libraries (info,
                                            multiarch_tuple,
                                            &libraries);
  g_assert_cmpint (issues, ==, SRT_LIBRARY_ISSUES_NONE);
  check_libraries_result (libraries);

  g_list_free_full (libraries, g_object_unref);
  g_free (expectations_in);
  g_object_unref (info);
}

/*
 * Check that the expectations can be auto-detected from the
 * `STEAM_RUNTIME` environment variable.
 */
static void
auto_expectations (Fixture *f,
                   gconstpointer context)
{
  SrtSystemInfo *info;
  gchar *steam_runtime = NULL;
  GList *libraries = NULL;
  SrtLibraryIssues issues;
  gchar **env;
  const char *multiarch_tuple = NULL;

#ifndef _SRT_MULTIARCH
  g_test_skip ("Unsupported architecture");
  return;
#else
  multiarch_tuple = _SRT_MULTIARCH;
#endif

  env = g_get_environ ();
  steam_runtime = g_build_filename (f->sysroots, "fake-steam-runtime", NULL);
  env = g_environ_setenv (env, "STEAM_RUNTIME", steam_runtime, TRUE);

  info = srt_system_info_new (NULL);
  srt_system_info_set_environ (info, env);
  issues = srt_system_info_check_libraries (info,
                                            multiarch_tuple,
                                            &libraries);
  g_assert_cmpint (issues, ==, SRT_LIBRARY_ISSUES_NONE);
  check_libraries_result (libraries);
  g_list_free_full (libraries, g_object_unref);

  g_object_unref (info);
  g_strfreev (env);
  g_free (steam_runtime);
}

static void
check_library_result (SrtLibrary *library)
{
  const char * const *missing_symbols;
  const char * const *misversioned_symbols;
  const char * const *dependencies;
  gboolean seen_libc;

  g_assert_cmpstr (srt_library_get_requested_name (library), ==, "libz.so.1");

  missing_symbols = srt_library_get_missing_symbols (library);
  g_assert_nonnull (missing_symbols);
  g_assert_cmpstr (missing_symbols[0], ==, NULL);

  misversioned_symbols = srt_library_get_misversioned_symbols (library);
  g_assert_nonnull (misversioned_symbols);
  g_assert_cmpstr (misversioned_symbols[0], ==, NULL);

  dependencies = srt_library_get_dependencies (library);
  g_assert_nonnull (dependencies);
  g_assert_cmpstr (dependencies[0], !=, NULL);
  seen_libc = FALSE;

  for (gsize i = 0; dependencies[i] != NULL; i++)
    {
      g_debug ("libz.so.1 depends on %s", dependencies[i]);

      if (strstr (dependencies[i], "/libc.so.") != NULL)
        seen_libc = TRUE;
    }

  g_assert_true (seen_libc);
}

/*
 * Test if `libz.so.1` is available in the running system and
 * if it has the expected symbols.
 */
static void
library_presence (Fixture *f,
                  gconstpointer context)
{
  SrtSystemInfo *info;
  gchar *expectations_in = NULL;
  SrtLibrary *library = NULL;
  SrtLibraryIssues issues;
  const char *multiarch_tuple = NULL;

#ifndef _SRT_MULTIARCH
  g_test_skip ("Unsupported architecture");
  return;
#else
  multiarch_tuple = _SRT_MULTIARCH;
#endif

  expectations_in = g_build_filename (f->srcdir, "expectations", NULL);
  info = srt_system_info_new (expectations_in);

  issues = srt_system_info_check_library (info,
                                          multiarch_tuple,
                                          "libz.so.1",
                                          &library);
  g_assert_cmpint (issues, ==, SRT_LIBRARY_ISSUES_NONE);

  check_library_result (library);

  g_clear_pointer (&library, g_object_unref);
  /* Do the check again, this time using the cache */
  issues = srt_system_info_check_library (info,
                                          multiarch_tuple,
                                          "libz.so.1",
                                          &library);
  g_assert_cmpint (issues, ==, SRT_LIBRARY_ISSUES_NONE);

  check_library_result (library);

  g_object_unref (library);
  g_free (expectations_in);
  g_object_unref (info);
}

static void
check_library_libz_missing_sym_result (SrtLibrary *library)
{
  const char * const *missing_symbols;
  const char * const *misversioned_symbols;
  const char * const *dependencies;
  gboolean seen_libc;

  g_assert_nonnull (library);
  g_assert_cmpstr (srt_library_get_requested_name (library), ==, "libz.so.1");
  g_debug ("path to libz.so.1 is %s", srt_library_get_absolute_path (library));
  g_assert_true (srt_library_get_absolute_path (library)[0] == '/');
  g_assert_true (g_file_test (srt_library_get_absolute_path (library), G_FILE_TEST_EXISTS));

  g_assert_cmpint (srt_library_get_issues (library) & SRT_LIBRARY_ISSUES_MISSING_SYMBOLS, !=, 0);
  g_assert_cmpint (srt_library_get_issues (library) & SRT_LIBRARY_ISSUES_MISVERSIONED_SYMBOLS, !=, 0);

  missing_symbols = srt_library_get_missing_symbols (library);
  g_assert_nonnull (missing_symbols);
  g_assert_cmpstr (missing_symbols[0], ==, "missing@NotAvailable");
  g_assert_cmpstr (missing_symbols[1], ==, NULL);

  misversioned_symbols = srt_library_get_misversioned_symbols (library);
  g_assert_nonnull (misversioned_symbols);
  g_assert_cmpstr (misversioned_symbols[0], ==, "crc32@WRONG_VERSION");
  g_assert_cmpstr (misversioned_symbols[1], ==, NULL);

  dependencies = srt_library_get_dependencies (library);
  g_assert_nonnull (dependencies);
  g_assert_cmpstr (dependencies[0], !=, NULL);
  seen_libc = FALSE;

  for (gsize i = 0; dependencies[i] != NULL; i++)
    {
      g_debug ("libz.so.1 depends on %s", dependencies[i]);

      if (strstr (dependencies[i], "/libc.so.") != NULL)
        seen_libc = TRUE;
    }

  g_assert_true (seen_libc);
}

static void
check_missing_libraries_result (GList *libraries)
{
  SrtLibrary *library = NULL;
  const char * const *missing_symbols;
  const char * const *misversioned_symbols;
  const char * const *dependencies;
  GList *l;

  g_assert_nonnull (libraries);
  l = libraries;

  /* Test first library. Alphabetical order is an API guarantee, so we know
   * which one it should be. */
  library = l->data;
  g_assert_nonnull (library);
  g_assert_cmpstr (srt_library_get_requested_name (library), ==, "libgio-MISSING-2.0.so.0");
  g_assert_cmpstr (srt_library_get_absolute_path (library), ==, NULL);

  g_assert_cmpint (srt_library_get_issues (library), ==, SRT_LIBRARY_ISSUES_CANNOT_LOAD);

  missing_symbols = srt_library_get_missing_symbols (library);
  g_assert_nonnull (missing_symbols);
  g_assert_cmpstr (missing_symbols[0], ==, NULL);

  misversioned_symbols = srt_library_get_misversioned_symbols (library);
  g_assert_nonnull (misversioned_symbols);
  g_assert_cmpstr (misversioned_symbols[0], ==, NULL);

  dependencies = srt_library_get_dependencies (library);
  g_assert_nonnull (dependencies);
  g_assert_cmpstr (dependencies[0], ==, NULL);

  /* Test second library */
  l = g_list_next (l);
  library = l->data;
  g_assert_nonnull (library);
  g_assert_cmpstr (srt_library_get_requested_name (library), ==, "libglib-2.0.so.0");
  g_debug ("path to libglib-2.0.so.0 is %s", srt_library_get_absolute_path (library));
  g_assert_true (srt_library_get_absolute_path (library)[0] == '/');
  g_assert_true (g_file_test (srt_library_get_absolute_path (library), G_FILE_TEST_EXISTS));

  g_assert_cmpint (srt_library_get_issues (library), ==, SRT_LIBRARY_ISSUES_NONE);

  missing_symbols = srt_library_get_missing_symbols (library);
  g_assert_nonnull (missing_symbols);
  g_assert_cmpstr (missing_symbols[0], ==, NULL);

  misversioned_symbols = srt_library_get_misversioned_symbols (library);
  g_assert_nonnull (misversioned_symbols);
  g_assert_cmpstr (misversioned_symbols[0], ==, NULL);

  dependencies = srt_library_get_dependencies (library);
  g_assert_nonnull (dependencies);
  g_assert_cmpstr (dependencies[0], !=, NULL);

  /* Test last library */
  l = g_list_next (l);
  library = l->data;
  check_library_libz_missing_sym_result (library);
}

/*
 * Test libraries that are either not available or with missing and
 * misversioned symbols.
 */
static void
libraries_missing (Fixture *f,
                   gconstpointer context)
{
  SrtSystemInfo *info;
  gchar *expectations_in = NULL;
  GList *libraries = NULL;
  SrtLibraryIssues issues;
  const char *multiarch_tuple = NULL;

#ifndef _SRT_MULTIARCH
  g_test_skip ("Unsupported architecture");
  return;
#else
  multiarch_tuple = _SRT_MULTIARCH;
#endif

  expectations_in = g_build_filename (f->srcdir, "expectations_with_missings", NULL);
  info = srt_system_info_new (expectations_in);
  issues = srt_system_info_check_libraries (info,
                                            multiarch_tuple,
                                            &libraries);
  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISSING_SYMBOLS, !=, 0);
  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_CANNOT_LOAD, !=, 0);
  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISVERSIONED_SYMBOLS, !=, 0);
  check_missing_libraries_result (libraries);
  g_list_free_full (libraries, g_object_unref);
  libraries = NULL;

  /* Do the check again, this time using the cache */
  issues = srt_system_info_check_libraries (info,
                                            multiarch_tuple,
                                            &libraries);
  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISSING_SYMBOLS, !=, 0);
  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_CANNOT_LOAD, !=, 0);
  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISVERSIONED_SYMBOLS, !=, 0);
  check_missing_libraries_result (libraries);

  g_list_free_full (libraries, g_object_unref);
  g_free (expectations_in);
  g_object_unref (info);
}

static void
check_library_missing_lib_result (SrtLibrary *library)
{
  const char * const *missing_symbols;
  const char * const *misversioned_symbols;
  const char * const *dependencies;

  g_assert_nonnull (library);
  g_assert_cmpstr (srt_library_get_requested_name (library), ==, "libMISSING.so.62");
  g_assert_cmpstr (srt_library_get_absolute_path (library), ==, NULL);

  missing_symbols = srt_library_get_missing_symbols (library);
  g_assert_nonnull (missing_symbols);
  g_assert_cmpstr (missing_symbols[0], ==, NULL);

  misversioned_symbols = srt_library_get_misversioned_symbols (library);
  g_assert_nonnull (misversioned_symbols);
  g_assert_cmpstr (misversioned_symbols[0], ==, NULL);

  dependencies = srt_library_get_dependencies (library);
  g_assert_nonnull (dependencies);
  g_assert_cmpstr (dependencies[0], ==, NULL);
}

/*
 * Test `libz.so.1` expecting missing and misversioned symbols.
 * Then test the missing library `libMISSING.so.62`.
 */
static void
library_missing (Fixture *f,
                 gconstpointer context)
{
  SrtSystemInfo *info;
  gchar *expectations_in = NULL;
  SrtLibrary *library = NULL;
  SrtLibraryIssues issues;
  const char *multiarch_tuple = NULL;

#ifndef _SRT_MULTIARCH
  g_test_skip ("Unsupported architecture");
  return;
#else
  multiarch_tuple = _SRT_MULTIARCH;
#endif

  expectations_in = g_build_filename (f->srcdir, "expectations_with_missings", NULL);
  info = srt_system_info_new (expectations_in);

  /* Check a present library that has a missing symbol */
  issues = srt_system_info_check_library (info,
                                          multiarch_tuple,
                                          "libz.so.1",
                                          &library);
  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISSING_SYMBOLS, !=, 0);
  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISVERSIONED_SYMBOLS, !=, 0);
  check_library_libz_missing_sym_result (library);

  g_clear_pointer (&library, g_object_unref);
  /* Do the check again, this time using the cache */
  issues = srt_system_info_check_library (info,
                                          multiarch_tuple,
                                          "libz.so.1",
                                          &library);
  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISSING_SYMBOLS, !=, 0);
  g_assert_cmpint (issues & SRT_LIBRARY_ISSUES_MISVERSIONED_SYMBOLS, !=, 0);
  check_library_libz_missing_sym_result (library);

  g_clear_pointer (&library, g_object_unref);
  /* Check for a library that isn't listed in any of the .symbols files */
  issues = srt_system_info_check_library (info,
                                          multiarch_tuple,
                                          "libMISSING.so.62",
                                          &library);
  g_assert_cmpint (issues, ==,
                   (SRT_LIBRARY_ISSUES_CANNOT_LOAD |
                    SRT_LIBRARY_ISSUES_UNKNOWN_EXPECTATIONS));
  check_library_missing_lib_result (library);

  g_clear_pointer (&library, g_object_unref);
  /* Do the check again, this time using the cache */
  issues = srt_system_info_check_library (info,
                                          multiarch_tuple,
                                          "libMISSING.so.62",
                                          &library);
  g_assert_cmpint (issues, ==,
                   (SRT_LIBRARY_ISSUES_CANNOT_LOAD |
                    SRT_LIBRARY_ISSUES_UNKNOWN_EXPECTATIONS));
  check_library_missing_lib_result (library);

  g_object_unref (library);
  g_free (expectations_in);
  g_object_unref (info);
}

/*
 * Test libraries with the expectations folder set to NULL.
 */
static void
wrong_expectations (Fixture *f,
                    gconstpointer context)
{
  SrtSystemInfo *info;
  SrtLibraryIssues issues;
  const char *multiarch_tuple = NULL;

#ifndef _SRT_MULTIARCH
  g_test_skip ("Unsupported architecture");
  return;
#else
  multiarch_tuple = _SRT_MULTIARCH;
#endif

  /* Set the expectations folder to one that does not contain the
   * necessary files. We expect the library checks to fail. */
  info = srt_system_info_new ("/dev");

  issues = srt_system_info_check_libraries (info,
                                            multiarch_tuple,
                                            NULL);
  g_assert_cmpint (issues, ==, SRT_LIBRARY_ISSUES_UNKNOWN_EXPECTATIONS);

  issues = srt_system_info_check_library (info,
                                          multiarch_tuple,
                                          "libz.so.1",
                                          NULL);
  g_assert_cmpint (issues, ==, SRT_LIBRARY_ISSUES_UNKNOWN_EXPECTATIONS);

  g_object_unref (info);
}

static void
multiarch_tuples_handling (Fixture *f,
                           gconstpointer context)
{
  GStrv tuples_list;
  SrtSystemInfo *info;
  const gchar *multiarches[] = {"foo8000", "bar9000", NULL};

  info = srt_system_info_new (NULL);

  tuples_list = srt_system_info_dup_multiarch_tuples (info);

  g_assert_cmpstr (srt_system_info_get_primary_multiarch_tuple (info), ==, tuples_list[0]);

#ifndef _SRT_MULTIARCH
  g_assert_cmpstr (tuples_list[0], ==, "UNKNOWN");
#else
  g_assert_cmpstr (tuples_list[0], ==, _SRT_MULTIARCH);
#endif

  g_assert_cmpstr (tuples_list[1], ==, NULL);

  g_strfreev (tuples_list);

  srt_system_info_set_multiarch_tuples (info, multiarches);
  tuples_list = srt_system_info_dup_multiarch_tuples (info);
  g_assert_cmpstr (tuples_list[0], ==, multiarches[0]);
  g_assert_cmpstr (tuples_list[1], ==, multiarches[1]);
  g_assert_cmpstr (tuples_list[2], ==, multiarches[2]);
  g_assert_cmpstr (srt_system_info_get_primary_multiarch_tuple (info), ==, multiarches[0]);

  g_object_unref (info);
  g_strfreev (tuples_list);
}

static void
steam_runtime (Fixture *f,
               gconstpointer context)
{
  SrtSystemInfo *info;
  SrtRuntimeIssues runtime_issues;
  SrtSteamIssues steam_issues;
  SrtSteam *steam_details;
  gchar *runtime_path = NULL;
  gchar *installation_path = NULL;
  gchar *bin32_path = NULL;
  FakeHome *fake_home;

  fake_home = fake_home_new (fake_home_path);
  fake_home_create_structure (fake_home);

  info = srt_system_info_new (NULL);
  fake_home_apply_to_system_info (fake_home, info);

  /* Check for runtime issues */
  runtime_issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (runtime_issues, ==, SRT_RUNTIME_ISSUES_NONE);
  runtime_path = srt_system_info_dup_runtime_path (info);
  g_assert_cmpstr (runtime_path, ==, fake_home->runtime);
  installation_path = srt_system_info_dup_steam_installation_path (info);
  g_assert_cmpstr (installation_path, ==, fake_home->steam_install);
  bin32_path = srt_system_info_dup_steam_bin32_path (info);
  g_assert_cmpstr (bin32_path, ==, fake_home->ubuntu12_32);
  g_free (runtime_path);
  g_free (installation_path);
  g_free (bin32_path);

  /* Do the check again, this time using the cache */
  runtime_issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (runtime_issues, ==, SRT_RUNTIME_ISSUES_NONE);
  runtime_path = srt_system_info_dup_runtime_path (info);
  g_assert_cmpstr (runtime_path, ==, fake_home->runtime);
  installation_path = srt_system_info_dup_steam_installation_path (info);
  g_assert_cmpstr (installation_path, ==, fake_home->steam_install);
  bin32_path = srt_system_info_dup_steam_bin32_path (info);
  g_assert_cmpstr (bin32_path, ==, fake_home->ubuntu12_32);

  /* Check for Steam issues */
  steam_issues = srt_system_info_get_steam_issues (info);
  g_assert_cmpint (steam_issues, ==, SRT_STEAM_ISSUES_NONE);
  steam_details = srt_system_info_get_steam_details (info);
  steam_issues = srt_steam_get_issues (steam_details);
  g_assert_cmpint (steam_issues, ==, SRT_STEAM_ISSUES_NONE);
  g_object_unref (steam_details);

  /* Do the check again, this time using the cache */
  steam_issues = srt_system_info_get_steam_issues (info);
  g_assert_cmpint (steam_issues, ==, SRT_STEAM_ISSUES_NONE);
  steam_details = srt_system_info_get_steam_details (info);
  steam_issues = srt_steam_get_issues (steam_details);
  g_assert_cmpint (steam_issues, ==, SRT_STEAM_ISSUES_NONE);

  fake_home_clean_up (fake_home);
  g_object_unref (info);
  g_object_unref (steam_details);
  g_free (runtime_path);
  g_free (installation_path);
  g_free (bin32_path);
}

static void
steam_runtime_missing (Fixture *f,
                       gconstpointer context)
{
  SrtSystemInfo *info;
  SrtRuntimeIssues runtime_issues;
  SrtSteamIssues steam_issues;
  gchar *full_ld_path = NULL;
  FakeHome *fake_home;

  fake_home = fake_home_new (fake_home_path);
  fake_home_create_structure (fake_home);

  full_ld_path = g_strdup (g_environ_getenv (fake_home->env, "LD_LIBRARY_PATH"));

  info = srt_system_info_new (NULL);

  /* Unset LD_LIBRARY_PATH */
  fake_home->env = g_environ_unsetenv (fake_home->env, "LD_LIBRARY_PATH");
  fake_home_apply_to_system_info (fake_home, info);
  runtime_issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (runtime_issues, ==, SRT_RUNTIME_ISSUES_NOT_IN_LD_PATH);

  /* Re set LD_LIBRARY_PATH and remove a required folder from the runtime */
  fake_home->env = g_environ_setenv (fake_home->env, "LD_LIBRARY_PATH", full_ld_path, TRUE);
  fake_home_apply_to_system_info (fake_home, info);
  g_assert_cmpint (g_rmdir (fake_home->amd64_usr_lib_64), ==, 0);
  runtime_issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (runtime_issues & SRT_RUNTIME_ISSUES_NOT_RUNTIME, !=, 0);
  g_assert_cmpint (runtime_issues & SRT_RUNTIME_ISSUES_NOT_IN_LD_PATH, !=, 0);
  steam_issues = srt_system_info_get_steam_issues (info);
  g_assert_cmpint (steam_issues, ==, SRT_STEAM_ISSUES_NONE);

  /* Do the check again, this time using the cache */
  runtime_issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (runtime_issues & SRT_RUNTIME_ISSUES_NOT_RUNTIME, !=, 0);
  g_assert_cmpint (runtime_issues & SRT_RUNTIME_ISSUES_NOT_IN_LD_PATH, !=, 0);
  steam_issues = srt_system_info_get_steam_issues (info);
  g_assert_cmpint (steam_issues, ==, SRT_STEAM_ISSUES_NONE);

  fake_home_clean_up (fake_home);
  g_object_unref (info);
  g_free (full_ld_path);
}

static void
steam_runtime_pinned (Fixture *f,
                      gconstpointer context)
{
  SrtSystemInfo *info;
  SrtRuntimeIssues issues;
  gchar *full_ld_path = NULL;
  gchar *ld_path = NULL;
  FakeHome *fake_home;

  fake_home = fake_home_new (fake_home_path);
  fake_home_create_structure (fake_home);

  full_ld_path = g_strdup (g_environ_getenv (fake_home->env, "LD_LIBRARY_PATH"));

  info = srt_system_info_new (NULL);

  /* Move the pinned libraries at the end of LD_LIBRARY_PATH */
  ld_path = g_strjoin (":",
                       fake_home->i386_lib_i386,
                       fake_home->i386_lib,
                       fake_home->i386_usr_lib_i386,
                       fake_home->i386_usr_lib,
                       fake_home->amd64_lib_64,
                       fake_home->amd64_lib,
                       fake_home->amd64_usr_lib_64,
                       fake_home->amd64_usr_lib,
                       fake_home->pinned_32,
                       fake_home->pinned_64,
                       NULL);
  fake_home->env = g_environ_setenv (fake_home->env, "LD_LIBRARY_PATH", ld_path, TRUE);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (SRT_RUNTIME_ISSUES_NOT_USING_NEWER_HOST_LIBRARIES, ==, issues);
  g_free (ld_path);

  /* Remove the pinned library folder */
  g_assert_cmpint (g_rmdir (fake_home->pinned_32), ==, 0);
  g_assert_cmpint (g_rmdir (fake_home->pinned_64), ==, 0);
  fake_home->env = g_environ_setenv (fake_home->env, "LD_LIBRARY_PATH", full_ld_path, TRUE);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (SRT_RUNTIME_ISSUES_NOT_USING_NEWER_HOST_LIBRARIES, ==, issues);

  /* Remove pinned libraries from LD_LIBRARY_PATH */
  ld_path = g_strjoin (":",
                       fake_home->i386_lib_i386,
                       fake_home->i386_lib,
                       fake_home->i386_usr_lib_i386,
                       fake_home->i386_usr_lib,
                       fake_home->amd64_lib_64,
                       fake_home->amd64_lib,
                       fake_home->amd64_usr_lib_64,
                       fake_home->amd64_usr_lib,
                       NULL);
  fake_home->env = g_environ_setenv (fake_home->env, "LD_LIBRARY_PATH", ld_path, TRUE);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (SRT_RUNTIME_ISSUES_NOT_USING_NEWER_HOST_LIBRARIES, ==, issues);

  fake_home_clean_up (fake_home);
  g_object_unref (info);
  g_free (full_ld_path);
  g_free (ld_path);
}

static void
runtime_disabled_or_missing (Fixture *f,
                             gconstpointer context)
{
  SrtSystemInfo *info;
  SrtRuntimeIssues issues;
  gchar *runtime_path = NULL;
  FakeHome *fake_home;

  fake_home = fake_home_new (fake_home_path);
  fake_home->create_steamrt_files = FALSE;
  fake_home_create_structure (fake_home);

  info = srt_system_info_new (NULL);

  /* Completely disable the runtime */
  fake_home->env = g_environ_setenv (fake_home->env, "STEAM_RUNTIME", "0", TRUE);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (issues, ==, SRT_RUNTIME_ISSUES_DISABLED);
  runtime_path = srt_system_info_dup_runtime_path (info);
  g_assert_cmpstr (runtime_path, ==, NULL);

  /* Set the runtime to a relative position.
   * Test if we can revover using the expected path.
   * We didn't create SteamRT files so expect to receive a "not_runtime"
   * issue. */
  fake_home->env = g_environ_setenv (fake_home->env, "STEAM_RUNTIME", "my/not/absolute/runtime/path", TRUE);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (SRT_RUNTIME_ISSUES_NOT_IN_ENVIRONMENT |
                   SRT_RUNTIME_ISSUES_NOT_RUNTIME, ==, issues);

  /* Remove the STEAM_RUNTIME environment. */
  fake_home->env = g_environ_unsetenv (fake_home->env, "STEAM_RUNTIME");
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (SRT_RUNTIME_ISSUES_NOT_IN_ENVIRONMENT |
                   SRT_RUNTIME_ISSUES_NOT_RUNTIME, ==, issues);

  /* Disable prefer host libraries */
  fake_home->env = g_environ_setenv (fake_home->env, "STEAM_RUNTIME_PREFER_HOST_LIBRARIES", "0", TRUE);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (SRT_RUNTIME_ISSUES_NOT_USING_NEWER_HOST_LIBRARIES |
                   SRT_RUNTIME_ISSUES_NOT_IN_ENVIRONMENT |
                   SRT_RUNTIME_ISSUES_NOT_RUNTIME, ==, issues);

  fake_home_clean_up (fake_home);
  g_object_unref (info);
}

static void
runtime_version (Fixture *f,
                 gconstpointer context)
{
  SrtSystemInfo *info;
  SrtRuntimeIssues issues;
  gchar *version = NULL;
  gchar *dup_version = NULL;
  GError *error = NULL;
  FakeHome *fake_home;

  fake_home = fake_home_new (fake_home_path);
  fake_home_create_structure (fake_home);

  version = g_build_filename (fake_home->runtime, "version.txt", NULL);
  info = srt_system_info_new (NULL);

  /* Check version with a trailing new line */
  g_file_set_contents (version, "steam-runtime_0.20190711.3\n", -1, &error);
  g_assert_no_error (error);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (issues, ==, SRT_RUNTIME_ISSUES_NONE);

  /* Check version with an empty number */
  g_file_set_contents (version, "steam-runtime_", -1, &error);
  g_assert_no_error (error);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (issues, ==, SRT_RUNTIME_ISSUES_NOT_RUNTIME);

  /* Check version without underscore */
  g_file_set_contents (version, "steam-runtime", -1, &error);
  g_assert_no_error (error);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (issues, ==, SRT_RUNTIME_ISSUES_NOT_RUNTIME);
  dup_version = srt_system_info_dup_runtime_version (info);
  g_assert_cmpstr (dup_version, ==, NULL);

  /* Check version with a custom prefix */
  g_file_set_contents (version, "custom-steam-runtime_0.20190711.3", -1, &error);
  g_assert_no_error (error);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (issues, ==, SRT_RUNTIME_ISSUES_UNOFFICIAL);

  /* Check version with a custom prefix and multiple underscores */
  g_file_set_contents (version, "custom_steam_runtime_0.20190711.3", -1, &error);
  g_assert_no_error (error);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (issues, ==, SRT_RUNTIME_ISSUES_UNOFFICIAL);
  dup_version = srt_system_info_dup_runtime_version (info);
  g_assert_cmpstr (dup_version, ==, "0.20190711.3");
  g_free (dup_version);

  /* Check an empty version file */
  g_file_set_contents (version, "", -1, &error);
  g_assert_no_error (error);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (issues, ==, SRT_RUNTIME_ISSUES_NOT_RUNTIME);
  dup_version = srt_system_info_dup_runtime_version (info);
  g_assert_cmpstr (dup_version, ==, NULL);

  /* Check expected version */
  g_file_set_contents (version, "steam-runtime_0.20190711.3", -1, &error);
  g_assert_no_error (error);
  srt_system_info_set_expected_runtime_version (info, "0.20190711.3");
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (issues, ==, SRT_RUNTIME_ISSUES_NONE);

  /* Check expected version with trailing new line */
  g_file_set_contents (version, "steam-runtime_0.20190711.3\n", -1, &error);
  g_assert_no_error (error);
  srt_system_info_set_expected_runtime_version (info, "0.20190711.3");
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (issues, ==, SRT_RUNTIME_ISSUES_NONE);

  /* Check wrong expected version */
  g_file_set_contents (version, "steam-runtime_0.20190711.3", -1, &error);
  g_assert_no_error (error);
  srt_system_info_set_expected_runtime_version (info, "0.20210813.4");
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (issues, ==, SRT_RUNTIME_ISSUES_UNEXPECTED_VERSION);

  /* Check wrong expected version */
  g_file_set_contents (version, "steam-runtime_", -1, &error);
  g_assert_no_error (error);
  srt_system_info_set_expected_runtime_version (info, "0.20180101.2");
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (SRT_RUNTIME_ISSUES_NOT_RUNTIME |
                   SRT_RUNTIME_ISSUES_UNEXPECTED_VERSION, ==, issues);
  dup_version = srt_system_info_dup_runtime_version (info);
  g_assert_cmpstr (dup_version, ==, "");

  /* Check expected version with custom prefix */
  g_file_set_contents (version, "my-custom_steam_runtime_0.20190711.3", -1, &error);
  g_assert_no_error (error);
  srt_system_info_set_expected_runtime_version (info, "0.20190711.3");
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (issues, ==, SRT_RUNTIME_ISSUES_UNOFFICIAL);

  fake_home_clean_up (fake_home);
  g_object_unref (info);
  g_free (version);
  g_free (dup_version);
}

static void
runtime_unexpected_location (Fixture *f,
                             gconstpointer context)
{
  SrtSystemInfo *info;
  SrtRuntimeIssues issues;
  gchar *dot_steam_root = NULL;
  gchar *my_runtime = NULL;
  gchar *ld_path = NULL;
  gchar *env_path = NULL;
  gchar **parts = NULL;
  GFile *symlink_gfile = NULL;
  GError *error = NULL;
  FakeHome *fake_home;

  fake_home = fake_home_new (fake_home_path);
  fake_home->create_root_symlink = FALSE;
  fake_home_create_structure (fake_home);

  info = srt_system_info_new (NULL);
  dot_steam_root = g_build_filename (fake_home->home, ".steam", "root", NULL);

  my_runtime = g_build_filename (fake_home->steam_install, "ubuntu12_32",
                                 "my-runtime", NULL);


  /* Create a new homedir/.steam/steam symlink that doesn't point to
   * the expected steam runtime path. */
  symlink_gfile = g_file_new_for_path (dot_steam_root);
  g_file_make_symbolic_link (symlink_gfile, fake_home->pinned_64, NULL, &error);
  g_assert_no_error (error);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (issues, ==, SRT_RUNTIME_ISSUES_UNEXPECTED_LOCATION);
  g_object_unref (symlink_gfile);

  /* Move the steam-runtime to another location called "my-runtime" and
   * adjust all the environment variables accordingly. */
  ld_path = g_strdup (g_environ_getenv (fake_home->env, "LD_LIBRARY_PATH"));
  parts = g_strsplit (ld_path, "/ubuntu12_32/steam-runtime/", -1);
  g_free (ld_path);
  ld_path = g_strjoinv ("/ubuntu12_32/my-runtime/", parts);
  g_strfreev (parts);

  env_path = g_strdup (g_environ_getenv (fake_home->env, "PATH"));
  parts = g_strsplit (env_path, "/ubuntu12_32/steam-runtime/", -1);
  g_free (env_path);
  env_path = g_strjoinv ("/ubuntu12_32/my-runtime/", parts);

  g_rename (fake_home->runtime, my_runtime);
  g_remove (dot_steam_root);
  symlink_gfile = g_file_new_for_path (dot_steam_root);
  g_file_make_symbolic_link (symlink_gfile, my_runtime, NULL, &error);
  g_assert_no_error (error);
  fake_home->env = g_environ_setenv (fake_home->env, "LD_LIBRARY_PATH", ld_path, TRUE);
  fake_home->env = g_environ_setenv (fake_home->env, "STEAM_RUNTIME", my_runtime, TRUE);
  fake_home->env = g_environ_setenv (fake_home->env, "PATH", env_path, TRUE);

  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (issues, ==, SRT_RUNTIME_ISSUES_UNEXPECTED_LOCATION);

  fake_home_clean_up (fake_home);
  g_object_unref (symlink_gfile);
  g_object_unref (info);
  g_free (dot_steam_root);
  g_free (my_runtime);
  g_free (ld_path);
  g_free (env_path);
  g_strfreev (parts);
}

static void
steam_symlink (Fixture *f,
               gconstpointer context)
{
  SrtSystemInfo *info;
  SrtSteamIssues issues;
  gchar *data_home = NULL;
  gchar *dot_steam_steam = NULL;
  gchar *dot_steam_root = NULL;
  gchar *dot_steam_bin32 = NULL;
  gchar *installation_path = NULL;
  gchar *ubuntu12_32 = NULL;
  GFile *symlink_gfile = NULL;
  GError *error = NULL;
  FakeHome *fake_home;

  fake_home = fake_home_new (fake_home_path);
  fake_home->create_steam_symlink = FALSE;
  fake_home_create_structure (fake_home);

  info = srt_system_info_new (NULL);
  dot_steam_steam = g_build_filename (fake_home->home, ".steam", "steam", NULL);
  dot_steam_root = g_build_filename (fake_home->home, ".steam", "root", NULL);
  dot_steam_bin32 = g_build_filename (fake_home->home, ".steam", "bin32", NULL);
  ubuntu12_32 = g_build_filename (fake_home->steam_install, "ubuntu12_32", NULL);

  /* We don't have a homedir/.steam/steam symlink. */
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_steam_issues (info);
  g_assert_cmpint ((SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK
                   | SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_DIRECTORY), ==, issues);

  /* Remove homedir/.steam/root symlink and create homedir/.steam/bin32 symlink. */
  g_remove (dot_steam_root);
  g_remove (dot_steam_bin32);
  symlink_gfile = g_file_new_for_path (dot_steam_bin32);
  g_file_make_symbolic_link (symlink_gfile, ubuntu12_32, NULL, &error);
  g_assert_no_error (error);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_steam_issues (info);
  g_assert_cmpint ((SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK
                   | SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_DIRECTORY
                   | SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_SYMLINK
                   | SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_DIRECTORY), ==, issues);

  /* Remove the homedir/.steam/bin32 symlink and set XDG_DATA_HOME env to a
   * folder that is not the expected homedir/.local/share */
  g_remove (dot_steam_bin32);
  data_home = g_build_filename (fake_home->home, "DataHome", NULL);
  fake_home->env = g_environ_setenv (fake_home->env, "XDG_DATA_HOME", data_home, TRUE);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_steam_issues (info);
  g_assert_cmpint ((SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK
                   | SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_DIRECTORY
                   | SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_SYMLINK
                   | SRT_STEAM_ISSUES_DOT_STEAM_ROOT_NOT_DIRECTORY
                   | SRT_STEAM_ISSUES_CANNOT_FIND
                   | SRT_STEAM_ISSUES_CANNOT_FIND_DATA), ==, issues);
  installation_path = srt_system_info_dup_steam_installation_path (info);
  g_assert_cmpstr (installation_path, ==, NULL);

  fake_home_clean_up (fake_home);
  g_object_unref (symlink_gfile);
  g_object_unref (info);
  g_free (data_home);
  g_free (dot_steam_steam);
  g_free (dot_steam_root);
  g_free (dot_steam_bin32);
  g_free (ubuntu12_32);
}

static void
steam_compat_environment_variable (Fixture *f,
                                   gconstpointer context)
{
  g_autoptr(SrtSystemInfo) info = NULL;
  SrtSteamIssues issues;
  g_autofree gchar *dot_steam_root = NULL;
  g_autofree gchar *dot_steam_root_resolved = NULL;
  g_autofree gchar *dot_steam_bin32 = NULL;
  FakeHome *fake_home;

  fake_home = fake_home_new (fake_home_path);
  fake_home_create_structure (fake_home);

  info = srt_system_info_new (NULL);
  dot_steam_root = g_build_filename (fake_home->home, ".steam", "root", NULL);
  dot_steam_bin32 = g_build_filename (fake_home->home, ".steam", "bin32", NULL);

  fake_home->env = g_environ_unsetenv (fake_home->env,
                                       "STEAM_COMPAT_CLIENT_INSTALL_PATH");
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_steam_issues (info);
  g_assert_cmpint (issues, ==, SRT_STEAM_ISSUES_NONE);

  fake_home->env = g_environ_setenv (fake_home->env,
                                     "STEAM_COMPAT_CLIENT_INSTALL_PATH",
                                     dot_steam_root, TRUE);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_steam_issues (info);
  g_assert_cmpint (issues, ==, SRT_STEAM_ISSUES_NONE);

  dot_steam_root_resolved = realpath (dot_steam_root, NULL);
  fake_home->env = g_environ_setenv (fake_home->env,
                                     "STEAM_COMPAT_CLIENT_INSTALL_PATH",
                                     dot_steam_root_resolved, TRUE);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_steam_issues (info);
  g_assert_cmpint (issues, ==, SRT_STEAM_ISSUES_NONE);

  /* Set STEAM_COMPAT_CLIENT_INSTALL_PATH to an unexpected value */
  fake_home->env = g_environ_setenv (fake_home->env,
                                     "STEAM_COMPAT_CLIENT_INSTALL_PATH",
                                     dot_steam_bin32, TRUE);
  fake_home_apply_to_system_info (fake_home, info);
  issues = srt_system_info_get_steam_issues (info);
  g_assert_cmpint (issues, ==,
                   SRT_STEAM_ISSUES_UNEXPECTED_STEAM_COMPAT_CLIENT_INSTALL_PATH);

  fake_home_clean_up (fake_home);
}

/* Recreate the conditions that triggered the Debian bug 916303.
 * Steam was installed into "~/.steam", which meant that the "steam/"
 * directory inside the Steam installation collided with the "~/.steam/steam"
 * symlink, preventing the symlink from being created. */
static void
debian_bug_916303 (Fixture *f,
                   gconstpointer context)
{
  SrtSystemInfo *info;
  SrtSteamIssues issues;
  FakeHome *fake_home;
  gchar *installation_path;
  gchar *data_path;

  fake_home = fake_home_new (fake_home_path);
  fake_home->has_debian_bug_916303 = TRUE;
  fake_home_create_structure (fake_home);

  info = srt_system_info_new (NULL);
  fake_home_apply_to_system_info (fake_home, info);

  issues = srt_system_info_get_steam_issues (info);
  g_assert_cmpint (issues, ==, SRT_STEAM_ISSUES_DOT_STEAM_STEAM_NOT_SYMLINK);
  installation_path = srt_system_info_dup_steam_installation_path (info);
  g_assert_cmpstr (installation_path, ==, fake_home->steam_install);
  g_assert_true (g_str_has_suffix (installation_path, "/.steam"));
  data_path = srt_system_info_dup_steam_data_path (info);
  g_assert_cmpstr (data_path, ==, fake_home->steam_data);
  g_assert_true (g_str_has_suffix (data_path, "/.steam/steam"));

  fake_home_clean_up (fake_home);
  g_object_unref (info);
  g_free (installation_path);
  g_free (data_path);
}

/* Behave as though we're testing a beta client. */
static void
testing_beta_client (Fixture *f,
                     gconstpointer context)
{
  SrtSystemInfo *info;
  SrtSteamIssues issues;
  FakeHome *fake_home;
  gchar *installation_path;
  gchar *data_path;

  fake_home = fake_home_new (fake_home_path);
  fake_home->testing_beta_client = TRUE;
  fake_home_create_structure (fake_home);

  info = srt_system_info_new (NULL);
  fake_home_apply_to_system_info (fake_home, info);

  issues = srt_system_info_get_steam_issues (info);
  g_assert_cmpint (issues, ==, 0);
  installation_path = srt_system_info_dup_steam_installation_path (info);
  g_assert_cmpstr (installation_path, ==, fake_home->steam_install);
  g_assert_true (g_str_has_suffix (installation_path, "/beta-client"));
  data_path = srt_system_info_dup_steam_data_path (info);
  g_assert_cmpstr (data_path, ==, fake_home->steam_data);
  g_assert_true (g_str_has_suffix (data_path, "/.local/share/Steam"));

  fake_home_clean_up (fake_home);
  g_object_unref (info);
  g_free (installation_path);
  g_free (data_path);
}

static void
os_debian10 (Fixture *f,
             gconstpointer context)
{
  SrtSystemInfo *info;
  gchar **strv;
  gchar *sysroot;
  gchar *s;

  sysroot = g_build_filename (f->sysroots, "debian10", NULL);

  info = srt_system_info_new (NULL);
  srt_system_info_set_sysroot (info, sysroot);

  s = srt_system_info_dup_os_build_id (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_id (info);
  g_assert_cmpstr (s, ==, "debian");
  g_free (s);

  strv = srt_system_info_dup_os_id_like (info, FALSE);
  g_assert_null (strv);
  g_strfreev (strv);

  strv = srt_system_info_dup_os_id_like (info, TRUE);
  g_assert_nonnull (strv);
  g_assert_cmpstr (strv[0], ==, "debian");
  g_assert_cmpstr (strv[1], ==, NULL);
  g_strfreev (strv);

  s = srt_system_info_dup_os_name (info);
  g_assert_cmpstr (s, ==, "Debian GNU/Linux");
  g_free (s);

  s = srt_system_info_dup_os_pretty_name (info);
  g_assert_cmpstr (s, ==, "Debian GNU/Linux 10 (buster)");
  g_free (s);

  s = srt_system_info_dup_os_variant (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_variant_id (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_version_codename (info);
  g_assert_cmpstr (s, ==, "buster");
  g_free (s);

  s = srt_system_info_dup_os_version_id (info);
  g_assert_cmpstr (s, ==, "10");
  g_free (s);

  g_object_unref (info);
  g_free (sysroot);
}

static void
os_debian_unstable (Fixture *f,
                    gconstpointer context)
{
  const char *sysroot_name = context;
  SrtSystemInfo *info;
  gchar **strv;
  gchar *sysroot;
  gchar *s;

  sysroot = g_build_filename (f->sysroots, sysroot_name, NULL);

  info = srt_system_info_new (NULL);
  srt_system_info_set_sysroot (info, sysroot);

  s = srt_system_info_dup_os_build_id (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_id (info);
  g_assert_cmpstr (s, ==, "debian");
  g_free (s);

  strv = srt_system_info_dup_os_id_like (info, FALSE);
  g_assert_null (strv);
  g_strfreev (strv);

  strv = srt_system_info_dup_os_id_like (info, TRUE);
  g_assert_nonnull (strv);
  g_assert_cmpstr (strv[0], ==, "debian");
  g_assert_cmpstr (strv[1], ==, NULL);
  g_strfreev (strv);

  s = srt_system_info_dup_os_name (info);
  g_assert_cmpstr (s, ==, "Debian GNU/Linux");
  g_free (s);

  s = srt_system_info_dup_os_pretty_name (info);
  g_assert_cmpstr (s, ==, "Debian GNU/Linux bullseye/sid");
  g_free (s);

  s = srt_system_info_dup_os_variant (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_variant_id (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_version_codename (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_version_id (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  g_object_unref (info);
  g_free (sysroot);
}

static void
os_steamrt (Fixture *f,
            gconstpointer context)
{
  SrtSystemInfo *info;
  gchar **strv;
  gchar *sysroot;
  gchar *s;
  SrtRuntimeIssues runtime_issues;

  if (_srt_tests_skip_if_really_in_steam_runtime ())
    return;

  sysroot = g_build_filename (f->sysroots, "steamrt", NULL);

  info = srt_system_info_new (NULL);
  srt_system_info_set_sysroot (info, sysroot);

  s = srt_system_info_dup_os_build_id (info);
  g_assert_cmpstr (s, ==, "0.20190924.0");
  g_free (s);

  s = srt_system_info_dup_os_id (info);
  g_assert_cmpstr (s, ==, "steamrt");
  g_free (s);

  strv = srt_system_info_dup_os_id_like (info, FALSE);
  g_assert_nonnull (strv);
  g_assert_cmpstr (strv[0], ==, "ubuntu");
  g_assert_cmpstr (strv[1], ==, "debian");
  g_assert_cmpstr (strv[2], ==, NULL);
  g_strfreev (strv);

  strv = srt_system_info_dup_os_id_like (info, TRUE);
  g_assert_nonnull (strv);
  g_assert_cmpstr (strv[0], ==, "steamrt");
  g_assert_cmpstr (strv[1], ==, "ubuntu");
  g_assert_cmpstr (strv[2], ==, "debian");
  g_assert_cmpstr (strv[3], ==, NULL);
  g_strfreev (strv);

  s = srt_system_info_dup_os_name (info);
  g_assert_cmpstr (s, ==, "Steam Runtime");
  g_free (s);

  s = srt_system_info_dup_os_pretty_name (info);
  g_assert_cmpstr (s, ==, "Steam Runtime 1 (scout)");
  g_free (s);

  s = srt_system_info_dup_os_variant (info);
  g_assert_cmpstr (s, ==, "Platform");
  g_free (s);

  s = srt_system_info_dup_os_variant_id (info);
  g_assert_cmpstr (s, ==, "com.valvesoftware.steamruntime.platform-amd64_i386-scout");
  g_free (s);

  s = srt_system_info_dup_os_version_codename (info);
  /* It isn't in os-release(5), but we infer it from the ID and VERSION_ID */
  g_assert_cmpstr (s, ==, "scout");
  g_free (s);

  s = srt_system_info_dup_os_version_id (info);
  g_assert_cmpstr (s, ==, "1");
  g_free (s);

  runtime_issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (runtime_issues, ==, SRT_RUNTIME_ISSUES_NONE);

  s = srt_system_info_dup_runtime_path (info);
  g_assert_cmpstr (s, ==, "/");
  g_free (s);

  s = srt_system_info_dup_runtime_version (info);
  g_assert_cmpstr (s, ==, "0.20190924.0");
  g_free (s);

  g_object_unref (info);
  g_free (sysroot);
}

static void
os_steamrt_unofficial (Fixture *f,
                       gconstpointer context)
{
  SrtSystemInfo *info;
  gchar **strv;
  gchar *sysroot;
  gchar *s;
  SrtRuntimeIssues runtime_issues;

  if (_srt_tests_skip_if_really_in_steam_runtime ())
    return;

  sysroot = g_build_filename (f->sysroots, "steamrt-unofficial", NULL);

  info = srt_system_info_new (NULL);
  srt_system_info_set_sysroot (info, sysroot);
  srt_system_info_set_expected_runtime_version (info, "0.20190711.3");

  s = srt_system_info_dup_os_build_id (info);
  g_assert_cmpstr (s, ==, "unofficial-0.20190924.0");
  g_free (s);

  s = srt_system_info_dup_os_id (info);
  g_assert_cmpstr (s, ==, "steamrt");
  g_free (s);

  strv = srt_system_info_dup_os_id_like (info, FALSE);
  g_assert_nonnull (strv);
  g_assert_cmpstr (strv[0], ==, "ubuntu");
  g_assert_cmpstr (strv[1], ==, "debian");
  g_assert_cmpstr (strv[2], ==, NULL);
  g_strfreev (strv);

  strv = srt_system_info_dup_os_id_like (info, TRUE);
  g_assert_nonnull (strv);
  g_assert_cmpstr (strv[0], ==, "steamrt");
  g_assert_cmpstr (strv[1], ==, "ubuntu");
  g_assert_cmpstr (strv[2], ==, "debian");
  g_assert_cmpstr (strv[3], ==, NULL);
  g_strfreev (strv);

  s = srt_system_info_dup_os_name (info);
  g_assert_cmpstr (s, ==, "Steam Runtime");
  g_free (s);

  s = srt_system_info_dup_os_pretty_name (info);
  g_assert_cmpstr (s, ==, "Steam Runtime 1 (scout)");
  g_free (s);

  s = srt_system_info_dup_os_variant (info);
  g_assert_cmpstr (s, ==, "Platform");
  g_free (s);

  s = srt_system_info_dup_os_variant_id (info);
  g_assert_cmpstr (s, ==, "com.valvesoftware.steamruntime.platform-amd64_i386-scout");
  g_free (s);

  s = srt_system_info_dup_os_version_codename (info);
  /* It isn't in os-release(5), but we infer it from the ID and VERSION_ID */
  g_assert_cmpstr (s, ==, "scout");
  g_free (s);

  s = srt_system_info_dup_os_version_id (info);
  g_assert_cmpstr (s, ==, "1");
  g_free (s);

  runtime_issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (runtime_issues, ==,
                   (SRT_RUNTIME_ISSUES_UNOFFICIAL
                    | SRT_RUNTIME_ISSUES_UNEXPECTED_VERSION));

  s = srt_system_info_dup_runtime_path (info);
  g_assert_cmpstr (s, ==, "/");
  g_free (s);

  s = srt_system_info_dup_runtime_version (info);
  g_assert_cmpstr (s, ==, "unofficial-0.20190924.0");
  g_free (s);

  g_object_unref (info);
  g_free (sysroot);
}

static void
os_invalid_os_release (Fixture *f,
                       gconstpointer context)
{
  SrtSystemInfo *info;
  gchar **strv;
  gchar *sysroot;
  gchar *s;
  SrtRuntimeIssues runtime_issues;

  if (_srt_tests_skip_if_really_in_steam_runtime ())
    return;

  sysroot = g_build_filename (f->sysroots, "invalid-os-release", NULL);

  info = srt_system_info_new (NULL);
  srt_system_info_set_sysroot (info, sysroot);
  srt_system_info_set_expected_runtime_version (info, "0.20190711.3");

  s = srt_system_info_dup_os_build_id (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_id (info);
  g_assert_cmpstr (s, ==, "steamrt");
  g_free (s);

  strv = srt_system_info_dup_os_id_like (info, FALSE);
  g_assert_null (strv);
  g_strfreev (strv);

  strv = srt_system_info_dup_os_id_like (info, TRUE);
  g_assert_nonnull (strv);
  g_assert_cmpstr (strv[0], ==, "steamrt");
  g_assert_cmpstr (strv[1], ==, NULL);
  g_strfreev (strv);

  s = srt_system_info_dup_os_name (info);
  g_assert_cmpstr (s, ==, "This file does not end with a newline");
  g_free (s);

  s = srt_system_info_dup_os_pretty_name (info);
  g_assert_cmpstr (s, ==, "The second name");
  g_free (s);

  s = srt_system_info_dup_os_variant (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_variant_id (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_version_codename (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_version_id (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  runtime_issues = srt_system_info_get_runtime_issues (info);
  g_assert_cmpint (runtime_issues, ==,
                   (SRT_RUNTIME_ISSUES_UNEXPECTED_VERSION
                    | SRT_RUNTIME_ISSUES_NOT_RUNTIME));

  s = srt_system_info_dup_runtime_path (info);
  g_assert_cmpstr (s, ==, "/");
  g_free (s);

  s = srt_system_info_dup_runtime_version (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  g_object_unref (info);
  g_free (sysroot);
}

static void
os_no_os_release (Fixture *f,
                  gconstpointer context)
{
  SrtSystemInfo *info;
  gchar **strv;
  gchar *sysroot;
  gchar *s;

  sysroot = g_build_filename (f->sysroots, "no-os-release", NULL);

  info = srt_system_info_new (NULL);
  srt_system_info_set_sysroot (info, sysroot);

  s = srt_system_info_dup_os_build_id (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_id (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  strv = srt_system_info_dup_os_id_like (info, FALSE);
  g_assert_null (strv);
  g_strfreev (strv);

  strv = srt_system_info_dup_os_id_like (info, TRUE);
  g_assert_null (strv);
  g_strfreev (strv);

  s = srt_system_info_dup_os_name (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_pretty_name (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_variant (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_variant_id (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_version_codename (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  s = srt_system_info_dup_os_version_id (info);
  g_assert_cmpstr (s, ==, NULL);
  g_free (s);

  g_object_unref (info);
  g_free (sysroot);
}

static void
overrides (Fixture *f,
           gconstpointer context)
{
  SrtSystemInfo *info;
  gchar **output;
  gchar **issues;
  gchar *sysroot;
  gchar *s;
  gsize i;
  gboolean seen_link;

  sysroot = g_build_filename (f->sysroots, "steamrt", NULL);

  info = srt_system_info_new (NULL);
  srt_system_info_set_sysroot (info, sysroot);

  s = srt_system_info_dup_os_id (info);
  g_assert_cmpstr (s, ==, "steamrt");
  g_free (s);

  output = srt_system_info_list_pressure_vessel_overrides (info, &issues);

  /* In the steamrt test overrides folder we expect to have a symbolic
   * link to "/run/host/usr/lib/libgcc_s.so.1" */
  seen_link = FALSE;
  /* The output is not guaranteed to be ordered */
  g_debug ("overrides content:");
  for (i = 0; output[i] != NULL; i++)
    {
      g_debug ("%s", output[i]);

      if (strstr (output[i], "/run/host/usr/lib/libgcc_s.so.1") != NULL)
        seen_link = TRUE;
    }
  /* The overrides folder contains 5 folders, plus 4 files, plus one ".keep" file */
  g_assert_cmpint (i, ==, 10);
  g_assert_true (seen_link);
  g_strfreev (output);

  g_assert_null (issues);

  /* Repeat the same check, this time using the cached result */
  output = srt_system_info_list_pressure_vessel_overrides (info, &issues);

  seen_link = FALSE;
  for (i = 0; output[i] != NULL; i++)
    {
      if (strstr (output[i], "/run/host/usr/lib/libgcc_s.so.1") != NULL)
        seen_link = TRUE;
    }
  g_assert_cmpint (i, ==, 10);
  g_assert_true (seen_link);
  g_strfreev (output);

  g_assert_null (issues);

  g_object_unref (info);
  g_free (sysroot);
}

static void
overrides_issues (Fixture *f,
                  gconstpointer context)
{
  SrtSystemInfo *info;
  gchar **output;
  gchar **issues;
  gchar *sysroot;
  gchar *lib_folder;
  gsize i;
  gboolean seen_link;

  sysroot = g_build_filename (f->sysroots, "steamrt-overrides-issues", NULL);

  info = srt_system_info_new (NULL);
  srt_system_info_set_sysroot (info, sysroot);

  lib_folder = g_build_filename (sysroot, "usr", "lib", "pressure-vessel",
                                 "overrides", "lib", NULL);

  /* Remove the read permission for the "lib" folder */
  g_assert_cmpint (g_chmod (lib_folder, 0200), ==, 0);

  if (g_access (lib_folder, R_OK) == 0)
    {
      g_test_skip ("This test can't be executed with elevated privileges");
      goto out;
    }

  output = srt_system_info_list_pressure_vessel_overrides (info, &issues);
  g_assert_nonnull (output);

  /* In the steamrt test overrides folder we expect to have a symbolic
   * link to "/run/host/usr/lib/libgcc_s.so.1" */
  seen_link = FALSE;
  /* The output is not guaranteed to be ordered */
  g_debug ("overrides content:");
  for (i = 0; output[i] != NULL; i++)
    {
      g_debug ("%s", output[i]);

      if (strstr (output[i], "/run/host/usr/lib/libgcc_s.so.1") != NULL)
        seen_link = TRUE;
    }
  /* The overrides folder contains 4 folders, plus one symlink, plus 2 ".keep" files.
   * We expect to not be able to open the "lib" folder, so we should have 4 less items than
   * a "normal" scenario */
  g_assert_cmpint (i, ==, 3);
  /* We expect not to be able to reach the symlink */
  g_assert_false (seen_link);
  g_strfreev (output);

  g_assert_nonnull (issues);
  g_assert_cmpstr (strstr (issues[0], "overrides/lib"), !=, NULL);
  g_strfreev (issues);

  out:
    /* Re set the permissions for "lib" to the default 755 */
    g_chmod (lib_folder, 0755);
    g_object_unref (info);
    g_free (sysroot);
    g_free (lib_folder);
}

static void
overrides_not_available (Fixture *f,
                         gconstpointer context)
{
  SrtSystemInfo *info;
  gchar **output;
  gchar **issues;
  gchar *sysroot;

  sysroot = g_build_filename (f->sysroots, "debian10", NULL);

  info = srt_system_info_new (NULL);
  srt_system_info_set_sysroot (info, sysroot);

  output = srt_system_info_list_pressure_vessel_overrides (info, &issues);

  g_assert_null (output);

  g_assert_null (issues);

  g_object_unref (info);
  g_free (sysroot);
}

static void
pinned_libraries (Fixture *f,
                  gconstpointer context)
{
  SrtSystemInfo *info;
  FakeHome *fake_home;
  gchar *target1 = NULL;
  gchar *target2 = NULL;
  gchar *start = NULL;
  gchar *has_pins = NULL;
  gchar **values = NULL;
  gchar **messages = NULL;
  GFile *symlink_gfile = NULL;
  gboolean seen_pins;
  gsize i;
  GError *error = NULL;

  fake_home = fake_home_new (fake_home_path);
  fake_home_create_structure (fake_home);

  info = srt_system_info_new (NULL);
  fake_home_apply_to_system_info (fake_home, info);

  start = g_build_filename (fake_home->pinned_32, "libcurl.so.3", NULL);
  target1 = g_build_filename (fake_home->pinned_32, "libcurl.so.4", NULL);
  symlink_gfile = g_file_new_for_path (start);

  g_file_make_symbolic_link (symlink_gfile, target1, NULL, &error);
  g_object_unref (symlink_gfile);
  g_assert_no_error (error);

  target2 = g_build_filename (fake_home->i386_usr_lib_i386, "libcurl.so.4.2.0", NULL);
  g_assert_cmpint (g_creat (target2, 0755), >, -1);
  symlink_gfile = g_file_new_for_path (target1);

  g_file_make_symbolic_link (symlink_gfile, target2, NULL, &error);
  g_object_unref (symlink_gfile);
  g_assert_no_error (error);

  has_pins = g_build_filename (fake_home->pinned_32, "has_pins", NULL);
  g_assert_cmpint (g_creat (has_pins, 0755), >, -1);

  values = srt_system_info_list_pinned_libs_32 (info, &messages);
  g_assert_nonnull (values);
  seen_pins = FALSE;
  /* The output is not guaranteed to be ordered */
  g_debug ("pinned_libs_32 content:");
  for (i = 0; values[i] != NULL; i++)
    {
      g_debug ("%s", values[i]);

      if (strstr (values[i], "has_pins") != NULL)
        seen_pins = TRUE;
    }
  /* We placed 3 files in `pinned_libs_32` */
  g_assert_cmpint (i, ==, 3);
  g_assert_true (seen_pins);
  g_strfreev (values);

  g_assert_null (messages);

  /* Repeat the same check, this time using the cached values */
  values = srt_system_info_list_pinned_libs_32 (info, &messages);
  g_assert_nonnull (values);
  seen_pins = FALSE;
  for (i = 0; values[i] != NULL; i++)
    {
      if (strstr (values[i], "has_pins") != NULL)
        seen_pins = TRUE;
    }
  g_assert_cmpint (i, ==, 3);
  g_assert_true (seen_pins);
  g_strfreev (values);

  g_assert_null (messages);

  g_free (target1);
  g_free (target2);
  g_free (start);
  g_free (has_pins);

  /* Check pinned_libs_64.
   * Set again the environ to flush the cached values */
  fake_home_apply_to_system_info (fake_home, info);
  start = g_build_filename (fake_home->pinned_64, "libcurl.so.3", NULL);
  target1 = g_build_filename (fake_home->pinned_64, "libcurl.so.4", NULL);
  symlink_gfile = g_file_new_for_path (start);

  g_file_make_symbolic_link (symlink_gfile, target1, NULL, &error);
  g_object_unref (symlink_gfile);
  g_assert_no_error (error);

  target2 = g_build_filename (fake_home->amd64_usr_lib_64, "libcurl.so.4.2.0", NULL);
  g_assert_cmpint (g_creat (target2, 0755), >, -1);
  symlink_gfile = g_file_new_for_path (target1);

  g_file_make_symbolic_link (symlink_gfile, target2, NULL, &error);
  g_object_unref (symlink_gfile);
  g_assert_no_error (error);

  has_pins = g_build_filename (fake_home->pinned_64, "has_pins", NULL);
  g_assert_cmpint (g_creat (has_pins, 0755), >, -1);

  values = srt_system_info_list_pinned_libs_64 (info, &messages);
  g_assert_nonnull (values);
  seen_pins = FALSE;
  g_debug ("pinned_libs_64 content:");
  for (i = 0; values[i] != NULL; i++)
    {
      g_debug ("%s", values[i]);

      if (strstr (values[i], "has_pins") != NULL)
        seen_pins = TRUE;
    }
  g_assert_cmpint (i, ==, 3);
  g_assert_true (seen_pins);
  g_strfreev (values);

  g_assert_null (messages);

  /* Repeat the same check, this time using the cached values */
  values = srt_system_info_list_pinned_libs_64 (info, &messages);
  g_assert_nonnull (values);
  seen_pins = FALSE;
  for (i = 0; values[i] != NULL; i++)
    {
      if (strstr (values[i], "has_pins") != NULL)
        seen_pins = TRUE;
    }
  g_assert_cmpint (i, ==, 3);
  g_assert_true (seen_pins);
  g_strfreev (values);

  g_assert_null (messages);

  g_free (target1);
  g_free (target2);
  g_free (start);
  g_free (has_pins);

  fake_home_clean_up (fake_home);
  g_object_unref (info);
}

static void
pinned_libraries_permission (Fixture *f,
                             gconstpointer context)
{
  SrtSystemInfo *info;
  FakeHome *fake_home;
  gchar *no_access = NULL;
  gchar **values = NULL;
  gchar **messages = NULL;
  gboolean seen_no_access;
  gsize i;

  fake_home = fake_home_new (fake_home_path);
  fake_home_create_structure (fake_home);

  info = srt_system_info_new (NULL);
  fake_home_apply_to_system_info (fake_home, info);

  no_access = g_build_filename (fake_home->pinned_32, "no_access", NULL);
  /* Creates a folder without read permissions */
  g_assert_cmpint (g_mkdir_with_parents (no_access, 0200), ==, 0);

  if (g_access (no_access, R_OK) == 0)
    {
      g_test_skip ("This test can't be executed with elevated privileges");
      goto out;
    }

  values = srt_system_info_list_pinned_libs_32 (info, &messages);
  g_assert_nonnull (values);
  seen_no_access = FALSE;
  /* The output is not guaranteed to be ordered */
  g_debug ("pinned_libs_32 content:");
  for (i = 0; values[i] != NULL; i++)
    {
      g_debug ("%s", values[i]);

      if (strstr (values[i], "no_access") != NULL)
        seen_no_access = TRUE;
    }
  /* We placed 1 folder in `pinned_libs_32` */
  g_assert_cmpint (i, ==, 1);
  g_assert_true (seen_no_access);
  g_strfreev (values);

  g_assert_nonnull (messages);
  g_assert_cmpstr (strstr (messages[0], "no_access"), !=, NULL);
  g_strfreev (messages);

  g_free (no_access);

  /* Check pinned_libs_64.
   * Set again the environ to flush the cached values */
  fake_home_apply_to_system_info (fake_home, info);

  no_access = g_build_filename (fake_home->pinned_64, "no_access", NULL);
  g_assert_cmpint (g_mkdir_with_parents (no_access, 0311), ==, 0);

  values = srt_system_info_list_pinned_libs_64 (info, &messages);
  g_assert_nonnull (values);
  seen_no_access = FALSE;
  /* The output is not guaranteed to be ordered */
  g_debug ("pinned_libs_64 content:");
  for (i = 0; values[i] != NULL; i++)
    {
      g_debug ("%s", values[i]);

      if (strstr (values[i], "no_access") != NULL)
        seen_no_access = TRUE;
    }
  /* We placed 1 folder in `pinned_libs_32` */
  g_assert_cmpint (i, ==, 1);
  g_assert_true (seen_no_access);
  g_strfreev (values);

  g_assert_nonnull (messages);
  g_assert_cmpstr (strstr (messages[0], "no_access"), !=, NULL);
  g_strfreev (messages);

  out:
    g_free (no_access);
    fake_home_clean_up (fake_home);
    g_object_unref (info);
}

static void
pinned_libraries_missing (Fixture *f,
                          gconstpointer context)
{
  SrtSystemInfo *info;
  FakeHome *fake_home;
  gchar **values = NULL;
  gchar **messages = NULL;

  fake_home = fake_home_new (fake_home_path);
  fake_home_create_structure (fake_home);

  info = srt_system_info_new (NULL);
  fake_home_apply_to_system_info (fake_home, info);

  g_assert_cmpint (g_rmdir (fake_home->pinned_32), ==, 0);

  values = srt_system_info_list_pinned_libs_32 (info, &messages);
  g_assert_nonnull (values);
  g_assert_cmpstr (values[0], ==, NULL);

  g_clear_pointer (&values, g_strfreev);

  g_assert_nonnull (messages);
  g_assert_cmpstr (strstr (messages[0], "pinned_libs_32"), !=, NULL);
  g_strfreev (messages);

  /* Check pinned_libs_64.
   * Set again the environ to flush the cached values */
  fake_home_apply_to_system_info (fake_home, info);

  g_assert_cmpint (g_rmdir (fake_home->pinned_64), ==, 0);

  values = srt_system_info_list_pinned_libs_64 (info, &messages);
  g_assert_nonnull (values);
  g_assert_cmpstr (values[0], ==, NULL);

  g_clear_pointer (&values, g_strfreev);

  g_assert_nonnull (messages);
  g_assert_cmpstr (strstr (messages[0], "pinned_libs_64"), !=, NULL);
  g_strfreev (messages);

  fake_home_clean_up (fake_home);
  g_object_unref (info);
}

static void
driver_environment (Fixture *f,
                    gconstpointer context)
{
  SrtSystemInfo *info;
  gchar **envp;
  gchar **output;
  gsize i;
  const gchar * const no_environment[] = { NULL };
  const gchar *environment[][2] = { {"LIBVA_DRIVER_NAME", "radeonsi"},
                                    {"MESA_LOADER_DRIVER_OVERRIDE", "i965"},
                                    {"VDPAU_DRIVER", "secret_2"},
                                    {"__GLX_FORCE_VENDOR_LIBRARY_0", "driver_display_zero"},
                                    {"__GLX_FORCE_VENDOR_LIBRARY_12", "display_twelve"},
                                    {"__GLX_VENDOR_LIBRARY_NAME", "my_custom_driver"},
                                    {NULL, NULL} };

  envp = g_strdupv ((gchar **) no_environment);

  for (i = 0; environment[i][0] != NULL; i++)
    envp = g_environ_setenv (envp, environment[i][0], environment[i][1], TRUE);

  info = srt_system_info_new (NULL);
  srt_system_info_set_environ (info, envp);

  output = srt_system_info_list_driver_environment (info);
  g_assert_nonnull (output);
  g_assert_nonnull (output[0]);
  for (i = 0; output[i] != NULL; i++)
    {
      gchar *key_value = g_strjoin ("=", environment[i][0], environment[i][1], NULL);
      g_assert_cmpstr (key_value, ==, output[i]);
      g_free (key_value);
    }
  /* plus one for the last NULL item */
  g_assert_cmpint (G_N_ELEMENTS (environment), ==, i+1);
  g_strfreev (output);

  /* Do it again using the cached values */
  output = srt_system_info_list_driver_environment (info);
  g_assert_nonnull (output);
  g_assert_nonnull (output[0]);
  for (i = 0; output[i] != NULL; i++)
    {
      gchar *key_value = g_strjoin ("=", environment[i][0], environment[i][1], NULL);
      g_assert_cmpstr (key_value, ==, output[i]);
      g_free (key_value);
    }
  /* plus one for the last NULL item */
  g_assert_cmpint (G_N_ELEMENTS (environment), ==, i+1);
  g_strfreev (output);

  /* Test when no custom graphics environment variables are available */
  for (i = 0; environment[i][0] != NULL; i++)
    envp = g_environ_unsetenv (envp, environment[i][0]);

  srt_system_info_set_environ (info, envp);
  output = srt_system_info_list_driver_environment (info);
  g_assert_null (output);

  /* Test that variations from the canonical __GLX_FORCE_VENDOR_LIBRARY_[0-9]+
   * are not picked up */
  envp = g_environ_setenv (envp, "__GLX_FORCE_VENDOR_LIBRARY_0_EXTRA", "test", TRUE);
  envp = g_environ_setenv (envp, "__GLX_FORCE_VENDOR_LIBRARY", "test", TRUE);
  envp = g_environ_setenv (envp, "A__GLX_FORCE_VENDOR_LIBRARY_0", "test", TRUE);
  envp = g_environ_setenv (envp, "__GLX_FORCE_VENDOR_LIBRARY_", "test", TRUE);
  envp = g_environ_setenv (envp, "__GLX_FORCE_VENDOR_LIBRARY0", "test", TRUE);

  srt_system_info_set_environ (info, envp);
  output = srt_system_info_list_driver_environment (info);
  /* We expect an empty list because the environment variables are not following the
   * expected pattern */
  g_assert_null (output);

  g_object_unref (info);
  g_strfreev (envp);
}

static void
steamscript_env (Fixture *f,
                  gconstpointer context)
{
  SrtSystemInfo *info;
  gchar **envp;
  g_autofree gchar *steamscript_path;
  g_autofree gchar *steamscript_version;

  envp = g_get_environ ();

  envp = g_environ_setenv (envp, "STEAMSCRIPT", "/usr/bin/steam", TRUE);
  envp = g_environ_setenv (envp, "STEAMSCRIPT_VERSION", "1.0.0.66", TRUE);

  info = srt_system_info_new (NULL);
  srt_system_info_set_environ (info, envp);

  steamscript_path = srt_system_info_dup_steamscript_path (info);
  steamscript_version = srt_system_info_dup_steamscript_version (info);

  g_assert_cmpstr (steamscript_path, ==, "/usr/bin/steam");
  g_assert_cmpstr (steamscript_version, ==, "1.0.0.66");

  g_clear_pointer (&steamscript_path, g_free);
  g_clear_pointer (&steamscript_version, g_free);

  envp = g_environ_unsetenv (envp, "STEAMSCRIPT");
  envp = g_environ_unsetenv (envp, "STEAMSCRIPT_VERSION");

  srt_system_info_set_environ (info, envp);

  steamscript_path = srt_system_info_dup_steamscript_path (info);
  steamscript_version = srt_system_info_dup_steamscript_version (info);

  g_assert_cmpstr (steamscript_path, ==, NULL);
  g_assert_cmpstr (steamscript_version, ==, NULL);

  g_object_unref (info);
  g_strfreev (envp);
}

/* For the purpose of this test an array that is NULL, and one with just one
 * NULL element, are considered to be equal */
static void
assert_equal_strings_arrays (const gchar * const *array1,
                             const gchar * const *array2)
{
  gsize i = 0;

  if (array1 == NULL)
    {
      if (array2 != NULL)
        g_assert_cmpstr (array2[0], ==, NULL);
      return;
    }

  if (array2 == NULL)
    {
      if (array1 != NULL)
        g_assert_cmpstr (array1[0], ==, NULL);
      return;
    }

  for (i = 0; array1[i] != NULL; i++)
    g_assert_cmpstr (array1[i], ==, array2[i]);

  g_assert_cmpstr (array2[i], ==, NULL);
}

typedef enum
{
  LIBDL_TOKEN_SKIP = 0,
  LIBDL_TOKEN_LIB,
  LIBDL_TOKEN_PLATFORM,
} LibdlToken;

typedef struct
{
  const gchar *path;
  const gchar *data_path;
  const gchar *steamscript_path;
  const gchar *steamscript_version;
  SrtSteamIssues issues;
} SteamInstallationTest;

typedef struct
{
  const gchar *path;
  const gchar *version;
  SrtRuntimeIssues issues;
  const gchar *pinned_libs_32[5];
  const gchar *pinned_libs_64[5];
  const gchar *messages_32[5];
  const gchar *messages_64[5];
} RuntimeTest;

typedef struct
{
  const gchar *build_id;
  const gchar *id;
  const gchar *id_like[5];
  const gchar *name;
  const gchar *pretty_name;
} OsReleaseTest;

typedef struct
{
  SrtContainerType type;
  const gchar *host_path;
  const gchar *flatpak_version;
} ContTest;

typedef struct
{
  SrtMachineType host_machine;
  const gchar *interpreter_root;
  SrtVirtualizationType type;
} VirtTest;

typedef struct
{
  const gchar *library_path;
  const gchar *library_link;
  const gchar *library_soname;
  gboolean is_extra;
} DriverTest;

typedef struct
{
  SrtGraphicsIssues issues;
  const gchar *name;
  const gchar *api_version;
  guint32 vulkan_driver_id;
  const gchar *driver_name;
  const gchar *driver_version;
  const gchar *vendor_id;
  const gchar *device_id;
  const gchar *messages;
  SrtVkPhysicalDeviceType type;
} GraphicsDeviceTest;

typedef struct
{
  SrtWindowSystem window_system;
  SrtRenderingInterface rendering_interface;
  const gchar *renderer;
  const gchar *version;
  SrtGraphicsLibraryVendor library_vendor;
  SrtGraphicsIssues issues;
  const gchar *messages;
  GraphicsDeviceTest devices[4];
  int exit_status;
  int terminating_signal;
  gboolean is_available;
} GraphicsTest;

typedef struct
{
  const char *path;
  const char *resolved;
  const char *error_domain;
  int error_code;
  const char *error_message;
} RuntimeLinkerTest;

typedef struct
{
  LibdlToken libdl_token;
  const char *expansion_value;
  const char *error_domain;
  int error_code;
  const char *error_message;
} LibdlTest;

typedef struct
{
  gboolean can_run;
  LibdlTest libdl[3];
  SrtLibraryIssues issues;
  RuntimeLinkerTest runtime_linker;
  DriverTest dri_drivers[5];
  DriverTest va_api_drivers[5];
  DriverTest vdpau_drivers[5];
  DriverTest glx_drivers[5];
  GraphicsTest graphics[10];
} ArchitectureTest;

typedef struct
{
  const gchar *name;
  const gchar *resulting_name;
  const gchar *charset;
  gboolean is_utf8;
  const gchar *error_domain;
  const gchar *error_message;
  int error_code;
} LocaleTest;

typedef struct
{
  const gchar *json_path;
  const gchar *library_path;
  const gchar *api_version;
  SrtLoadableIssues issues;
  const gchar *error_domain;
  const gchar *error_message;
  int error_code;
} IcdTest;

typedef struct
{
  const gchar *json_path;
  const gchar *name;
  const gchar *description;
  const gchar *type;
  const gchar *api_version;
  const gchar *implementation_version;
  const gchar *library_path;
  SrtLoadableIssues issues;
  const gchar *error_domain;
  const gchar *error_message;
  int error_code;
} LayerTest;

typedef struct
{
  const gchar *id;
  const gchar *commandline;
  const gchar *filename;
  gboolean default_handler;
  gboolean steam_handler;
} DesktopEntryTest;

typedef struct
{
  const gchar *name;
  gboolean available;
  guint32 version;
} XdgPortalInfoTest;

typedef struct
{
  XdgPortalInfoTest interfaces[3];
  XdgPortalInfoTest backends[3];
  SrtXdgPortalIssues issues;
  const gchar *messages;
} XdgPortalTest;

typedef struct
{
  const gchar *description;
  const gchar *input_name;
  gboolean can_write_uinput;
  SteamInstallationTest steam_installation;
  RuntimeTest runtime;
  OsReleaseTest os_release;
  ContTest container;
  VirtTest virt;
  const gchar *driver_environment[5];
  ArchitectureTest architecture[G_N_ELEMENTS (multiarch_tuples)];
  SrtLocaleIssues locale_issues;
  LocaleTest locale[5];
  IcdTest egl_icd[3];
  IcdTest egl_ext_platform[3];
  IcdTest vulkan_icd[3];
  LayerTest vulkan_explicit_layer[3];
  LayerTest vulkan_implicit_layer[3];
  DesktopEntryTest desktop_entry[3];
  XdgPortalTest xdg_portal;
  SrtX86FeatureFlags x86_features;
  SrtX86FeatureFlags x86_known;
} JsonTest;

static JsonTest json_test[] =
{
  { /* Begin Full JSON report */
    .description = "full JSON parsing",
    .input_name = "full-good-report.json",
    .can_write_uinput = TRUE,
    .steam_installation =
    {
      .path = "/home/me/.local/share/Steam",
      .data_path = "/home/me/.local/share/Steam",
      .steamscript_path = "/usr/bin/steam",
      .steamscript_version = "1.0.0.66",
      .issues = SRT_STEAM_ISSUES_STEAMSCRIPT_NOT_IN_ENVIRONMENT,
    },

    .runtime =
    {
      .path = "/home/me/.steam/root/ubuntu12_32/steam-runtime",
      .version = "0.20200123.4",
      .issues = SRT_RUNTIME_ISSUES_NONE,
      .pinned_libs_64 =
      {
        "pinned_libs_64/has_pins",
        "pinned_libs_64/libjack.so.0",
        "pinned_libs_64/system_libGLU.so.1",
        NULL,
      },
    },

    .os_release =
    {
      .id = "arch",
      .id_like = {"ubuntu", "debian", NULL},
      .name = "Arch Linux",
      .pretty_name = "Arch Linux",
      .build_id = "rolling",
    },

    .virt =
    {
      .host_machine = SRT_MACHINE_TYPE_AARCH64,
      .interpreter_root = "/home/me/rootfs",
      .type = SRT_VIRTUALIZATION_TYPE_FEX_EMU,
    },

    .container =
    {
      .type = SRT_CONTAINER_TYPE_DOCKER,
      .host_path = "/the/host/path",
    },

    .architecture =
    {
      {
        .can_run = TRUE,
        .libdl =
        {
          {
            .libdl_token = LIBDL_TOKEN_LIB,
            .expansion_value = "lib",
          },
          {
            .libdl_token = LIBDL_TOKEN_PLATFORM,
            .expansion_value = "mock",
          },
        },
        .runtime_linker =
        {
          .path = "/lib64/ld-linux-mock.so.2",
          .resolved = "/usr/lib/ld-2.31.so",
        },
        .graphics =
        {
          {
            .window_system = SRT_WINDOW_SYSTEM_X11,
            .rendering_interface = SRT_RENDERING_INTERFACE_VULKAN,
            .renderer = SRT_TEST_GOOD_GRAPHICS_RENDERER,
            .version = SRT_TEST_GOOD_VULKAN_VERSION,
            .is_available = TRUE,
            .devices =
            {
              {
                .name = SRT_TEST_GOOD_GRAPHICS_RENDERER,
                .api_version = SRT_TEST_GOOD_GRAPHICS_API_VERSION,
                .vulkan_driver_id = SRT_TEST_GOOD_GRAPHICS_DRIVER_ID,
                .driver_name = SRT_TEST_GOOD_GRAPHICS_DRIVER_NAME,
                .driver_version = SRT_TEST_GOOD_GRAPHICS_DRIVER_VERSION,
                .vendor_id = SRT_TEST_GOOD_GRAPHICS_VENDOR_ID,
                .device_id = SRT_TEST_GOOD_GRAPHICS_DEVICE_ID,
                .type = SRT_VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU,
              },
              {
                .name = SRT_TEST_SOFTWARE_GRAPHICS_RENDERER,
                .api_version = SRT_TEST_SOFTWARE_GRAPHICS_API_VERSION,
                .driver_version = SRT_TEST_SOFTWARE_GRAPHICS_DRIVER_VERSION,
                .vendor_id = SRT_TEST_SOFTWARE_GRAPHICS_VENDOR_ID,
                .device_id = SRT_TEST_SOFTWARE_GRAPHICS_DEVICE_ID,
                .type = SRT_VK_PHYSICAL_DEVICE_TYPE_CPU,
              },
            },
          },
          {
            .window_system = SRT_WINDOW_SYSTEM_X11,
            .rendering_interface = SRT_RENDERING_INTERFACE_VDPAU,
            .renderer = "G3DVL VDPAU Driver Shared Library version 1.0\n",
            .is_available = TRUE,
          },
          {
            .window_system = SRT_WINDOW_SYSTEM_X11,
            .rendering_interface = SRT_RENDERING_INTERFACE_VAAPI,
            .renderer = "Mesa Gallium driver 20.0.5 for AMD Radeon RX 5700 XT (NAVI10, DRM 3.36.0, 5.6.7-arch1-1, LLVM 10.0.0)\n",
            .is_available = TRUE,
          },
          {
            .window_system = SRT_WINDOW_SYSTEM_GLX,
            .rendering_interface = SRT_RENDERING_INTERFACE_GL,
            .messages = "libGL: Can't open configuration file /etc/drirc: No such file or directory.\n/usr/share/libdrm/amdgpu.ids version: 1.0.0\nlibGL: Using DRI3 for screen 0\n",
            .renderer = "AMD Radeon RX 5700 XT (NAVI10, DRM 3.36.0, 5.6.7-arch1-1, LLVM 10.0.0)",
            .version = "4.6 (Compatibility Profile) Mesa 20.0.5",
            .library_vendor = SRT_GRAPHICS_LIBRARY_VENDOR_GLVND,
            .issues = SRT_GRAPHICS_ISSUES_CANNOT_DRAW,
            .terminating_signal = 6,
            .is_available = TRUE,
          },
          {
            .window_system = SRT_WINDOW_SYSTEM_EGL_X11,
            .rendering_interface = SRT_RENDERING_INTERFACE_GL,
            .renderer = "AMD Radeon RX 5700 XT (NAVI10, DRM 3.36.0, 5.6.7-arch1-1, LLVM 10.0.0)",
            .version = "4.6 (Compatibility Profile) Mesa 20.0.5",
            .library_vendor = SRT_GRAPHICS_LIBRARY_VENDOR_GLVND,
            .is_available = TRUE,
          },
          {
            .window_system = SRT_WINDOW_SYSTEM_EGL_X11,
            .rendering_interface = SRT_RENDERING_INTERFACE_GLESV2,
            .renderer = "AMD Radeon RX 5700 XT (NAVI10, DRM 3.36.0, 5.6.7-arch1-1, LLVM 10.0.0)",
            .version = "OpenGL ES 3.2 Mesa 20.0.5",
            .library_vendor = SRT_GRAPHICS_LIBRARY_VENDOR_GLVND,
            .is_available = TRUE,
          },
        },
        .dri_drivers =
        {
          {
            .library_path = "/usr/lib/dri/i915_dri.so",
          },
          {
            .library_path = "/usr/lib/dri/radeonsi_dri.so",
          },
        },
        .va_api_drivers =
        {
          {
            .library_path = "/usr/lib/dri/vdpau_drv_video.so",
          },
        },
        .vdpau_drivers =
        {
          {
            .library_path = "/usr/lib/vdpau/libvdpau_radeonsi.so",
            .library_link = "libvdpau_radeonsi.so.1.0.0",
          },
          {
            .library_path = "/usr/lib/vdpau/libvdpau_radeonsi.so.1",
            .library_link = "libvdpau_radeonsi.so.1.0.0",
          },
        },
        .glx_drivers =
        {
          {
            .library_soname = "libGLX_indirect.so.0",
            .library_path = "/usr/lib/libGLX_mesa.so.0.0.0",
          },
          {
            .library_soname = "libGLX_mesa.so.0",
            .library_path = "/usr/lib/libGLX_mesa.so.0.0.0",
          },
        },
      },
    },

    .locale_issues = SRT_LOCALE_ISSUES_C_UTF8_MISSING | SRT_LOCALE_ISSUES_I18N_SUPPORTED_MISSING,
    .locale =
    {
      {
        .name = "", // <default>
        .resulting_name = "en_US.UTF-8",
        .charset = "UTF-8",
        .is_utf8 = TRUE,
      },
      {
        .name = "C",
        .resulting_name = "C",
        .charset = "ANSI_X3.4-1968",
        .is_utf8 = FALSE,
      },
      {
        .name = "C.UTF-8",
        .error_domain = "srt-locale-error-quark",
        .error_code = 0,
        .error_message = "No such file or directory",
      },
      {
        .name = "en_US.UTF-8",
        .resulting_name = "en_US.UTF-8",
        .charset = "UTF-8",
        .is_utf8 = TRUE,
      },
    },

    .egl_icd =
    {
      {
        .json_path = "/usr/share/glvnd/egl_vendor.d/51_mesa.json",
        .library_path = "libEGL_mesa.so.0",
      },
    },

    .egl_ext_platform =
    {
      {
        .json_path = "/usr/share/egl/egl_external_platform.d/10_nvidia_wayland.json",
        .library_path = "libnvidia-egl-wayland.so.1",
      },
    },

    .vulkan_icd =
    {
      {
        .json_path = "/usr/share/vulkan/icd.d/amd_icd64.json",
        .library_path = "/usr/lib/amdvlk64.so",
        .api_version = "1.2.136",
      },
      {
        .json_path = "/usr/share/vulkan/icd.d/amd_icd_partial.json",
        .library_path = "/usr/lib/amdicdpartial.so",
        .api_version = "1.1.130",
        .issues = SRT_LOADABLE_ISSUES_API_SUBSET,
      },
    },

    .vulkan_explicit_layer =
    {
      {
        .json_path = "/usr/share/vulkan/explicit_layer.d/VkLayer_MESA_overlay.json",
        .name = "VK_LAYER_MESA_overlay",
        .description = "Mesa Overlay layer",
        .type = "GLOBAL",
        .api_version = "1.1.73",
        .implementation_version = "1",
        .library_path = "libVkLayer_MESA_overlay.so",
      },
    },

    .vulkan_implicit_layer =
    {
      {
        .json_path = "/usr/share/vulkan/implicit_layer.d/MangoHud.json",
        .name = "VK_LAYER_MANGOHUD_overlay",
        .description = "Vulkan Hud Overlay",
        .type = "GLOBAL",
        .api_version = "1.2.135",
        .implementation_version = "1",
        .library_path = "/usr/$LIB/libMangoHud.so",
      },
    },

    .desktop_entry =
    {
      {
        .id = "steam.desktop",
        .commandline = "/usr/bin/steam-runtime %U",
        .filename = "/usr/share/applications/steam.desktop",
        .default_handler = TRUE,
        .steam_handler = TRUE,
      },
    },

    .xdg_portal =
    {
      .interfaces =
      {
        {
          .name = "org.freedesktop.portal.OpenURI",
          .available = TRUE,
          .version = 3,
        },
        {
          .name = "org.freedesktop.portal.Email",
          .available = TRUE,
          .version = 2,
        },
      },
      .backends =
      {
        {
          .name = "org.freedesktop.impl.portal.desktop.gtk",
          .available = TRUE,
        },
        {
          .name = "org.freedesktop.impl.portal.desktop.kde",
          .available = FALSE,
        },
      },
    },
  }, /* End Full JSON report */

  { /* Begin Partial JSON report */
    .description = "partial JSON parsing",
    .input_name = "partial-report.json",
    .steam_installation =
    {
      .issues = SRT_STEAM_ISSUES_UNKNOWN,
    },
    .runtime =
    {
      .issues = SRT_RUNTIME_ISSUES_UNKNOWN,
    },
    .virt =
    {
      .host_machine = SRT_MACHINE_TYPE_UNKNOWN,
      .interpreter_root = NULL,
      .type = SRT_VIRTUALIZATION_TYPE_MICROSOFT,
    },
    .container =
    {
      .type = SRT_CONTAINER_TYPE_FLATPAK,
      .flatpak_version = "1.10.2",
    },
    .driver_environment =
    {
      "LIBVA_DRIVER_NAME=vava",
      "<invalid>",
      "MESA_LOADER_DRIVER_OVERRIDE=radeonsi",
    },
    .architecture =
    {
      {
        .can_run = FALSE,
        .runtime_linker =
        {
          .path = "/lib64/ld-linux-mock.so.2",
          /* Error domain and code are missing from the report, so we
           * make something up */
          .error_domain = "srt-architecture-error-quark",
          .error_code = SRT_ARCHITECTURE_ERROR_INTERNAL_ERROR,
          .error_message = "We just don't know",
        },
        .issues = SRT_LIBRARY_ISSUES_UNKNOWN,
        .graphics =
        {
          {
            .window_system = SRT_WINDOW_SYSTEM_X11,
            .rendering_interface = SRT_RENDERING_INTERFACE_VULKAN,
            .renderer = SRT_TEST_GOOD_GRAPHICS_RENDERER,
            .version = SRT_TEST_GOOD_VULKAN_VERSION,
            .is_available = TRUE,
            .devices =
            {
              {
                .name = SRT_TEST_GOOD_GRAPHICS_RENDERER,
                .api_version = SRT_TEST_GOOD_GRAPHICS_API_VERSION,
                .driver_version = SRT_TEST_GOOD_GRAPHICS_DRIVER_VERSION,
                .type = SRT_VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU,
                /* A trailing newline is added by
                 * _srt_json_object_dup_array_of_lines_member() */
                .messages = SRT_TEST_MIXED_VULKAN_MESSAGES_2 "\n",
                .issues = SRT_GRAPHICS_ISSUES_CANNOT_DRAW,
              },
            },
          },
          {
            .window_system = SRT_WINDOW_SYSTEM_X11,
            .rendering_interface = SRT_RENDERING_INTERFACE_VDPAU,
            .renderer = "G3DVL VDPAU Driver Shared Library version 1.0\n",
            .is_available = TRUE,
          },
        },
      },
    },
    .locale_issues = SRT_LOCALE_ISSUES_UNKNOWN,
    .locale =
    {
      {
        .name = "", // <default>
        .resulting_name = "en_US.UTF-8",
        .charset = "UTF-8",
        .is_utf8 = TRUE,
      },
      {
        .name = "C",
        .error_domain = "srt-locale-error-quark",
        .error_code = 1,
        .error_message = "Information about the requested locale is missing",
      },
    },
    .vulkan_icd =
    {
      {
        .json_path = "/usr/share/vulkan/icd.d/amd_icd64.json",
        .error_domain = "g-io-error-quark", /* Default domain */
        .error_code = G_IO_ERROR_FAILED, /* Default error code */
        .error_message = "Something went wrong",
        .issues = SRT_LOADABLE_ISSUES_CANNOT_LOAD,
      },
    },
    .vulkan_explicit_layer =
    {
      {
        .json_path = "/usr/share/vulkan/explicit_layer.d/VkLayer_MESA_overlay.json",
        .name = "VK_LAYER_MESA_overlay",
        .description = "Mesa Overlay layer",
        .type = "GLOBAL",
        .api_version = "1.1.73",
        .implementation_version = "1",
        .library_path = "libVkLayer_MESA_overlay.so",
        .issues = SRT_LOADABLE_ISSUES_DUPLICATED,
      },
      {
        .json_path = "/usr/share/vulkan/explicit_layer.d/VkLayer_new.json",
        .name = "VK_LAYER_MESA_overlay",
        .description = "Mesa Overlay layer",
        .type = "GLOBAL",
        .api_version = "1.1.73",
        .implementation_version = "1",
        .library_path = "/usr/lib/libVkLayer_MESA_overlay.so",
        .issues = SRT_LOADABLE_ISSUES_DUPLICATED,
      },
    },
    .vulkan_implicit_layer =
    {
      {
        .json_path = "/usr/share/vulkan/implicit_layer.d/MangoHud.json",
        .error_domain = "g-io-error-quark", /* Default domain */
        .error_code = G_IO_ERROR_FAILED, /* Default error code */
        .error_message = "Something went wrong",
        .issues = SRT_LOADABLE_ISSUES_CANNOT_LOAD,
      },
    },
    .xdg_portal =
    {
      .issues = SRT_XDG_PORTAL_ISSUES_TIMEOUT,
      .messages = "timeout: failed to run command ‘mock-linux-gnu-check-xdg-portal’: No such file or directory\n",
    },
  }, /* End Partial JSON report */

  { /* Begin Partial-2 JSON report */
    .description = "partial-2 JSON parsing",
    .input_name = "partial-report-2.json",
    .steam_installation =
    {
      .path = "/home/me/.local/share/Steam",
      .issues = SRT_STEAM_ISSUES_UNKNOWN,
    },
    .runtime =
    {
      .path = "/home/me/.steam/root/ubuntu12_32/steam-runtime",
    },
    .os_release =
    {
      .id = "arch",
    },
    .virt =
    {
      .host_machine = SRT_MACHINE_TYPE_UNKNOWN,
      .interpreter_root = NULL,
      .type = SRT_VIRTUALIZATION_TYPE_NONE,
    },
    .container =
    {
      .type = SRT_CONTAINER_TYPE_DOCKER,
    },
    .architecture =
    {
      {
        .can_run = TRUE,
        .issues = SRT_LIBRARY_ISSUES_UNKNOWN,
        .runtime_linker =
        {
          .path = "/lib64/ld-linux-mock.so.2",
          /* We don't have the expected ld.so in the report */
          .error_domain = "srt-architecture-error-quark",
          .error_code = SRT_ARCHITECTURE_ERROR_INTERNAL_ERROR,
          .error_message = "Expected \"/lib64/ld-linux-mock.so.2\" in report, but got \"/foobar\"",
        },
      },
    },
    .locale_issues = SRT_LOCALE_ISSUES_UNKNOWN,
    .locale =
    {
      {
        .name = "", // <default>
        /* Missing the required "charset" */
        .error_domain = "g-io-error-quark", /* Default domain */
        .error_code = G_IO_ERROR_FAILED, /* Default error code */
        .error_message = "(missing error message)",
      },
    },
    .xdg_portal =
    {
      .issues = SRT_XDG_PORTAL_ISSUES_UNKNOWN,
    },
  }, /* End Partial-2 JSON report */

  { /* Begin Empty JSON report */
    .description = "empty JSON parsing",
    .input_name = "empty-report.json",
    .steam_installation =
    {
      .issues = SRT_STEAM_ISSUES_UNKNOWN,
    },
    .runtime =
    {
      .issues = SRT_RUNTIME_ISSUES_UNKNOWN,
    },
    .virt =
    {
      .host_machine = SRT_MACHINE_TYPE_UNKNOWN,
      .interpreter_root = NULL,
      .type = SRT_VIRTUALIZATION_TYPE_UNKNOWN,
    },
    .container =
    {
      .type = SRT_CONTAINER_TYPE_UNKNOWN,
    },
    .architecture =
    {
      {
        .runtime_linker =
        {
          .path = "/lib64/ld-linux-mock.so.2",
          .error_domain = "srt-architecture-error-quark",
          .error_code = SRT_ARCHITECTURE_ERROR_NO_INFORMATION,
          .error_message = "ABI \"mock-linux-gnu\" not included in report",
        },
        .issues = SRT_LIBRARY_ISSUES_CANNOT_LOAD,
      },
    },
    .locale_issues = SRT_LOCALE_ISSUES_UNKNOWN,
    .xdg_portal =
    {
      .issues = SRT_XDG_PORTAL_ISSUES_UNKNOWN,
    },
  }, /* End Empty JSON report */

  { /* Begin Newer JSON report */
    .description = "newer JSON parsing",
    .input_name = "newer-report.json",
    .steam_installation =
    {
      .issues = SRT_STEAM_ISSUES_UNKNOWN,
    },
    .runtime =
    {
      .issues = SRT_RUNTIME_ISSUES_UNKNOWN,
    },
    .virt =
    {
      .host_machine = SRT_MACHINE_TYPE_UNKNOWN,
      .interpreter_root = NULL,
      .type = SRT_VIRTUALIZATION_TYPE_UNKNOWN,
    },
    .container =
    {
      .type = SRT_CONTAINER_TYPE_UNKNOWN,
    },
    .architecture =
    {
      {
        .can_run = FALSE,
        .issues = SRT_LIBRARY_ISSUES_CANNOT_LOAD | SRT_LIBRARY_ISSUES_UNKNOWN,
        .graphics =
        {
          {
            .window_system = SRT_WINDOW_SYSTEM_X11,
            .rendering_interface = SRT_RENDERING_INTERFACE_VDPAU,
            .renderer = "G3DVL VDPAU Driver Shared Library version 1.0\n",
            .library_vendor = SRT_GRAPHICS_LIBRARY_VENDOR_UNKNOWN,
            .issues = SRT_GRAPHICS_ISSUES_CANNOT_DRAW | SRT_GRAPHICS_ISSUES_UNKNOWN,
            .is_available = TRUE,
          },
        },
      },
    },
    .locale_issues = SRT_LOCALE_ISSUES_C_UTF8_MISSING | SRT_LOCALE_ISSUES_UNKNOWN,
    .xdg_portal =
    {
      .issues = SRT_XDG_PORTAL_ISSUES_UNKNOWN,
    },
  }, /* End Newer JSON report */
};

#if defined(__i386__) || defined(__x86_64__) || !defined(_SRT_MULTIARCH)
static const ArchitectureTest i386_architecture_full =
{
  .can_run = FALSE,
  .libdl =
  {
    {
      .libdl_token = LIBDL_TOKEN_LIB,
      .expansion_value = "lib32",
    },
    {
      .libdl_token = LIBDL_TOKEN_PLATFORM,
      .error_domain = "g-io-error-quark",
      .error_code = G_IO_ERROR_NOT_FOUND,
      .error_message = "Unable to find the library: ${ORIGIN}/i386-linux-gnu/${PLATFORM}/libidentify-platform.so: "
                        "cannot open shared object file: No such file or directory",
    },
  },
  .runtime_linker =
  {
    .path = "/lib/ld-linux.so.2",
    .error_domain = "g-io-error-quark",
    .error_code = G_IO_ERROR_NOT_FOUND,
    .error_message = "No such file or directory",
  },
  .issues = SRT_LIBRARY_ISSUES_UNKNOWN,
  .graphics =
  {
    {
      .window_system = SRT_WINDOW_SYSTEM_X11,
      .rendering_interface = SRT_RENDERING_INTERFACE_VULKAN,
      .messages = "ERROR: [Loader Message] Code 0 : /usr/lib/amdvlk64.so: wrong ELF class: ELFCLASS64\nCannot create Vulkan instance.\n",
      .issues = SRT_GRAPHICS_ISSUES_CANNOT_LOAD | SRT_GRAPHICS_ISSUES_CANNOT_DRAW,
      .exit_status = 1,
      .is_available = TRUE,
    },
    {
      .window_system = SRT_WINDOW_SYSTEM_X11,
      .rendering_interface = SRT_RENDERING_INTERFACE_VDPAU,
      .renderer = "G3DVL VDPAU Driver Shared Library version 1.0\n",
      .is_available = TRUE,
    },
    {
      .window_system = SRT_WINDOW_SYSTEM_X11,
      .rendering_interface = SRT_RENDERING_INTERFACE_VAAPI,
      .renderer = "Mesa Gallium driver 20.0.5 for AMD Radeon RX 5700 XT (NAVI10, DRM 3.36.0, 5.6.7-arch1-1, LLVM 10.0.0)\n",
      .is_available = TRUE,
    },
    {
      .window_system = SRT_WINDOW_SYSTEM_GLX,
      .rendering_interface = SRT_RENDERING_INTERFACE_GL,
      .renderer = "AMD Radeon RX 5700 XT (NAVI10, DRM 3.36.0, 5.6.7-arch1-1, LLVM 10.0.0)",
      .version = "4.6 (Compatibility Profile) Mesa 20.0.5",
      .library_vendor = SRT_GRAPHICS_LIBRARY_VENDOR_GLVND,
      .is_available = TRUE,
    },
    {
      .window_system = SRT_WINDOW_SYSTEM_EGL_X11,
      .rendering_interface = SRT_RENDERING_INTERFACE_GL,
      .renderer = "AMD Radeon RX 5700 XT (NAVI10, DRM 3.36.0, 5.6.7-arch1-1, LLVM 10.0.0)",
      .version = "4.6 (Compatibility Profile) Mesa 20.0.5",
      .library_vendor = SRT_GRAPHICS_LIBRARY_VENDOR_GLVND,
      .is_available = TRUE,
    },
    {
      .window_system = SRT_WINDOW_SYSTEM_EGL_X11,
      .rendering_interface = SRT_RENDERING_INTERFACE_GLESV2,
      .renderer = "AMD Radeon RX 5700 XT (NAVI10, DRM 3.36.0, 5.6.7-arch1-1, LLVM 10.0.0)",
      .version = "OpenGL ES 3.2 Mesa 20.0.5",
      .library_vendor = SRT_GRAPHICS_LIBRARY_VENDOR_GLVND,
      .is_available = TRUE,
    },
  },
  .dri_drivers =
  {
    {
      .library_path = "/usr/lib32/dri/radeonsi_dri.so",
    },
    {
      .library_path = "/usr/lib32/dri/vmwgfx_dri.so",
    },
  },
  .va_api_drivers =
  {
    {
      .library_path = "/home/me/.local/share/Steam/ubuntu12_32/steam-runtime/usr/lib/i386-linux-gnu/dri/dummy_drv_video.so",
    },
  },
  .vdpau_drivers =
  {
    {
      .library_path = "/home/me/.local/share/Steam/ubuntu12_32/steam-runtime/usr/lib/i386-linux-gnu/vdpau/libvdpau_trace.so.1",
      .library_link = "libvdpau_trace.so.1.0.0",
    },
  },
  .glx_drivers =
  {
    {
      .library_soname = "libGLX_indirect.so.0",
      .library_path = "/usr/lib32/libGLX_mesa.so.0.0.0",
    },
    {
      .library_soname = "libGLX_mesa.so.0",
      .library_path = "/usr/lib32/libGLX_mesa.so.0.0.0",
    },
  },
};

static const ArchitectureTest i386_architecture_partial =
{
  .can_run = TRUE,
  .issues = SRT_LIBRARY_ISSUES_UNKNOWN,
  .runtime_linker =
  {
    .path = "/lib/ld-linux.so.2",
    .error_domain = "srt-architecture-error-quark",
    .error_code = SRT_ARCHITECTURE_ERROR_NO_INFORMATION,
    .error_message = "Runtime linker for \"i386-linux-gnu\" not included in report",
  },
};

static const ArchitectureTest i386_architecture_missing =
{
  /* i386 is completely missing from this report */
  .issues = SRT_LIBRARY_ISSUES_CANNOT_LOAD,
  .runtime_linker =
  {
    .path = "/lib/ld-linux.so.2",
    .error_domain = "srt-architecture-error-quark",
    .error_code = SRT_ARCHITECTURE_ERROR_NO_INFORMATION,
    .error_message = "ABI \"i386-linux-gnu\" not included in report",
  },
};
#endif

static void
assert_expected_runtime_linker (SrtSystemInfo *info,
                                const char *multiarch_tuple,
                                const RuntimeLinkerTest *rtld)
{
  g_autoptr(GError) error = NULL;
  g_autofree gchar *resolved = NULL;
  const char *expected;
  gboolean ok;

  /* shorthand notation for having no expectations */
  if (rtld->path == NULL)
    return;

  expected = srt_architecture_get_expected_runtime_linker (multiarch_tuple);
  ok = srt_system_info_check_runtime_linker (info, multiarch_tuple,
                                             &resolved, &error);

  g_assert_cmpstr (rtld->path, ==, expected);

  if (rtld->resolved != NULL)
    {
      g_assert (rtld->error_domain == NULL);
      g_assert (rtld->error_code == 0);
      g_assert (rtld->error_message == NULL);

      g_assert_no_error (error);
      g_assert_true (ok);
      g_assert_cmpstr (resolved, ==, rtld->resolved);
    }
  else
    {
      g_assert (rtld->error_domain != NULL);
      g_assert (rtld->error_message != NULL);

      g_assert_error (error, g_quark_from_string (rtld->error_domain),
                      rtld->error_code);
      g_assert_cmpstr (error->message, ==, rtld->error_message);
      g_assert_false (ok);
      g_assert_cmpstr (resolved, ==, NULL);
    }
}

static void
assert_expected_libdl (SrtSystemInfo *info,
                       const char *multiarch_tuple,
                       const LibdlTest *libdl)
{
  g_autoptr(GError) error = NULL;
  g_autofree gchar *libdl_expanded = NULL;

  switch (libdl->libdl_token)
    {
      case LIBDL_TOKEN_LIB:
        libdl_expanded = srt_system_info_dup_libdl_lib (info, multiarch_tuple,
                                                        &error);
        break;

      case LIBDL_TOKEN_PLATFORM:
        libdl_expanded = srt_system_info_dup_libdl_platform (info, multiarch_tuple,
                                                             &error);
        break;

      case LIBDL_TOKEN_SKIP:
      default:
        g_return_if_reached ();
    }

  g_assert_cmpstr (libdl->expansion_value, ==, libdl_expanded);

  if (libdl->expansion_value != NULL)
    {
      g_assert (libdl->error_domain == NULL);
      g_assert (libdl->error_code == 0);
      g_assert_cmpstr (libdl->error_message, ==, NULL);

      g_assert_no_error (error);
    }
  else
    {
      g_assert_cmpstr (libdl->expansion_value, ==, NULL);
      g_assert_cmpstr (libdl->error_domain, !=, NULL);
      g_assert_cmpstr (libdl->error_message, !=, NULL);

      g_assert_error (error, g_quark_from_string (libdl->error_domain),
                      libdl->error_code);
      g_assert_cmpstr (error->message, ==, libdl->error_message);
    }
}

static void
json_parsing (Fixture *f,
              gconstpointer context)
{
  /* Keep this in sync with the json_test declared above. These are the
   * architecture-specific changes compared to the general json_test defined
   * before. */
  g_assert_cmpint (G_N_ELEMENTS (json_test), ==, 5);
#if defined(__i386__) || defined(__x86_64__) || !defined(_SRT_MULTIARCH)
  JsonTest *jt = &json_test[0];  /* full good report */
  jt->runtime.pinned_libs_32[0] = "pinned_libs_32/has_pins";
  jt->runtime.pinned_libs_32[1] = "pinned_libs_32/libdbusmenu-gtk.so.4 -> "
                                  "/home/me/.local/share/Steam/ubuntu12_32/steam-runtime/usr/lib/i386-linux-gnu/libdbusmenu-gtk.so.4.0.13";
  jt->runtime.pinned_libs_32[2] = "pinned_libs_32/system_libGLU.so.1";
  jt->runtime.pinned_libs_64[1] = "pinned_libs_64/libjack.so.0 -> "
                                  "/home/me/.local/share/Steam/ubuntu12_32/steam-runtime/usr/lib/x86_64-linux-gnu/libjack.so.0.1.0";
  jt->architecture[1] = jt->architecture[0];
  jt->architecture[0] = i386_architecture_full;
  jt->architecture[1].libdl[1].expansion_value = "x86_64";
  jt->architecture[1].runtime_linker.path = "/lib64/ld-linux-x86-64.so.2";
  jt->architecture[1].runtime_linker.resolved = "/lib/x86_64-linux-gnu/ld-2.31.so";
  jt->architecture[1].va_api_drivers[1].library_path = "/home/me/.local/share/Steam/ubuntu12_32/steam-runtime/usr/lib/x86_64-linux-gnu/dri/dummy_drv_video.so";
  jt->x86_features = SRT_X86_FEATURE_X86_64 | SRT_X86_FEATURE_CMPXCHG16B;
  jt->x86_known = SRT_X86_FEATURE_X86_64 | SRT_X86_FEATURE_SSE3 | SRT_X86_FEATURE_CMPXCHG16B;

  jt = &json_test[1];  /* partial report */
  jt->architecture[1] = jt->architecture[0];
  jt->architecture[0] = i386_architecture_partial;
  jt->architecture[1].runtime_linker.path = "/lib64/ld-linux-x86-64.so.2";
  jt->xdg_portal.messages = "timeout: failed to run command ‘x86_64-linux-gnu-check-xdg-portal’: No such file or directory\n",

  jt = &json_test[2];  /* partial-2 report */
  jt->architecture[1] = jt->architecture[0];
  jt->architecture[0] = i386_architecture_missing;
  jt->architecture[1].runtime_linker.path = "/lib64/ld-linux-x86-64.so.2";
  jt->architecture[1].runtime_linker.error_message = "Expected \"/lib64/ld-linux-x86-64.so.2\" in report, but got \"/foobar\"",

  jt = &json_test[3];  /* empty report */
  jt->architecture[1] = jt->architecture[0];
  jt->architecture[0] = i386_architecture_missing;
  jt->architecture[1].runtime_linker.path = "/lib64/ld-linux-x86-64.so.2";
  jt->architecture[1].runtime_linker.error_message = "ABI \"x86_64-linux-gnu\" not included in report";

  jt = &json_test[4];  /* newer report */
  jt->architecture[1] = jt->architecture[0];
  jt->architecture[0] = i386_architecture_missing;
  jt->x86_features = SRT_X86_FEATURE_X86_64 | SRT_X86_FEATURE_SSE3 | SRT_X86_FEATURE_UNKNOWN;
  jt->x86_known = SRT_X86_FEATURE_X86_64 | SRT_X86_FEATURE_SSE3 | SRT_X86_FEATURE_CMPXCHG16B | SRT_X86_FEATURE_UNKNOWN;

#elif defined(__aarch64__)
  JsonTest *jt = &json_test[0];  /* full good report */
  jt->architecture[0].libdl[1].expansion_value = "aarch64";
  jt->architecture[0].runtime_linker.path = "/lib/ld-linux-aarch64.so.1";
  jt->architecture[0].dri_drivers[0].library_path = "/usr/lib/dri/mediatek_dri.so";

  jt = &json_test[1];  /* partial report */
  jt->architecture[0].runtime_linker.path = "/lib/ld-linux-aarch64.so.1";
  jt->xdg_portal.messages = "timeout: failed to run command ‘aarch64-linux-gnu-check-xdg-portal’: No such file or directory\n";

  jt = &json_test[2];  /* partial-2 report */
  jt->architecture[0].runtime_linker.path = "/lib/ld-linux-aarch64.so.1";
  jt->architecture[0].runtime_linker.error_message = "Expected \"/lib/ld-linux-aarch64.so.1\" in report, but got \"/foobar\"";

  jt = &json_test[3];  /* empty report */
  jt->architecture[0].runtime_linker.path = "/lib/ld-linux-aarch64.so.1";
  jt->architecture[0].runtime_linker.error_message = "ABI \"aarch64-linux-gnu\" not included in report";
#endif

  for (gsize i = 0; i < G_N_ELEMENTS (json_test); i++)
    {
      const JsonTest *t = &json_test[i];
      SrtSystemInfo *info;
      gchar *input_json;
      gchar **driver_environment_list;
      gsize j;
      gsize jj;
      gsize k;
      GList *icds;
      GList *iter;
      GList *desktop_entries;
      gchar *steam_path = NULL;
      gchar *steam_data_path = NULL;
      gchar *runtime_path = NULL;
      gchar *runtime_v = NULL;
      gchar *build_id;
      gchar *id;
      gchar *name;
      gchar *pretty_name;
      gchar *host_directory;
      g_autoptr(SrtObjectList) portal_interfaces = NULL;
      g_autoptr(SrtObjectList) portal_backends = NULL;
      g_autoptr(SrtObjectList) explicit_layers = NULL;
      g_autoptr(SrtObjectList) implicit_layers = NULL;
      g_autoptr(SrtContainerInfo) container = NULL;
      g_autoptr(SrtVirtualizationInfo) virt = NULL;
      g_autofree gchar *portal_messages = NULL;
      g_autofree gchar *steamscript_path = NULL;
      g_autofree gchar *steamscript_version = NULL;
      SrtXdgPortalIssues issues;
      gchar **pinned_32 = NULL;
      gchar **messages_32 = NULL;
      gchar **pinned_64 = NULL;
      gchar **messages_64 = NULL;
      gchar **id_like = NULL;
      GError *error = NULL;

      g_test_message ("%s: %s", t->input_name, t->description);

      input_json = g_build_filename (f->srcdir, "json-report", multiarch_tuples[0],
                                     t->input_name, NULL);

      info = srt_system_info_new_from_json (input_json, &error);

      g_assert_no_error (error);

      g_assert_cmpint (t->can_write_uinput, ==, srt_system_info_can_write_to_uinput (info));

      steam_path = srt_system_info_dup_steam_installation_path (info);
      steam_data_path = srt_system_info_dup_steam_data_path (info);
      steamscript_path = srt_system_info_dup_steamscript_path (info);
      steamscript_version = srt_system_info_dup_steamscript_version (info);
      g_assert_cmpstr (t->steam_installation.path, ==, steam_path);
      g_assert_cmpstr (t->steam_installation.data_path, ==, steam_data_path);
      g_assert_cmpstr (t->steam_installation.steamscript_path, ==, steamscript_path);
      g_assert_cmpstr (t->steam_installation.steamscript_version, ==, steamscript_version);
      g_assert_cmpint (t->steam_installation.issues, ==, srt_system_info_get_steam_issues (info));

      runtime_path = srt_system_info_dup_runtime_path (info);
      runtime_v = srt_system_info_dup_runtime_version (info);
      pinned_32 = srt_system_info_list_pinned_libs_32 (info, &messages_32);
      pinned_64 = srt_system_info_list_pinned_libs_64 (info, &messages_64);
      g_assert_cmpstr (t->runtime.path, ==, runtime_path);
      g_assert_cmpstr (t->runtime.version, ==, runtime_v);
      g_assert_cmpint (t->runtime.issues, ==, srt_system_info_get_runtime_issues (info));
      assert_equal_strings_arrays (t->runtime.pinned_libs_32, (const gchar * const *)pinned_32);
      assert_equal_strings_arrays (t->runtime.pinned_libs_64, (const gchar * const *)pinned_64);
      assert_equal_strings_arrays (t->runtime.messages_32, (const gchar * const *)messages_32);
      assert_equal_strings_arrays (t->runtime.messages_64, (const gchar * const *)messages_64);

      build_id = srt_system_info_dup_os_build_id (info);
      id = srt_system_info_dup_os_id (info);
      id_like = srt_system_info_dup_os_id_like (info, FALSE);
      name = srt_system_info_dup_os_name (info);
      pretty_name = srt_system_info_dup_os_pretty_name (info);
      g_assert_cmpstr (t->os_release.build_id, ==, build_id);
      g_assert_cmpstr (t->os_release.id, ==, id);
      assert_equal_strings_arrays (t->os_release.id_like, (const gchar * const *)id_like);
      g_assert_cmpstr (t->os_release.name, ==, name);
      g_assert_cmpstr (t->os_release.pretty_name, ==, pretty_name);

      container = srt_system_info_check_container (info);
      host_directory = srt_system_info_dup_container_host_directory (info);
      g_assert_cmpint (t->container.type, ==, srt_system_info_get_container_type (info));
      g_assert_cmpstr (t->container.host_path, ==, host_directory);
      g_assert_cmpstr (t->container.flatpak_version, ==,
                       srt_container_info_get_flatpak_version (container));

      virt = srt_system_info_check_virtualization (info);
      g_assert_cmpint (t->virt.type, ==,
                       srt_virtualization_info_get_virtualization_type (virt));
      g_assert_cmpint (t->virt.host_machine, ==,
                       srt_virtualization_info_get_host_machine (virt));
      g_assert_cmpstr (t->virt.interpreter_root, ==,
                       srt_virtualization_info_get_interpreter_root (virt));

      driver_environment_list = srt_system_info_list_driver_environment (info);
      assert_equal_strings_arrays (t->driver_environment,
                                   (const gchar * const *)driver_environment_list);

      for (j = 0; j < G_N_ELEMENTS (multiarch_tuples); j++)
        {
          ArchitectureTest this_arch = t->architecture[j];
          GList *dri_list;
          GList *va_api_list;
          GList *vdpau_list;
          GList *glx_list;

          g_assert_cmpint (this_arch.can_run, ==, srt_system_info_can_run (info, multiarch_tuples[j]));
          if (this_arch.can_run)
            {
              g_assert_cmpint (this_arch.issues, ==,
                               srt_system_info_check_libraries (info, multiarch_tuples[j], NULL));
            }

          g_assert_cmpint (this_arch.issues, ==,
                           srt_system_info_check_libraries (info, multiarch_tuples[j], NULL));

          for (jj = 0; this_arch.libdl[jj].libdl_token != LIBDL_TOKEN_SKIP; jj++)
            assert_expected_libdl (info, multiarch_tuples[j], &this_arch.libdl[jj]);

          assert_expected_runtime_linker (info,
                                          multiarch_tuples[j],
                                          &this_arch.runtime_linker);

          for (jj = 0; this_arch.graphics[jj].is_available; jj++)
            {
              SrtGraphics *graphics = NULL;
              g_autoptr(SrtObjectList) devices = NULL;
              SrtGraphicsLibraryVendor library_vendor;
              srt_system_info_check_graphics (info,
                                              multiarch_tuples[j],
                                              this_arch.graphics[jj].window_system,
                                              this_arch.graphics[jj].rendering_interface,
                                              &graphics);
              g_assert_cmpstr (this_arch.graphics[jj].messages, ==,
                               srt_graphics_get_messages (graphics));
              g_assert_cmpstr (this_arch.graphics[jj].renderer, ==,
                               srt_graphics_get_renderer_string (graphics));
              g_assert_cmpstr (this_arch.graphics[jj].version, ==,
                               srt_graphics_get_version_string (graphics));
              g_assert_cmpint (this_arch.graphics[jj].issues, ==,
                               srt_graphics_get_issues (graphics));
              srt_graphics_library_is_vendor_neutral (graphics, &library_vendor);
              g_assert_cmpint (this_arch.graphics[jj].library_vendor, ==, library_vendor);
              g_assert_cmpint (this_arch.graphics[jj].exit_status, ==,
                               srt_graphics_get_exit_status (graphics));
              g_assert_cmpint (this_arch.graphics[jj].terminating_signal, ==,
                               srt_graphics_get_terminating_signal (graphics));

              devices = srt_graphics_get_devices (graphics);
              for (k = 0, iter = devices; iter != NULL; iter = iter->next, k++)
                {
                  GraphicsDeviceTest this_device_test = this_arch.graphics[jj].devices[k];
                  g_assert_cmpstr (srt_graphics_device_get_name (iter->data), ==,
                                   this_device_test.name);
                  g_assert_cmpstr (srt_graphics_device_get_api_version (iter->data), ==,
                                   this_device_test.api_version);
                  g_assert_cmpuint (srt_graphics_device_get_vulkan_driver_id (iter->data), ==,
                                    this_device_test.vulkan_driver_id);
                  g_assert_cmpstr (srt_graphics_device_get_driver_name (iter->data), ==,
                                   this_device_test.driver_name);
                  g_assert_cmpstr (srt_graphics_device_get_driver_version (iter->data), ==,
                                   this_device_test.driver_version);
                  g_assert_cmpstr (srt_graphics_device_get_vendor_id (iter->data), ==,
                                   this_device_test.vendor_id);
                  g_assert_cmpstr (srt_graphics_device_get_device_id (iter->data), ==,
                                   this_device_test.device_id);
                  g_assert_cmpint (srt_graphics_device_get_device_type (iter->data), ==,
                                   this_device_test.type);
                  g_assert_cmpstr (srt_graphics_device_get_messages (iter->data), ==,
                                   this_device_test.messages);
                  g_assert_cmpint (srt_graphics_device_get_issues (iter->data), ==,
                                   this_device_test.issues);
                }

              g_object_unref (graphics);
            }

          dri_list = srt_system_info_list_dri_drivers (info, multiarch_tuples[j],
                                                       SRT_DRIVER_FLAGS_INCLUDE_ALL);
          for (jj = 0, iter = dri_list; iter != NULL; iter = iter->next, jj++)
            {
              g_assert_cmpstr (this_arch.dri_drivers[jj].library_path, ==,
                               srt_dri_driver_get_library_path (iter->data));
              g_assert_cmpint (this_arch.dri_drivers[jj].is_extra, ==,
                               srt_dri_driver_is_extra (iter->data));
            }
          g_assert_cmpstr (this_arch.dri_drivers[jj].library_path, ==, NULL);

          va_api_list = srt_system_info_list_va_api_drivers (info, multiarch_tuples[j],
                                                             SRT_DRIVER_FLAGS_INCLUDE_ALL);
          for (jj = 0, iter = va_api_list; iter != NULL; iter = iter->next, jj++)
            {
              g_assert_cmpstr (this_arch.va_api_drivers[jj].library_path, ==,
                               srt_va_api_driver_get_library_path (iter->data));
              g_assert_cmpint (this_arch.va_api_drivers[jj].is_extra, ==,
                               srt_va_api_driver_is_extra (iter->data));
            }
          g_assert_cmpstr (this_arch.va_api_drivers[jj].library_path, ==, NULL);

          vdpau_list = srt_system_info_list_vdpau_drivers (info, multiarch_tuples[j],
                                                           SRT_DRIVER_FLAGS_INCLUDE_ALL);
          for (jj = 0, iter = vdpau_list; iter != NULL; iter = iter->next, jj++)
            {
              g_assert_cmpstr (this_arch.vdpau_drivers[jj].library_path, ==,
                               srt_vdpau_driver_get_library_path (iter->data));
              g_assert_cmpstr (this_arch.vdpau_drivers[jj].library_link, ==,
                               srt_vdpau_driver_get_library_link (iter->data));
              g_assert_cmpint (this_arch.va_api_drivers[jj].is_extra, ==,
                               srt_vdpau_driver_is_extra (iter->data));
            }
          g_assert_cmpstr (this_arch.vdpau_drivers[jj].library_path, ==, NULL);
          g_assert_cmpstr (this_arch.vdpau_drivers[jj].library_link, ==, NULL);

          glx_list = srt_system_info_list_glx_icds (info, multiarch_tuples[j], SRT_DRIVER_FLAGS_INCLUDE_ALL);
          for (jj = 0, iter = glx_list; iter != NULL; iter = iter->next, jj++)
            {
              g_assert_cmpstr (this_arch.glx_drivers[jj].library_path, ==,
                               srt_glx_icd_get_library_path (iter->data));
              g_assert_cmpstr (this_arch.glx_drivers[jj].library_soname, ==,
                               srt_glx_icd_get_library_soname (iter->data));
            }
          g_assert_cmpstr (this_arch.glx_drivers[jj].library_path, ==, NULL);
          g_assert_cmpstr (this_arch.glx_drivers[jj].library_soname, ==, NULL);

          g_list_free_full (dri_list, g_object_unref);
          g_list_free_full (va_api_list, g_object_unref);
          g_list_free_full (vdpau_list, g_object_unref);
          g_list_free_full (glx_list, g_object_unref);
        }

      g_assert_cmpint (t->locale_issues, ==, srt_system_info_get_locale_issues (info));
      for (j = 0; t->locale[j].name != NULL; j++)
        {
          error = NULL;
          SrtLocale *this_locale = srt_system_info_check_locale (info, t->locale[j].name, &error);
          if (this_locale != NULL)
            {
              g_assert_cmpstr (t->locale[j].resulting_name, ==,
                               srt_locale_get_resulting_name (this_locale));
              g_assert_cmpstr (t->locale[j].charset, ==,
                               srt_locale_get_charset (this_locale));
              g_assert_cmpint (t->locale[j].is_utf8, ==,
                               srt_locale_is_utf8 (this_locale));
            }
          else
            {
              g_assert_cmpstr (t->locale[j].error_domain, ==,
                               g_quark_to_string (error->domain));
              g_assert_cmpint (t->locale[j].error_code, ==, error->code);
              g_assert_cmpstr (t->locale[j].error_message, ==, error->message);
            }
          g_clear_object (&this_locale);
          g_clear_error (&error);
        }

      icds = srt_system_info_list_egl_icds (info, multiarch_tuples);
      for (j = 0, iter = icds; iter != NULL; iter = iter->next, j++)
        {
          error = NULL;
          g_assert_cmpstr (t->egl_icd[j].json_path, ==, srt_egl_icd_get_json_path (iter->data));
          if (srt_egl_icd_check_error (iter->data, &error))
            {
              g_assert_cmpstr (t->egl_icd[j].library_path, ==, srt_egl_icd_get_library_path (iter->data));
            }
          else
            {
              g_assert_cmpstr (t->egl_icd[j].error_domain, ==,
                               g_quark_to_string (error->domain));
              g_assert_cmpint (t->egl_icd[j].error_code, ==, error->code);
              g_assert_cmpstr (t->egl_icd[j].error_message, ==, error->message);
            }
          g_clear_error (&error);
        }
      g_list_free_full (icds, g_object_unref);

      icds = srt_system_info_list_egl_external_platforms (info, multiarch_tuples);
      for (j = 0, iter = icds; iter != NULL; iter = iter->next, j++)
        {
          error = NULL;
          g_assert_cmpstr (t->egl_ext_platform[j].json_path, ==, srt_egl_external_platform_get_json_path (iter->data));
          if (srt_egl_external_platform_check_error (iter->data, &error))
            {
              g_assert_cmpstr (t->egl_ext_platform[j].library_path, ==, srt_egl_external_platform_get_library_path (iter->data));
            }
          else
            {
              g_assert_cmpstr (t->egl_ext_platform[j].error_domain, ==,
                               g_quark_to_string (error->domain));
              g_assert_cmpint (t->egl_ext_platform[j].error_code, ==, error->code);
              g_assert_cmpstr (t->egl_ext_platform[j].error_message, ==, error->message);
            }
          g_clear_error (&error);
        }
      g_list_free_full (icds, g_object_unref);

      icds = srt_system_info_list_vulkan_icds (info, multiarch_tuples);
      for (j = 0, iter = icds; iter != NULL; iter = iter->next, j++)
        {
          error = NULL;
          g_assert_cmpstr (t->vulkan_icd[j].json_path, ==, srt_vulkan_icd_get_json_path (iter->data));
          g_assert_cmpint (t->vulkan_icd[j].issues, ==, srt_vulkan_icd_get_issues (iter->data));
          if (srt_vulkan_icd_check_error (iter->data, &error))
            {
              g_assert_cmpstr (t->vulkan_icd[j].library_path, ==,
                               srt_vulkan_icd_get_library_path (iter->data));
              g_assert_cmpstr (t->vulkan_icd[j].api_version, ==,
                               srt_vulkan_icd_get_api_version (iter->data));
            }
          else
            {
              g_assert_cmpstr (t->vulkan_icd[j].error_domain, ==,
                               g_quark_to_string (error->domain));
              g_assert_cmpint (t->vulkan_icd[j].error_code, ==, error->code);
              g_assert_cmpstr (t->vulkan_icd[j].error_message, ==, error->message);
            }
        }

      explicit_layers = srt_system_info_list_explicit_vulkan_layers (info);
      for (j = 0, iter = explicit_layers; iter != NULL; iter = iter->next, j++)
        {
          error = NULL;
          g_assert_cmpstr (t->vulkan_explicit_layer[j].json_path, ==,
                           srt_vulkan_layer_get_json_path (iter->data));
          g_assert_cmpint (t->vulkan_explicit_layer[j].issues, ==,
                           srt_vulkan_layer_get_issues (iter->data));
          if (srt_vulkan_layer_check_error (iter->data, &error))
            {
              g_assert_cmpstr (t->vulkan_explicit_layer[j].name, ==,
                               srt_vulkan_layer_get_name (iter->data));
              g_assert_cmpstr (t->vulkan_explicit_layer[j].description, ==,
                               srt_vulkan_layer_get_description (iter->data));
              g_assert_cmpstr (t->vulkan_explicit_layer[j].type, ==,
                               srt_vulkan_layer_get_type_value (iter->data));
              g_assert_cmpstr (t->vulkan_explicit_layer[j].api_version, ==,
                               srt_vulkan_layer_get_api_version (iter->data));
              g_assert_cmpstr (t->vulkan_explicit_layer[j].implementation_version, ==,
                               srt_vulkan_layer_get_implementation_version (iter->data));
              g_assert_cmpstr (t->vulkan_explicit_layer[j].library_path, ==,
                               srt_vulkan_layer_get_library_path (iter->data));
            }
          else
            {
              g_assert_cmpstr (t->vulkan_explicit_layer[j].error_domain, ==,
                               g_quark_to_string (error->domain));
              g_assert_cmpint (t->vulkan_explicit_layer[j].error_code, ==, error->code);
              g_assert_cmpstr (t->vulkan_explicit_layer[j].error_message, ==, error->message);
            }
        }

      implicit_layers = srt_system_info_list_implicit_vulkan_layers (info);
      for (j = 0, iter = implicit_layers; iter != NULL; iter = iter->next, j++)
        {
          error = NULL;
          g_assert_cmpstr (t->vulkan_implicit_layer[j].json_path, ==,
                           srt_vulkan_layer_get_json_path (iter->data));
          g_assert_cmpint (t->vulkan_implicit_layer[j].issues, ==,
                           srt_vulkan_layer_get_issues (iter->data));
          if (srt_vulkan_layer_check_error (iter->data, &error))
            {
              g_assert_cmpstr (t->vulkan_implicit_layer[j].name, ==,
                               srt_vulkan_layer_get_name (iter->data));
              g_assert_cmpstr (t->vulkan_implicit_layer[j].description, ==,
                               srt_vulkan_layer_get_description (iter->data));
              g_assert_cmpstr (t->vulkan_implicit_layer[j].type, ==,
                               srt_vulkan_layer_get_type_value (iter->data));
              g_assert_cmpstr (t->vulkan_implicit_layer[j].api_version, ==,
                               srt_vulkan_layer_get_api_version (iter->data));
              g_assert_cmpstr (t->vulkan_implicit_layer[j].implementation_version, ==,
                               srt_vulkan_layer_get_implementation_version (iter->data));
              g_assert_cmpstr (t->vulkan_implicit_layer[j].library_path, ==,
                               srt_vulkan_layer_get_library_path (iter->data));
            }
          else
            {
              g_assert_cmpstr (t->vulkan_implicit_layer[j].error_domain, ==,
                               g_quark_to_string (error->domain));
              g_assert_cmpint (t->vulkan_implicit_layer[j].error_code, ==, error->code);
              g_assert_cmpstr (t->vulkan_implicit_layer[j].error_message, ==, error->message);
            }
        }
      g_assert_cmpstr (t->vulkan_implicit_layer[j].json_path, ==, NULL);

      desktop_entries = srt_system_info_list_desktop_entries (info);
      for (j = 0, iter = desktop_entries; iter != NULL; iter = iter->next, j++)
        {
          g_assert_cmpstr (t->desktop_entry[j].id, ==, srt_desktop_entry_get_id (iter->data));
          g_assert_cmpstr (t->desktop_entry[j].commandline, ==,
                           srt_desktop_entry_get_commandline (iter->data));
          g_assert_cmpstr (t->desktop_entry[j].filename, ==, srt_desktop_entry_get_filename (iter->data));
          g_assert_cmpint (t->desktop_entry[j].default_handler, ==,
                           srt_desktop_entry_is_default_handler (iter->data));
          g_assert_cmpint (t->desktop_entry[j].steam_handler, ==,
                           srt_desktop_entry_is_steam_handler (iter->data));
        }

      portal_interfaces = srt_system_info_list_xdg_portal_interfaces (info);
      for (j = 0, iter = portal_interfaces; iter != NULL; iter = iter->next, j++)
        {
          g_assert_cmpstr (t->xdg_portal.interfaces[j].name, ==,
                           srt_xdg_portal_interface_get_name (iter->data));
          g_assert_cmpint (t->xdg_portal.interfaces[j].available, ==,
                           srt_xdg_portal_interface_is_available (iter->data));
          g_assert_cmpint (t->xdg_portal.interfaces[j].version, ==,
                           srt_xdg_portal_interface_get_version (iter->data));
        }
      g_assert_cmpstr (t->xdg_portal.interfaces[j].name, ==, NULL);

      portal_backends = srt_system_info_list_xdg_portal_backends (info);
      for (j = 0, iter = portal_backends; iter != NULL; iter = iter->next, j++)
        {
          g_assert_cmpstr (t->xdg_portal.backends[j].name, ==,
                           srt_xdg_portal_backend_get_name (iter->data));
          g_assert_cmpint (t->xdg_portal.backends[j].available, ==,
                           srt_xdg_portal_backend_is_available (iter->data));
        }
      g_assert_cmpstr (t->xdg_portal.backends[j].name, ==, NULL);

      issues = srt_system_info_get_xdg_portal_issues (info, &portal_messages);

      g_assert_cmpint (issues, ==, t->xdg_portal.issues);
      g_assert_cmpstr (portal_messages, ==, t->xdg_portal.messages);

      g_assert_cmpint (t->x86_features, ==, srt_system_info_get_x86_features (info));
      g_assert_cmpint (t->x86_known, ==, srt_system_info_get_known_x86_features (info));

      g_list_free_full (icds, g_object_unref);
      g_free (steam_path);
      g_free (steam_data_path);
      g_free (runtime_path);
      g_free (runtime_v);
      g_free (build_id);
      g_free (id);
      g_free (name);
      g_free (pretty_name);
      g_free (host_directory);
      g_strfreev (pinned_32);
      g_strfreev (pinned_64);
      g_strfreev (messages_32);
      g_strfreev (messages_64);
      g_strfreev (id_like);
      g_strfreev (driver_environment_list);
      g_free (input_json);
      g_clear_error (&error);
      g_object_unref (info);
    }
}

static void
architecture_symlinks (Fixture *f,
                       gconstpointer context)
{
  g_autoptr(SrtSystemInfo) info;
  g_autofree gchar *sysroot = NULL;
  gboolean ret;

  sysroot = g_build_filename (f->sysroots, "debian10", NULL);

  info = srt_system_info_new (NULL);
  srt_system_info_set_sysroot (info, sysroot);

  /* In the mock Debian 10 sysroot created by tests/generate-sysroots.py,
   * the well-known linker paths are symbolic links, much like they are
   * on a real Debian system. */

    {
      g_autoptr(GError) error = NULL;
      g_autofree gchar *resolved = NULL;

      ret = srt_system_info_check_runtime_linker (info, SRT_ABI_X86_64,
                                                  &resolved, &error);
      g_assert_no_error (error);
      g_assert_true (ret);
      g_assert_cmpstr (resolved, ==, "/usr/lib/x86_64-linux-gnu/ld.so");
    }

    {
      g_autoptr(GError) error = NULL;
      g_autofree gchar *resolved = NULL;

      ret = srt_system_info_check_runtime_linker (info, SRT_ABI_I386,
                                                  &resolved, &error);
      g_assert_no_error (error);
      g_assert_true (ret);
      g_assert_cmpstr (resolved, ==, "/usr/lib/i386-linux-gnu/ld.so");
    }

  /* The sysroot doesn't include x32 support. */
    {
      g_autoptr(GError) error = NULL;
      g_autofree gchar *resolved = NULL;

      ret = srt_system_info_check_runtime_linker (info, "x86_64-linux-gnux32",
                                                  &resolved, &error);
      g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
      g_assert_false (ret);
      g_assert_cmpstr (resolved, ==, NULL);
    }

    {
      g_autoptr(GError) error = NULL;
      g_autofree gchar *resolved = NULL;

      ret = srt_system_info_check_runtime_linker (info, "hal9000-netbsd",
                                                  &resolved, &error);
      /* We have no idea what the runtime linker would be, so we have
       * no way to check for it */
      g_assert_error (error, SRT_ARCHITECTURE_ERROR,
                      SRT_ARCHITECTURE_ERROR_NO_INFORMATION);
      g_assert_false (ret);
      g_assert_cmpstr (resolved, ==, NULL);
    }
}

static void
architecture_notlinks (Fixture *f,
                       gconstpointer context)
{
  g_autoptr(SrtSystemInfo) info;
  g_autofree gchar *sysroot = NULL;
  gboolean ret;

  sysroot = g_build_filename (f->sysroots, "ubuntu16", NULL);

  info = srt_system_info_new (NULL);
  srt_system_info_set_sysroot (info, sysroot);

  /* In the mock Ubuntu 16.04 sysroot created by tests/generate-sysroots.py,
   * the well-known runtime linker for x86_64 is a real file (unlike
   * real Ubuntu systems) and the runtime linker for i386 is missing. */

    {
      g_autoptr(GError) error = NULL;
      g_autofree gchar *resolved = NULL;

      ret = srt_system_info_check_runtime_linker (info, "x86_64-linux-gnu",
                                                  &resolved, &error);
      g_assert_no_error (error);
      g_assert_true (ret);
      g_assert_cmpstr (resolved, ==, "/lib64/ld-linux-x86-64.so.2");
    }

    {
      g_autoptr(GError) error = NULL;
      g_autofree gchar *resolved = NULL;

      ret = srt_system_info_check_runtime_linker (info, "i386-linux-gnu",
                                                  &resolved, &error);
      g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
      g_assert_false (ret);
      g_assert_cmpstr (resolved, ==, NULL);
    }
}

int
main (int argc,
      char **argv)
{
  int status;

  argv0 = argv[0];

  /* We can't use %G_TEST_OPTION_ISOLATE_DIRS because we are targeting an older glib verion.
   * As a workaround we use our function `setup_env()`.
   * We can't use an environ because in `_srt_steam_check()` we call `g_app_info_get_*` that
   * doesn't support a custom environ. */
  _srt_tests_init (&argc, &argv, NULL);
  fake_home_path = _srt_global_setup_private_xdg_dirs ();
  global_sysroots = _srt_global_setup_sysroots (argv0);

  g_test_add ("/system-info/object", Fixture, NULL,
              setup, test_object, teardown);
  g_test_add ("/system-info/test_libdl", Fixture, NULL,
              setup, test_libdl, teardown);
  g_test_add ("/system-info/libraries_presence", Fixture, NULL,
              setup, libraries_presence, teardown);
  g_test_add ("/system-info/auto_expectations", Fixture, NULL,
              setup, auto_expectations, teardown);
  g_test_add ("/system-info/library_presence", Fixture, NULL,
              setup, library_presence, teardown);
  g_test_add ("/system-info/libraries_missing", Fixture, NULL,
              setup, libraries_missing, teardown);
  g_test_add ("/system-info/library_missing", Fixture, NULL,
              setup, library_missing, teardown);
  g_test_add ("/system-info/wrong_expectations", Fixture, NULL,
              setup, wrong_expectations, teardown);
  g_test_add ("/system-info/multiarch_tuples_handling", Fixture, NULL,
              setup, multiarch_tuples_handling, teardown);

  g_test_add ("/system-info/steam_runtime", Fixture, NULL,
              setup, steam_runtime, teardown);
  g_test_add ("/system-info/steam_runtime_missing", Fixture, NULL,
              setup, steam_runtime_missing, teardown);
  g_test_add ("/system-info/steam_runtime_pinned", Fixture, NULL,
              setup, steam_runtime_pinned, teardown);
  g_test_add ("/system-info/runtime_disabled_or_missing", Fixture, NULL,
              setup, runtime_disabled_or_missing, teardown);
  g_test_add ("/system-info/runtime_version", Fixture, NULL,
              setup, runtime_version, teardown);
  g_test_add ("/system-info/runtime_unexpected_location", Fixture, NULL,
              setup, runtime_unexpected_location, teardown);
  g_test_add ("/system-info/steam_symlink", Fixture, NULL,
              setup, steam_symlink, teardown);
  g_test_add ("/system-info/steam_compat_environment_variable", Fixture, NULL,
              setup, steam_compat_environment_variable, teardown);
  g_test_add ("/system-info/debian_bug_916303", Fixture, NULL,
              setup, debian_bug_916303, teardown);
  g_test_add ("/system-info/testing_beta_client", Fixture, NULL,
              setup, testing_beta_client, teardown);

  g_test_add ("/system-info/os/debian10", Fixture, NULL,
              setup, os_debian10, teardown);
  g_test_add ("/system-info/os/debian-unstable", Fixture,
              "debian-unstable",
              setup, os_debian_unstable, teardown);
  g_test_add ("/system-info/os/flatpak-on-debian-unstable", Fixture,
              "flatpak-example/run/host",
              setup, os_debian_unstable, teardown);
  g_test_add ("/system-info/os/steamrt", Fixture, NULL,
              setup, os_steamrt, teardown);
  g_test_add ("/system-info/os/steamrt-unofficial", Fixture, NULL,
              setup, os_steamrt_unofficial, teardown);
  g_test_add ("/system-info/os/invalid-os-release", Fixture, NULL,
              setup, os_invalid_os_release, teardown);
  g_test_add ("/system-info/os/no-os-release", Fixture, NULL,
              setup, os_no_os_release, teardown);

  g_test_add ("/system-info/libs/overrides", Fixture, NULL,
              setup, overrides, teardown);
  g_test_add ("/system-info/libs/overrides_issues", Fixture, NULL,
              setup, overrides_issues, teardown);
  g_test_add ("/system-info/libs/overrides_not_available", Fixture, NULL,
              setup, overrides_not_available, teardown);
  g_test_add ("/system-info/libs/pinned_libraries", Fixture, NULL,
              setup, pinned_libraries, teardown);
  g_test_add ("/system-info/libs/pinned_libraries_permission", Fixture, NULL,
              setup, pinned_libraries_permission, teardown);
  g_test_add ("/system-info/libs/pinned_libraries_missing", Fixture, NULL,
              setup, pinned_libraries_missing, teardown);

  g_test_add ("/system-info/driver_environment", Fixture, NULL,
              setup, driver_environment, teardown);
  g_test_add ("/system-info/steamscript_env", Fixture, NULL,
              setup, steamscript_env, teardown);

  g_test_add ("/system-info/json_parsing", Fixture, NULL,
              setup, json_parsing, teardown);

  g_test_add ("/system-info/architecture/symlinks", Fixture, NULL,
              setup, architecture_symlinks, teardown);
  g_test_add ("/system-info/architecture/notlinks", Fixture, NULL,
              setup, architecture_notlinks, teardown);

  status = g_test_run ();

  if (!_srt_global_teardown_private_xdg_dirs ())
    g_debug ("Unable to remove the fake home parent directory of: %s", fake_home_path);

  _srt_global_teardown_sysroots ();
  g_clear_pointer (&global_sysroots, g_free);

  return status;
}