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