From 6a6b36bbc7d46bb787064c792635a933ac258929 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Thu, 23 Nov 2023 12:08:14 +0000 Subject: [PATCH 1/2] gtestutils: Add g_test_trap_subprocess_with_envp() for testing envs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a variant of `g_test_trap_subprocess()` which allows the environment for the child process to be specified. This is quite useful when you want to test code which reads environment variables, as it’s not safe to set those after the start of `main()`. This will be useful within and outwith GLib for testing such code. Signed-off-by: Philip Withnall Helps: #1618 --- docs/reference/glib/glib-sections.txt.in | 1 + glib/gtestutils.c | 61 +++++++++++++++++++++--- glib/gtestutils.h | 5 ++ glib/tests/testing.c | 20 ++++++++ 4 files changed, 81 insertions(+), 6 deletions(-) diff --git a/docs/reference/glib/glib-sections.txt.in b/docs/reference/glib/glib-sections.txt.in index db8f47230..52029b861 100644 --- a/docs/reference/glib/glib-sections.txt.in +++ b/docs/reference/glib/glib-sections.txt.in @@ -3363,6 +3363,7 @@ g_test_assert_expected_messages GTestTrapFlags GTestSubprocessFlags g_test_trap_subprocess +g_test_trap_subprocess_with_envp g_test_trap_has_passed g_test_trap_reached_timeout g_test_trap_assert_passed diff --git a/glib/gtestutils.c b/glib/gtestutils.c index 9d5b1cfbb..704f5ce0e 100644 --- a/glib/gtestutils.c +++ b/glib/gtestutils.c @@ -3850,6 +3850,32 @@ G_GNUC_END_IGNORE_DEPRECATIONS * @test_flags: Flags to modify subprocess behaviour. * * Respawns the test program to run only @test_path in a subprocess. + * + * This is equivalent to calling g_test_trap_subprocess_with_envp() with `envp` + * set to %NULL. See the documentation for that function for full details. + * + * Since: 2.38 + */ +void +g_test_trap_subprocess (const char *test_path, + guint64 usec_timeout, + GTestSubprocessFlags test_flags) +{ + g_test_trap_subprocess_with_envp (test_path, NULL, usec_timeout, test_flags); +} + +/** + * g_test_trap_subprocess_with_envp: + * @test_path: (nullable): Test to run in a subprocess + * @envp: (array zero-terminated=1) (nullable) (element-type filename): Environment + * to run the test in, or %NULL to inherit the parent’s environment. This must + * be in the GLib filename encoding. + * @usec_timeout: Timeout for the subprocess test in micro seconds. + * @test_flags: Flags to modify subprocess behaviour. + * + * Respawns the test program to run only @test_path in a subprocess with the + * given @envp environment. + * * This can be used for a test case that might not return, or that * might abort. * @@ -3863,6 +3889,8 @@ G_GNUC_END_IGNORE_DEPRECATIONS * tests with names of this form will automatically be skipped in the * parent process. * + * If @envp is %NULL, the parent process’ environment will be inherited. + * * If @usec_timeout is non-0, the test subprocess is aborted and * considered failing if its run time exceeds it. * @@ -3905,23 +3933,44 @@ G_GNUC_END_IGNORE_DEPRECATIONS * g_test_trap_assert_stderr ("*ERROR*too large*"); * } * + * static void + * test_different_username (void) + * { + * if (g_test_subprocess ()) + * { + * // Code under test goes here + * g_message ("Username is now simulated as %s", g_getenv ("USER")); + * return; + * } + * + * // Reruns this same test in a subprocess + * g_autoptr(GStrv) envp = g_get_environ (); + * envp = g_environ_setenv (g_steal_pointer (&envp), "USER", "charlie", TRUE); + * g_test_trap_subprocess_with_envp (NULL, envp, 0, G_TEST_SUBPROCESS_DEFAULT); + * g_test_trap_assert_passed (); + * g_test_trap_assert_stdout ("Username is now simulated as charlie"); + * } + * * int * main (int argc, char **argv) * { * g_test_init (&argc, &argv, NULL); * - * g_test_add_func ("/myobject/create_large_object", + * g_test_add_func ("/myobject/create-large-object", * test_create_large_object); + * g_test_add_func ("/myobject/different-username", + * test_different_username); * return g_test_run (); * } * ]| * - * Since: 2.38 + * Since: 2.80 */ void -g_test_trap_subprocess (const char *test_path, - guint64 usec_timeout, - GTestSubprocessFlags test_flags) +g_test_trap_subprocess_with_envp (const char *test_path, + const char * const *envp, + guint64 usec_timeout, + GTestSubprocessFlags test_flags) { GError *error = NULL; GPtrArray *argv; @@ -3980,7 +4029,7 @@ g_test_trap_subprocess (const char *test_path, if (!g_spawn_async_with_pipes (test_initial_cwd, (char **)argv->pdata, - NULL, flags, + (char **) envp, flags, NULL, NULL, &pid, NULL, &stdout_fd, &stderr_fd, &error)) diff --git a/glib/gtestutils.h b/glib/gtestutils.h index 30ede2588..69ca9c9b7 100644 --- a/glib/gtestutils.h +++ b/glib/gtestutils.h @@ -515,6 +515,11 @@ GLIB_AVAILABLE_IN_2_38 void g_test_trap_subprocess (const char *test_path, guint64 usec_timeout, GTestSubprocessFlags test_flags); +GLIB_AVAILABLE_IN_2_80 +void g_test_trap_subprocess_with_envp (const char *test_path, + const char * const *envp, + guint64 usec_timeout, + GTestSubprocessFlags test_flags); GLIB_AVAILABLE_IN_ALL gboolean g_test_trap_has_passed (void); diff --git a/glib/tests/testing.c b/glib/tests/testing.c index acbc1be22..83aaad9bb 100644 --- a/glib/tests/testing.c +++ b/glib/tests/testing.c @@ -404,6 +404,25 @@ test_subprocess_timeout (void) g_assert_true (g_test_trap_reached_timeout ()); } +static void +test_subprocess_envp (void) +{ + char **envp = NULL; + + if (g_test_subprocess ()) + { + g_assert_cmpstr (g_getenv ("TEST_SUBPROCESS_VARIABLE"), ==, "definitely set"); + return; + } + + envp = g_get_environ (); + envp = g_environ_setenv (g_steal_pointer (&envp), "TEST_SUBPROCESS_VARIABLE", "definitely set", TRUE); + g_test_trap_subprocess_with_envp (NULL, (const gchar * const *) envp, + 0, G_TEST_SUBPROCESS_DEFAULT); + g_test_trap_assert_passed (); + g_strfreev (envp); +} + /* run a test with fixture setup and teardown */ typedef struct { guint seed; @@ -2909,6 +2928,7 @@ main (int argc, g_test_add_func ("/trap_subprocess/no-such-test", test_subprocess_no_such_test); if (g_test_slow ()) g_test_add_func ("/trap_subprocess/timeout", test_subprocess_timeout); + g_test_add_func ("/trap_subprocess/envp", test_subprocess_envp); g_test_add_func ("/trap_subprocess/patterns", test_subprocess_patterns); From f0032094d24548130ff691fd82c0227694f7de66 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Thu, 23 Nov 2023 12:09:54 +0000 Subject: [PATCH 2/2] tests: Fix use of TMPDIR in utils test All GLib tests normally respect `TMPDIR` for writing their temporary files to. The utils test, however, contained a hack which overwrote `TMPDIR` so that it could test the behaviour of `g_get_tmp_dir()` for regressions. Unfortunately, that hack affected the whole `utils` test suite, not just the one regression test. Use the new `g_test_trap_subprocess_with_envp()` API to allow the regression test to be run as a subprocess with its environment modified, which allows us to remove the hack affecting the rest of the test suite. Spotted in https://gitlab.gnome.org/GNOME/glib/-/issues/3179#note_1925161. Signed-off-by: Philip Withnall --- glib/tests/utils.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/glib/tests/utils.c b/glib/tests/utils.c index 3abef9312..e71d9a6ef 100644 --- a/glib/tests/utils.c +++ b/glib/tests/utils.c @@ -211,8 +211,27 @@ test_prgname_thread_safety (void) static void test_tmpdir (void) { + char **envp = NULL; + g_test_bug ("https://bugzilla.gnome.org/show_bug.cgi?id=627969"); - g_assert_cmpstr (g_get_tmp_dir (), !=, ""); + g_test_summary ("Test that g_get_tmp_dir() returns a correct default if TMPDIR is set to the empty string"); + + if (g_test_subprocess ()) + { + g_assert_cmpstr (g_get_tmp_dir (), !=, ""); + return; + } + + envp = g_get_environ (); + + envp = g_environ_setenv (g_steal_pointer (&envp), "TMPDIR", "", TRUE); + envp = g_environ_unsetenv (g_steal_pointer (&envp), "TMP"); + envp = g_environ_unsetenv (g_steal_pointer (&envp), "TEMP"); + + g_test_trap_subprocess_with_envp (NULL, (const gchar * const *) envp, + 0, G_TEST_SUBPROCESS_DEFAULT); + g_test_trap_assert_passed (); + g_strfreev (envp); } #if defined(__GNUC__) && (__GNUC__ >= 4) @@ -1309,11 +1328,6 @@ main (int argc, { argv0 = argv[0]; - /* for tmpdir test, need to do this early before g_get_any_init */ - g_setenv ("TMPDIR", "", TRUE); - g_unsetenv ("TMP"); - g_unsetenv ("TEMP"); - /* g_test_init() only calls g_set_prgname() if g_get_prgname() * returns %NULL, but g_get_prgname() on Windows never returns NULL. * So we need to do this by hand to make test_appname() work on