gtestutils: Add g_test_trap_subprocess_with_envp() for testing envs

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 <pwithnall@gnome.org>

Helps: #1618
This commit is contained in:
Philip Withnall 2023-11-23 12:08:14 +00:00
parent c7a02280f5
commit 6a6b36bbc7
4 changed files with 81 additions and 6 deletions

View File

@ -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

View File

@ -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 parents 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))

View File

@ -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);

View File

@ -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);