diff --git a/tests/meson.build b/tests/meson.build index 13b248dd320ab396b59e6e1cf009fe7aafebd700..3ead662eff677ed91cfb1a15f34c832311a9dd59 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -74,6 +74,7 @@ test_utils = static_library( sources : [ 'fake-home.c', 'test-init.c', + 'test-json-utils.c', 'test-utils.c', ], c_args : [ diff --git a/tests/system-info-cli.c b/tests/system-info-cli.c index a42076fb1f0d99dbcc6b139a7eaf715e3b88dd93..44ff386dbf2f43cbacb1b88fd7f208eb958046f6 100644 --- a/tests/system-info-cli.c +++ b/tests/system-info-cli.c @@ -43,6 +43,7 @@ #include <json-glib/json-glib.h> #include "test-utils.h" +#include "test-json-utils.h" #include "fake-home.h" #if defined(__i386__) || defined(__x86_64__) @@ -737,34 +738,6 @@ static const JsonTest json_test[] = }, }; -static void -stdout_to_stderr_child_setup (gpointer nil) -{ - if (dup2 (STDERR_FILENO, STDOUT_FILENO) != STDOUT_FILENO) - _srt_async_signal_safe_error ("test-system-info-cli", - "Unable to redirect stdout to stderr", - 1); -} - -static gchar * -pretty_print_json (const char *unformatted) -{ - g_autoptr(JsonParser) parser = NULL; - JsonNode *node = NULL; /* not owned */ - g_autoptr(JsonGenerator) generator = NULL; - g_autoptr(GError) error = NULL; - - parser = json_parser_new (); - json_parser_load_from_data (parser, unformatted, -1, &error); - g_assert_no_error (error); - node = json_parser_get_root (parser); - g_assert_nonnull (node); - generator = json_generator_new (); - json_generator_set_root (generator, node); - json_generator_set_pretty (generator, TRUE); - return json_generator_to_data (generator, NULL); -} - static void json_parsing (Fixture *f, gconstpointer context) @@ -782,7 +755,6 @@ json_parsing (Fixture *f, g_auto(GStrv) envp = NULL; g_autoptr(GError) error = NULL; const gchar *argv[] = { "steam-runtime-system-info", "--verbose", NULL }; - gsize len; g_test_message ("%s: input=%s output=%s", test->description, test->input_name, test->output_name); @@ -791,10 +763,7 @@ json_parsing (Fixture *f, output_json = g_build_filename (f->srcdir, "json-report", multiarch_tuples[0], test->output_name, NULL); - g_file_get_contents (output_json, &unformatted, &len, &error); - g_assert_no_error (error); - g_assert_cmpuint (len, ==, strlen (unformatted)); - expectation = pretty_print_json (unformatted); + expectation = _srt_json_pretty_from_file (output_json); g_clear_pointer (&unformatted, g_free); envp = g_get_environ (); @@ -814,51 +783,9 @@ json_parsing (Fixture *f, g_assert_true (result); g_assert_cmpint (exit_status, ==, 0); g_assert_nonnull (unformatted); - output = pretty_print_json (unformatted); - - if (g_strcmp0 (output, expectation) != 0) - { - const char *artifacts = g_getenv ("AUTOPKGTEST_ARTIFACTS"); - g_autofree gchar *tmpdir = NULL; - g_autofree gchar *expected_path = NULL; - g_autofree gchar *output_path = NULL; - const gchar *diff_argv[] = { "diff", "-u", "<expected>", "<output>", NULL }; - - if (artifacts == NULL) - { - tmpdir = g_dir_make_tmp ("srt-tests-XXXXXX", &error); - g_assert_no_error (error); - artifacts = tmpdir; - } - - expected_path = g_build_filename (artifacts, "expected.json", NULL); - g_file_set_contents (expected_path, expectation, -1, &error); - g_assert_no_error (error); - - output_path = g_build_filename (artifacts, "output.json", NULL); - g_file_set_contents (output_path, output, -1, &error); - g_assert_no_error (error); - - g_assert_cmpstr (diff_argv[2], ==, "<expected>"); - diff_argv[2] = expected_path; - g_assert_cmpstr (diff_argv[3], ==, "<output>"); - diff_argv[3] = output_path; - g_assert_null (diff_argv[4]); - - /* Ignore error from calling diff, if any: we're running it - * for its side-effect of producing output on our stderr. */ - g_spawn_sync (NULL, (gchar **) diff_argv, NULL, G_SPAWN_SEARCH_PATH, - stdout_to_stderr_child_setup, NULL, - NULL, NULL, NULL, NULL); - - g_test_message ("Output for comparison: %s %s", - expected_path, output_path); - - if (tmpdir != NULL) - _srt_rm_rf (tmpdir); - } + output = _srt_json_pretty (unformatted); - g_assert_cmpstr (output, ==, expectation); + _srt_assert_streq_diff (expectation, output); } } diff --git a/tests/test-json-utils.c b/tests/test-json-utils.c new file mode 100644 index 0000000000000000000000000000000000000000..06f9d8e98ca3964eb37b886969e28017f8c8c510 --- /dev/null +++ b/tests/test-json-utils.c @@ -0,0 +1,72 @@ +/* + * Copyright © 2019-2025 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 "tests/test-json-utils.h" + +#include <glib.h> + +#include "steam-runtime-tools/glib-backports-internal.h" +#include "steam-runtime-tools/json-glib-backports-internal.h" + +/** + * Returns a new string containing the input JSON but pretty-printed. + */ +gchar * +_srt_json_pretty (const char *unformatted) +{ + g_autoptr(JsonParser) parser = NULL; + JsonNode *node = NULL; /* not owned */ + g_autoptr(JsonGenerator) generator = NULL; + g_autoptr(GError) error = NULL; + + parser = json_parser_new (); + json_parser_load_from_data (parser, unformatted, -1, &error); + g_assert_no_error (error); + node = json_parser_get_root (parser); + g_assert_nonnull (node); + generator = json_generator_new (); + json_generator_set_root (generator, node); + json_generator_set_pretty (generator, TRUE); + return json_generator_to_data (generator, NULL); +} + +/** + * Returns the contents of the given JSON file, pretty-printed via + * _srt_json_pretty(). + **/ +gchar * +_srt_json_pretty_from_file (const char *path) +{ + g_autoptr(GError) error = NULL; + g_autofree char *contents = NULL; + gsize len; + + g_file_get_contents (path, + &contents, &len, &error); + g_assert_no_error (error); + g_assert_cmpuint (len, ==, strlen (contents)); + + return _srt_json_pretty (contents); +} diff --git a/tests/test-json-utils.h b/tests/test-json-utils.h new file mode 100644 index 0000000000000000000000000000000000000000..290b5f03fd6e4292fb4d2866ecb8f82aeb1f7b99 --- /dev/null +++ b/tests/test-json-utils.h @@ -0,0 +1,31 @@ +/* + * Copyright © 2019-2025 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. + */ + +#pragma once + +#include <glib.h> + +gchar * _srt_json_pretty (const char *unformatted); +gchar * _srt_json_pretty_from_file (const char *path); diff --git a/tests/test-utils.c b/tests/test-utils.c index 99d55776455c1b01abdb1ca3428e7ecea01229fc..f71b47c2d24f4b1db1a38cad1ae53f42ebb8b98d 100644 --- a/tests/test-utils.c +++ b/tests/test-utils.c @@ -317,3 +317,85 @@ _srt_tests_skip_if_really_in_steam_runtime (void) return FALSE; } + +/** + * Show a diff of both strings, additionally saving their contents to a + * temporary directory for later inspections. Performs a diff via `$SRT_DIFF` + * (defaults to `diff`) with the options in `$SRT_DIFF_OPTS` (defaults to `-u` + * if not given *and* `$SRT_DIFF` was unset). + */ +void +_srt_show_diff (const char *expected, + const char *actual) +{ + g_autoptr(GPtrArray) diff_argv = g_ptr_array_new (); + g_autoptr(GSubprocessLauncher) launcher = + g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDERR_MERGE); + g_autoptr(GSubprocess) diff = NULL; + g_autoptr(GError) error = NULL; + g_auto(GStrv) diff_opts_split = NULL; + g_autofree gchar *tmpdir = NULL; + g_autofree gchar *expected_path = NULL; + g_autofree gchar *actual_path = NULL; + glnx_autofd int stderr_dup = -1; + const char *artifacts = g_getenv ("AUTOPKGTEST_ARTIFACTS"); + const char *diff_exe = g_getenv ("SRT_DIFF"); + const char *diff_opts = g_getenv ("SRT_DIFF_OPTS"); + + g_assert_no_errno ((stderr_dup = dup (STDERR_FILENO))); + g_subprocess_launcher_take_stdout_fd (launcher, glnx_steal_fd (&stderr_dup)); + + if (artifacts == NULL) + { + tmpdir = g_dir_make_tmp ("srt-tests-XXXXXX", &error); + g_assert_no_error (error); + artifacts = tmpdir; + } + + expected_path = g_build_filename (artifacts, "expected", NULL); + g_file_set_contents (expected_path, expected, -1, &error); + g_assert_no_error (error); + + actual_path = g_build_filename (artifacts, "actual", NULL); + g_file_set_contents (actual_path, actual, -1, &error); + g_assert_no_error (error); + + g_ptr_array_add (diff_argv, (gpointer) (diff_exe ?: "diff")); + /* Only add custom diff opts if given; but otherwise, if no diff tool + was given, default to '-u' for easier-to-read unified diffs. */ + if (diff_opts != NULL) + { + char **p; + if (!g_shell_parse_argv (diff_opts, NULL, &diff_opts_split, &error)) + { + if (g_error_matches (error, G_SHELL_ERROR, G_SHELL_ERROR_EMPTY_STRING)) + g_clear_error (&error); + else + g_error ("Parsing $SRT_DIFF_OPTS: %s", error->message); + } + + for (p = diff_opts_split; p != NULL && *p != NULL; p++) + g_ptr_array_add (diff_argv, *p); + } + else if (diff_exe == NULL) + { + g_ptr_array_add (diff_argv, (gpointer) "-u"); + } + g_ptr_array_add (diff_argv, expected_path); + g_ptr_array_add (diff_argv, actual_path); + g_ptr_array_add (diff_argv, NULL); + + /* Ignore error from calling diff, if any: we're running it + * for its side-effect of producing output on our stderr. */ + diff = g_subprocess_launcher_spawnv (launcher, + (const char * const *) diff_argv->pdata, + NULL); + if (diff != NULL) + g_subprocess_wait (diff, NULL, NULL); + + g_test_message ("Files for comparison: %s %s", + expected_path, actual_path); + + if (tmpdir != NULL) + _srt_rm_rf (tmpdir); +} diff --git a/tests/test-utils.h b/tests/test-utils.h index 3268f8cd9af2f2f6ad752d1c09e023594cc6e68b..5585e193d9f6c2d4a1beb3ca1bd639d0264b3434 100644 --- a/tests/test-utils.h +++ b/tests/test-utils.h @@ -51,3 +51,20 @@ gchar *_srt_global_setup_sysroots (const char *argv0); gboolean _srt_global_teardown_sysroots (void); gboolean _srt_tests_skip_if_really_in_steam_runtime (void); + +void _srt_show_diff (const char *expected, + const char *actual); + +/** + * Asserts two strings as equal, showing a line-based diff of their contents via + * _srt_show_diff() if they don't match. + */ +#define _srt_assert_streq_diff(a, b) \ + G_STMT_START { \ + const char *__a = (a), *__b = (b); \ + if (!g_str_equal (__a, __b)) {\ + _srt_show_diff (__a, __b); \ + g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC,\ + "assertion failed (" #a " == " #b ")"); \ + } \ + } G_STMT_END