From 47a4c9e974180edee2c59a158103f7b34feb5ecb Mon Sep 17 00:00:00 2001
From: Ludovico de Nittis <ludovico.denittis@collabora.com>
Date: Tue, 6 Aug 2019 12:10:04 +0200
Subject: [PATCH] Enhance examples/system-info.c to show libraries and uinput
 capabilities

examples/system-info.c now prints a JSON object containing all the
available API of steam-runtime-tools.

Signed-off-by: Ludovico de Nittis <ludovico.denittis@collabora.com>
---
 examples/meson.build         |   5 +-
 examples/system-info.c       | 264 +++++++++++++++++++-
 tests/examples-system-info.c | 457 +++++++++++++++++++++++++++++++++++
 tests/meson.build            |   4 +-
 4 files changed, 715 insertions(+), 15 deletions(-)
 create mode 100644 tests/examples-system-info.c

diff --git a/examples/meson.build b/examples/meson.build
index 56d300e66..605fb8d83 100644
--- a/examples/meson.build
+++ b/examples/meson.build
@@ -24,7 +24,10 @@
 executable(
   'steam-runtime-system-info',
   'system-info.c',
-  dependencies : [glib, libsteamrt_dep],
+  dependencies : [glib, libsteamrt_dep, json_glib],
+  c_args : [
+      '-D_GNU_SOURCE',
+  ],
   install : true,
 )
 
diff --git a/examples/system-info.c b/examples/system-info.c
index aca0ed52c..2156213c1 100644
--- a/examples/system-info.c
+++ b/examples/system-info.c
@@ -27,30 +27,268 @@
  * Output basic information about the system on which the tool is run.
  * The output is a JSON object with the following keys:
  *
- * can-run:
- *   An object. The keys are strings representing multiarch tuples, as
- *   used in Debian and the freedesktop.org SDK runtime. The values are
- *   boolean: true if we can definitely run executables of this
- *   architecture, or false if we cannot prove that we can do so.
+ * can-write-uinput:
+ *   The values are boolean: true if we can write to `/dev/uninput`,
+ *   false if we are not able to do it.
+ *
+ * architectures:
+ *   An object. The keys are multiarch tuples like %SRT_ABI_I386,
+ *   as used in Debian and the freedesktop.org SDK runtime.
+ *   The values are objects with more details of the architecture:
+ *
+ *   can-run:
+ *     The values are boolean: true if we can definitely run
+ *     executables of this architecture, or false if we cannot prove
+ *     that we can do so.
+ *
+ *   library-issues-summary:
+ *     A string array listing all the libraries problems that has been found
+ *     in the running system. Possible values can be: "cannot-load",
+ *     "missing-symbols", "misversioned-symbols" and "internal-error".
+ *     If "can-run" for this architecture is false we skip the library check
+ *     and this "library-issues-summary" will not be printed at all.
+ *
+ *   library-details:
+ *     An object. The keys are library SONAMEs, such as `libjpeg.so.62`.
+ *     The values are objects with more details of the library:
+ *
+ *     path:
+ *       The value is a string representing the full path about where the
+ *       @library has been found. The value is `null` if the library is
+ *       not available in the system.
+ *
+ *     issues:
+ *       A string array listing all the @library problems that has been
+ *       found in the running system. For possible values check
+ *       `library-issues-summary`. This object will be available only if
+ *       there are some issues.
+ *
+ *     missing-symbols:
+ *       A string array listing all the symbols that were expected to be
+ *       provided by @library but were not found. This object will be
+ *       available only if there are sone missing symbols.
+ *
+ *     misversioned-symbols:
+ *       A string array listing all the symbols that were expected to be
+ *       provided by @library but were available with a different version.
+ *       This object will be available only if there are some misversioned
+ *       symbols.
  */
 
 #include <steam-runtime-tools/steam-runtime-tools.h>
 
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
 #include <glib.h>
 
+#include <json-glib/json-glib.h>
+
+enum
+{
+  OPTION_HELP = 1,
+  OPTION_EXPECTATION,
+  OPTION_VERBOSE,
+};
+
+struct option long_options[] =
+{
+    { "expectations", required_argument, NULL, OPTION_EXPECTATION },
+    { "verbose", no_argument, NULL, OPTION_VERBOSE },
+    { "help", no_argument, NULL, OPTION_HELP },
+    { NULL, 0, NULL, 0 }
+};
+
+static void usage (int code) __attribute__((__noreturn__));
+
+/*
+ * Print usage information and exit with status @code.
+ */
+static void
+usage (int code)
+{
+  FILE *fp;
+
+  if (code == 0)
+    fp = stdout;
+  else
+    fp = stderr;
+
+  fprintf (fp, "Usage: %s [OPTIONS]\n",
+           program_invocation_short_name);
+  exit (code);
+}
+
+static void
+jsonify_library_issues (JsonBuilder *builder,
+                        SrtLibraryIssues issues)
+{
+  if ((issues & SRT_LIBRARY_ISSUES_CANNOT_LOAD) != 0)
+    json_builder_add_string_value (builder, "cannot-load");
+
+  if ((issues & SRT_LIBRARY_ISSUES_MISSING_SYMBOLS) != 0)
+    json_builder_add_string_value (builder, "missing-symbols");
+
+  if ((issues & SRT_LIBRARY_ISSUES_MISVERSIONED_SYMBOLS) != 0)
+    json_builder_add_string_value (builder, "misversioned-symbols");
+
+  if ((issues & SRT_LIBRARY_ISSUES_INTERNAL_ERROR) != 0)
+    json_builder_add_string_value (builder, "internal-error");
+}
+
+static void
+print_libraries_details (JsonBuilder *builder,
+                         GList *libraries,
+                         gboolean verbose)
+{
+  json_builder_set_member_name (builder, "library-details");
+  json_builder_begin_object (builder);
+  for (GList *l = libraries; l != NULL; l = l->next)
+    {
+      if (verbose || srt_library_get_issues (l->data) != SRT_LIBRARY_ISSUES_NONE)
+        {
+          const char * const *missing_symbols;
+          const char * const *misversioned_symbols;
+          json_builder_set_member_name (builder, srt_library_get_soname (l->data));
+          json_builder_begin_object (builder);
+          json_builder_set_member_name (builder, "path");
+          json_builder_add_string_value (builder, srt_library_get_absolute_path (l->data));
+
+          if (srt_library_get_issues (l->data) != SRT_LIBRARY_ISSUES_NONE)
+            {
+              json_builder_set_member_name (builder, "issues");
+              json_builder_begin_array (builder);
+              jsonify_library_issues (builder, srt_library_get_issues (l->data));
+              json_builder_end_array (builder);
+            }
+
+          missing_symbols = srt_library_get_missing_symbols (l->data);
+          if (missing_symbols[0] != NULL)
+            {
+              json_builder_set_member_name (builder, "missing-symbols");
+              json_builder_begin_array (builder);
+              for (gsize i = 0; missing_symbols[i] != NULL; i++)
+                json_builder_add_string_value (builder, missing_symbols[i]);
+              json_builder_end_array (builder);
+            }
+
+          misversioned_symbols = srt_library_get_misversioned_symbols (l->data);
+          if (misversioned_symbols[0] != NULL)
+            {
+              json_builder_set_member_name (builder, "misversioned-symbols");
+              json_builder_begin_array (builder);
+              for (gsize i = 0; misversioned_symbols[i] != NULL; i++)
+                json_builder_add_string_value (builder, misversioned_symbols[i]);
+              json_builder_end_array (builder);
+            }
+
+          json_builder_end_object (builder);
+        }
+    }
+  json_builder_end_object (builder);
+
+  return;
+}
+
 int
 main (int argc,
       char **argv)
 {
-  g_print ("{\n");
+  SrtSystemInfo *info;
+  SrtLibraryIssues issues = SRT_LIBRARY_ISSUES_NONE;
+  char *expectations = NULL;
+  gboolean verbose = FALSE;
+  JsonBuilder *builder;
+  JsonGenerator *generator;
+  gboolean can_run = FALSE;
+  gchar *json_output;
+  GList *libraries = NULL;
+  GList *detailed_errors = NULL;
+  int opt;
+  static const char * const multiarch_tuples[] = { SRT_ABI_I386, SRT_ABI_X86_64 };
+
+  while ((opt = getopt_long (argc, argv, "", long_options, NULL)) != -1)
+    {
+      switch (opt)
+        {
+          case OPTION_EXPECTATION:
+            expectations = optarg;
+            break;
+
+          case OPTION_VERBOSE:
+            verbose = TRUE;
+            break;
+
+          case OPTION_HELP:
+            printf ("HELP\n");
+            usage (0);
+            break;
+
+          case '?':
+          default:
+            printf ("DEFAULT\n");
+            usage (1);
+            break;  /* not reached */
+        }
+    }
+
+  if (optind != argc)
+    usage (1);
+
+  info = srt_system_info_new (expectations);
+
+  builder = json_builder_new ();
+  json_builder_begin_object (builder);
+
+  json_builder_set_member_name (builder, "can-write-uinput");
+  json_builder_add_boolean_value (builder, srt_system_info_can_write_to_uinput (info));
+
+  json_builder_set_member_name (builder, "architectures");
+  json_builder_begin_object (builder);
+
+  for (gsize i = 0; i < G_N_ELEMENTS (multiarch_tuples); i++)
+    {
+      json_builder_set_member_name (builder, multiarch_tuples[i]);
+      json_builder_begin_object (builder);
+      json_builder_set_member_name (builder, "can-run");
+      can_run = srt_system_info_can_run (info, multiarch_tuples[i]);
+      json_builder_add_boolean_value (builder, can_run);
+
+      if (can_run && expectations != NULL)
+        {
+          json_builder_set_member_name (builder, "library-issues-summary");
+          json_builder_begin_array (builder);
+          issues = srt_system_info_check_libraries (info,
+                                                    multiarch_tuples[i],
+                                                    &libraries);
+          jsonify_library_issues (builder, issues);
+          json_builder_end_array (builder);
+        }
+
+      if (libraries != NULL && (issues != SRT_LIBRARY_ISSUES_NONE || verbose))
+          print_libraries_details (builder, libraries, verbose);
+
+      json_builder_end_object (builder);
+      g_list_free_full (detailed_errors, g_free);
+      g_list_free_full (libraries, g_object_unref);
+    }
+
+  json_builder_end_object (builder);
+  json_builder_end_object (builder);
+
+  JsonNode *root = json_builder_get_root (builder);
+  generator = json_generator_new ();
+  json_generator_set_root (generator, root);
+  json_generator_set_pretty (generator, TRUE);
+  json_output = json_generator_to_data (generator, NULL);
+  g_print ("%s", json_output);
 
-  g_print ("  \"can-run\": {\n");
-  g_print ("    \"%s\": %s,\n", SRT_ABI_I386,
-           srt_architecture_can_run_i386 () ? "true" : "false");
-  g_print ("    \"%s\": %s\n", SRT_ABI_X86_64,
-           srt_architecture_can_run_x86_64 () ? "true" : "false");
-  g_print ("  }\n");
+  g_free (json_output);
+  g_object_unref (generator);
+  json_node_free (root);
+  g_object_unref (builder);
+  g_object_unref (info);
 
-  g_print ("}\n");
   return 0;
 }
diff --git a/tests/examples-system-info.c b/tests/examples-system-info.c
new file mode 100644
index 000000000..2a330d5dd
--- /dev/null
+++ b/tests/examples-system-info.c
@@ -0,0 +1,457 @@
+/*
+ * 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 <steam-runtime-tools/steam-runtime-tools.h>
+
+#include <steam-runtime-tools/glib-compat.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include <fcntl.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <json-glib/json-glib.h>
+
+#include "test-utils.h"
+
+static const char *argv0;
+static const char * const multiarch_tuples[] = { SRT_ABI_I386, SRT_ABI_X86_64 };
+
+typedef struct
+{
+  gchar *srcdir;
+  gchar *builddir;
+} 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);
+}
+
+static void
+teardown (Fixture *f,
+          gconstpointer context)
+{
+  G_GNUC_UNUSED const Config *config = context;
+
+  g_free (f->srcdir);
+  g_free (f->builddir);
+}
+
+/*
+ * Test if the expected libraries are available in the
+ * running system.
+ */
+static void
+libraries_presence (Fixture *f,
+                    gconstpointer context)
+{
+  int exit_status = -1;
+  JsonParser *parser = NULL;
+  JsonNode *node = NULL;
+  JsonObject *json;
+  JsonObject *json_arch;
+  GError *error = NULL;
+  gchar *output = NULL;
+  gchar *examples = NULL;
+  SrtSystemInfo *info = srt_system_info_new (NULL);
+  gchar *expectations_in = g_build_filename (f->srcdir, "expectations", NULL);
+  const gchar *argv[] =
+    {
+      "steam-runtime-system-info", "--expectations", expectations_in, NULL
+    };
+
+  g_assert_true (g_spawn_sync (NULL,    /* working directory */
+                               (gchar **) argv,
+                               NULL,    /* envp */
+                               G_SPAWN_SEARCH_PATH,
+                               NULL,    /* child setup */
+                               NULL,    /* user data */
+                               &output, /* stdout */
+                               NULL,    /* stderr */
+                               &exit_status,
+                               &error));
+  g_assert_cmpint (exit_status, ==, 0);
+  g_assert_null (error);
+  g_assert_nonnull (output);
+
+  /* We can't use `json_from_string()` directly because we are targeting an
+   * older json-glib version */
+  parser = json_parser_new ();
+  g_assert_true (json_parser_load_from_data (parser, output, -1, NULL));
+  node = json_parser_get_root (parser);
+  json = json_node_get_object (node);
+
+  g_assert_true (json_object_has_member (json, "can-write-uinput"));
+
+  g_assert_true (json_object_has_member (json, "architectures"));
+  json = json_object_get_object_member (json, "architectures");
+
+  for (gsize i = 0; i < G_N_ELEMENTS (multiarch_tuples); i++)
+    {
+      g_assert_true (json_object_has_member (json, multiarch_tuples[i]));
+      json_arch = json_object_get_object_member (json, multiarch_tuples[i]);
+      g_assert_true (json_object_has_member (json_arch, "can-run"));
+      g_assert_cmpint (json_object_get_boolean_member (json_arch, "can-run"),
+                      ==, srt_system_info_can_run (info, multiarch_tuples[i]));
+      g_assert_cmpint (json_object_has_member (json_arch, "library-issues-summary"),
+                      ==, srt_system_info_can_run (info, multiarch_tuples[i]));
+      if (json_object_has_member (json_arch, "library-issues-summary"))
+        {
+          JsonArray *array;
+          array = json_object_get_array_member (json_arch, "library-issues-summary");
+          g_assert_cmpint (json_array_get_length (array), ==, 0);
+        }
+    }
+  
+  g_object_unref (parser);
+  g_object_unref (info);
+  g_free (examples);
+  g_free (expectations_in);
+  g_free (output);
+  g_clear_error (&error);
+}
+
+static void
+check_libraries_missing (JsonObject *json_arch)
+{
+  JsonArray *array;
+  JsonObject *json_details;
+  JsonObject *json_lib;
+  if (json_object_has_member (json_arch, "library-issues-summary"))
+    {
+      array = json_object_get_array_member (json_arch, "library-issues-summary");
+      g_assert_cmpint (json_array_get_length (array), ==, 3);
+      g_assert_cmpstr (json_array_get_string_element (array, 0), ==, "cannot-load");
+      g_assert_cmpstr (json_array_get_string_element (array, 1), ==, "missing-symbols");
+      g_assert_cmpstr (json_array_get_string_element (array, 2), ==, "misversioned-symbols");
+
+      g_assert_true (json_object_has_member (json_arch, "library-details"));
+      json_details = json_object_get_object_member (json_arch, "library-details");
+
+      g_assert_true (json_object_has_member (json_details, "libgio-MISSING-2.0.so.0"));
+      json_lib = json_object_get_object_member (json_details, "libgio-MISSING-2.0.so.0");
+      g_assert_true (json_object_has_member (json_lib, "path"));
+      g_assert_cmpstr (json_object_get_string_member (json_lib, "path"), ==, NULL);
+      g_assert_true (json_object_has_member (json_lib, "issues"));
+      array = json_object_get_array_member (json_lib, "issues");
+      g_assert_cmpint (json_array_get_length (array), ==, 1);
+      g_assert_cmpstr (json_array_get_string_element (array, 0), ==, "cannot-load");
+      g_assert_false (json_object_has_member (json_lib, "missing-symbols"));
+      g_assert_false (json_object_has_member (json_lib, "misversioned-symbols"));
+
+      g_assert_true (json_object_has_member (json_details, "libz.so.1"));
+      json_lib = json_object_get_object_member (json_details, "libz.so.1");
+      g_assert_true (json_object_has_member (json_lib, "path"));
+      g_assert_cmpstr (json_object_get_string_member (json_lib, "path"), !=, NULL);
+      g_assert_true (json_object_has_member (json_lib, "issues"));
+      array = json_object_get_array_member (json_lib, "issues");
+      g_assert_cmpint (json_array_get_length (array), ==, 2);
+      g_assert_cmpstr (json_array_get_string_element (array, 0), ==, "missing-symbols");
+      g_assert_cmpstr (json_array_get_string_element (array, 1), ==, "misversioned-symbols");
+      array = json_object_get_array_member (json_lib, "missing-symbols");
+      g_assert_cmpint (json_array_get_length (array), ==, 1);
+      g_assert_cmpstr (json_array_get_string_element (array, 0), ==, "missing@NotAvailable");
+      array = json_object_get_array_member (json_lib, "misversioned-symbols");
+      g_assert_cmpint (json_array_get_length (array), ==, 1);
+      g_assert_cmpstr (json_array_get_string_element (array, 0), ==, "crc32@WRONG_VERSION");
+    }
+}
+
+/*
+ * Test libraries that are either not available or with missing and
+ * misversioned symbols.
+ */
+static void
+libraries_missing (Fixture *f,
+                   gconstpointer context)
+{
+  int exit_status = -1;
+  JsonParser *parser = NULL;
+  JsonNode *node = NULL;
+  JsonObject *json;
+  JsonObject *json_arch;
+  GError *error = NULL;
+  gchar *output = NULL;
+  gchar *examples = NULL;
+  SrtSystemInfo *info = srt_system_info_new (NULL);
+  gchar *expectations_in = g_build_filename (f->srcdir, "expectations_with_missings", NULL);
+  const gchar *argv[] =
+    {
+      "steam-runtime-system-info", "--expectations", expectations_in, NULL
+    };
+
+  g_assert_true (g_spawn_sync (NULL,    /* working directory */
+                               (gchar **) argv,
+                               NULL,    /* envp */
+                               G_SPAWN_SEARCH_PATH,
+                               NULL,    /* child setup */
+                               NULL,    /* user data */
+                               &output, /* stdout */
+                               NULL,    /* stderr */
+                               &exit_status,
+                               &error));
+  g_assert_cmpint (exit_status, ==, 0);
+  g_assert_null (error);
+  g_assert_nonnull (output);
+
+  /* We can't use `json_from_string()` directly because we are targeting an
+   * older json-glib version */
+  parser = json_parser_new ();
+  g_assert_true (json_parser_load_from_data (parser, output, -1, NULL));
+  node = json_parser_get_root (parser);
+  json = json_node_get_object (node);
+
+  g_assert_true (json_object_has_member (json, "can-write-uinput"));
+  
+  g_assert_true (json_object_has_member (json, "architectures"));
+  json = json_object_get_object_member (json, "architectures");
+
+  for (gsize i = 0; i < G_N_ELEMENTS (multiarch_tuples); i++)
+    {
+      g_assert_true (json_object_has_member (json, multiarch_tuples[i]));
+      json_arch = json_object_get_object_member (json, multiarch_tuples[i]);
+      g_assert_true (json_object_has_member (json_arch, "can-run"));
+      g_assert_cmpint (json_object_get_boolean_member (json_arch, "can-run"),
+                      ==, srt_system_info_can_run (info, multiarch_tuples[i]));
+      g_assert_cmpint (json_object_has_member (json_arch, "library-issues-summary"),
+                      ==, srt_system_info_can_run (info, multiarch_tuples[i]));
+
+      check_libraries_missing (json_arch);
+    }
+  
+  g_object_unref (parser);
+  g_object_unref (info);
+  g_free (examples);
+  g_free (expectations_in);
+  g_free (output);
+  g_clear_error (&error);
+}
+
+static void
+check_library_no_errors (JsonObject *json_details,
+                         const char *library_soname)
+{
+  JsonObject *json_lib;
+
+  g_assert_true (json_object_has_member (json_details, library_soname));
+  json_lib = json_object_get_object_member (json_details, library_soname);
+  g_assert_true (json_object_has_member (json_lib, "path"));
+  g_assert_cmpstr (json_object_get_string_member (json_lib, "path"), !=, NULL);
+  g_assert_false (json_object_has_member (json_lib, "issues"));
+  g_assert_false (json_object_has_member (json_lib, "missing-symbols"));
+  g_assert_false (json_object_has_member (json_lib, "misversioned-symbols"));
+}
+
+static void
+check_libraries_verbose (JsonObject *json_arch)
+{
+  JsonArray *array;
+  JsonObject *json_details;
+  if (json_object_has_member (json_arch, "library-issues-summary"))
+    {
+      array = json_object_get_array_member (json_arch, "library-issues-summary");
+      g_assert_cmpint (json_array_get_length (array), ==, 0);
+
+      g_assert_true (json_object_has_member (json_arch, "library-details"));
+      json_details = json_object_get_object_member (json_arch, "library-details");
+
+      check_library_no_errors (json_details, "libgio-2.0.so.0");
+
+      check_library_no_errors (json_details, "libglib-2.0.so.0");
+
+      check_library_no_errors (json_details, "libz.so.1");
+    }
+}
+
+/*
+ * Test the presence of libraries with the verbose option
+ */
+static void
+libraries_presence_verbose (Fixture *f,
+                            gconstpointer context)
+{
+  int exit_status = -1;
+  JsonParser *parser = NULL;
+  JsonNode *node = NULL;
+  JsonObject *json;
+  JsonObject *json_arch;
+  GError *error = NULL;
+  gchar *output = NULL;
+  gchar *examples = NULL;
+  SrtSystemInfo *info = srt_system_info_new (NULL);
+  gchar *expectations_in = g_build_filename (f->srcdir, "expectations", NULL);
+  const gchar *argv[] =
+    {
+      "steam-runtime-system-info", "--expectations", expectations_in, "--verbose", NULL
+    };
+
+  g_assert_true (g_spawn_sync (NULL,    /* working directory */
+                               (gchar **) argv,
+                               NULL,    /* envp */
+                               G_SPAWN_SEARCH_PATH,
+                               NULL,    /* child setup */
+                               NULL,    /* user data */
+                               &output, /* stdout */
+                               NULL,    /* stderr */
+                               &exit_status,
+                               &error));
+  g_assert_cmpint (exit_status, ==, 0);
+  g_assert_null (error);
+  g_assert_nonnull (output);
+
+  /* We can't use `json_from_string()` directly because we are targeting an
+   * older json-glib version */
+  parser = json_parser_new ();
+  g_assert_true (json_parser_load_from_data (parser, output, -1, NULL));
+  node = json_parser_get_root (parser);
+  json = json_node_get_object (node);
+
+  g_assert_true (json_object_has_member (json, "can-write-uinput"));
+  
+  g_assert_true (json_object_has_member (json, "architectures"));
+  json = json_object_get_object_member (json, "architectures");
+
+  for (gsize i = 0; i < G_N_ELEMENTS (multiarch_tuples); i++)
+    {
+      g_assert_true (json_object_has_member (json, multiarch_tuples[i]));
+      json_arch = json_object_get_object_member (json, multiarch_tuples[i]);
+      g_assert_true (json_object_has_member (json_arch, "can-run"));
+      g_assert_cmpint (json_object_get_boolean_member (json_arch, "can-run"),
+                      ==, srt_system_info_can_run (info, multiarch_tuples[i]));
+      g_assert_cmpint (json_object_has_member (json_arch, "library-issues-summary"),
+                      ==, srt_system_info_can_run (info, multiarch_tuples[i]));
+
+      check_libraries_verbose (json_arch);
+    }
+  
+  g_object_unref (parser);
+  g_object_unref (info);
+  g_free (examples);
+  g_free (expectations_in);
+  g_free (output);
+  g_clear_error (&error);
+}
+
+/*
+ * Test `steam-runtime-system-info` with no additional arguments
+ */
+static void
+no_arguments (Fixture *f,
+              gconstpointer context)
+{
+  int exit_status = -1;
+  JsonParser *parser = NULL;
+  JsonNode *node = NULL;
+  JsonObject *json;
+  JsonObject *json_arch;
+  GError *error = NULL;
+  gchar *output = NULL;
+  gchar *examples = NULL;
+  SrtSystemInfo *info = srt_system_info_new (NULL);
+  gchar *expectations_in = g_build_filename (f->srcdir, "expectations", NULL);
+  const gchar *argv[] = { "steam-runtime-system-info", NULL };
+
+  g_assert_true (g_spawn_sync (NULL,    /* working directory */
+                               (gchar **) argv,
+                               NULL,    /* envp */
+                               G_SPAWN_SEARCH_PATH,
+                               NULL,    /* child setup */
+                               NULL,    /* user data */
+                               &output, /* stdout */
+                               NULL,    /* stderr */
+                               &exit_status,
+                               &error));
+  g_assert_cmpint (exit_status, ==, 0);
+  g_assert_null (error);
+  g_assert_nonnull (output);
+
+  /* We can't use `json_from_string()` directly because we are targeting an
+   * older json-glib version */
+  parser = json_parser_new ();
+  g_assert_true (json_parser_load_from_data (parser, output, -1, NULL));
+  node = json_parser_get_root (parser);
+  json = json_node_get_object (node);
+
+  g_assert_true (json_object_has_member (json, "can-write-uinput"));
+  
+  g_assert_true (json_object_has_member (json, "architectures"));
+  json = json_object_get_object_member (json, "architectures");
+
+  for (gsize i = 0; i < G_N_ELEMENTS (multiarch_tuples); i++)
+    {
+      g_assert_true (json_object_has_member (json, multiarch_tuples[i]));
+      json_arch = json_object_get_object_member (json, multiarch_tuples[i]);
+      g_assert_true (json_object_has_member (json_arch, "can-run"));
+      g_assert_cmpint (json_object_get_boolean_member (json_arch, "can-run"),
+                      ==, srt_system_info_can_run (info, multiarch_tuples[i]));
+    }
+  
+  g_object_unref (parser);
+  g_object_unref (info);
+  g_free (examples);
+  g_free (expectations_in);
+  g_free (output);
+  g_clear_error (&error);
+}
+
+int
+main (int argc,
+      char **argv)
+{
+  argv0 = argv[0];
+
+  g_test_init (&argc, &argv, NULL);
+  g_test_add ("/system-info/libraries_presence", Fixture, NULL,
+              setup, libraries_presence, teardown);
+  g_test_add ("/system-info/libraries_missing", Fixture, NULL,
+              setup, libraries_missing, teardown);
+  g_test_add ("/system-info/libraries_presence_verbose", Fixture, NULL,
+              setup, libraries_presence_verbose, teardown);
+  g_test_add ("/system-info/no_arguments", Fixture, NULL,
+              setup, no_arguments, teardown);
+
+  return g_test_run ();
+}
diff --git a/tests/meson.build b/tests/meson.build
index 5ff5cf52e..4a9d6e0d3 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -25,9 +25,11 @@ test_env = environment()
 test_env.set('G_TEST_SRCDIR', meson.current_source_dir())
 test_env.set('G_TEST_BUILDDIR', meson.current_build_dir())
 test_env.set('SRT_HELPERS_PATH', join_paths(meson.current_build_dir(), '..', 'helpers'))
+test_env.prepend('PATH', join_paths(meson.current_build_dir(), '..', 'examples'))
 
 tests = [
   'architecture',
+  'examples-system-info',
   'library',
   'system-info',
 ]
@@ -53,7 +55,7 @@ foreach test_name : tests
     c_args : [
       '-D_SRT_MULTIARCH="' + multiarch + '"',
     ],
-    dependencies : [glib, gobject, libsteamrt_dep],
+    dependencies : [glib, gobject, libsteamrt_dep, json_glib],
     install : get_option('installed_tests'),
     install_dir : tests_dir,
   )
-- 
GitLab