SDL: Add more SDL_Process tests

From 7241dd9ec3585db5a8ca6b98cba205941788febe Mon Sep 17 00:00:00 2001
From: Anonymous Maarten <[EMAIL REDACTED]>
Date: Sat, 14 Sep 2024 17:58:07 +0200
Subject: [PATCH] Add more SDL_Process tests

---
 test/CMakeLists.txt |  17 +-
 test/childprocess.c | 131 ++++----
 test/testprocess.c  | 786 +++++++++++++++++++++++++++++++++++---------
 3 files changed, 724 insertions(+), 210 deletions(-)

diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index f6676d3203d99..a9def98fe422c 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -368,13 +368,6 @@ add_sdl_test_executable(testmouse SOURCES testmouse.c)
 add_sdl_test_executable(testoverlay NEEDS_RESOURCES TESTUTILS SOURCES testoverlay.c)
 add_sdl_test_executable(testplatform NONINTERACTIVE SOURCES testplatform.c)
 add_sdl_test_executable(testpower NONINTERACTIVE SOURCES testpower.c)
-add_sdl_test_executable(testprocess
-                        NONINTERACTIVE THREADS
-                        NONINTERACTIVE_ARGS $<TARGET_FILE:childprocess>
-                        INSTALLED_ARGS "${CMAKE_INSTALL_FULL_LIBEXECDIR}/installed-tests/SDL3/childprocess${CMAKE_EXECUTABLE_SUFFIX}"
-                        SOURCES testprocess.c)
-add_sdl_test_executable(childprocess SOURCES childprocess.c)
-add_dependencies(testprocess childprocess)
 add_sdl_test_executable(testfilesystem NONINTERACTIVE SOURCES testfilesystem.c)
 if(WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 4)
     add_sdl_test_executable(pretest SOURCES pretest.c NONINTERACTIVE NONINTERACTIVE_TIMEOUT 60)
@@ -414,6 +407,16 @@ add_sdl_test_executable(testtime SOURCES testtime.c)
 add_sdl_test_executable(testmanymouse SOURCES testmanymouse.c)
 add_sdl_test_executable(testmodal SOURCES testmodal.c)
 
+
+add_sdl_test_executable(testprocess
+    NONINTERACTIVE THREADS
+    NONINTERACTIVE_ARGS $<TARGET_FILE:childprocess>
+    INSTALLED_ARGS "${CMAKE_INSTALL_FULL_LIBEXECDIR}/installed-tests/SDL3/childprocess${CMAKE_EXECUTABLE_SUFFIX}"
+    SOURCES testprocess.c
+)
+add_sdl_test_executable(childprocess SOURCES childprocess.c)
+add_dependencies(testprocess childprocess)
+
 if (HAVE_WAYLAND)
     # Set the GENERATED property on the protocol file, since it is first created at build time
     set_property(SOURCE ${SDL3_BINARY_DIR}/wayland-generated-protocols/xdg-shell-protocol.c PROPERTY GENERATED 1)
diff --git a/test/childprocess.c b/test/childprocess.c
index bed765dc076c4..e30ba52e912c9 100644
--- a/test/childprocess.c
+++ b/test/childprocess.c
@@ -5,15 +5,20 @@
 #include <stdio.h>
 #include <errno.h>
 
+#ifdef SDL_PLATFORM_WINDOWS
+#include <windows.h>
+#else
+#include <fcntl.h>
+#include <unistd.h>
+#endif
 
 int main(int argc, char *argv[]) {
     SDLTest_CommonState *state;
     int i;
-    const char *expect_environment = NULL;
-    bool expect_environment_match = false;
     bool print_arguments = false;
     bool print_environment = false;
     bool stdin_to_stdout = false;
+    bool read_stdin = false;
     bool stdin_to_stderr = false;
     int exit_code = 0;
 
@@ -21,50 +26,62 @@ int main(int argc, char *argv[]) {
 
     for (i = 1; i < argc;) {
         int consumed = SDLTest_CommonArg(state, i);
-        if (SDL_strcmp(argv[i], "--print-arguments") == 0) {
-            print_arguments = true;
-            consumed = 1;
-        } else if (SDL_strcmp(argv[i], "--print-environment") == 0) {
-            print_environment = true;
-            consumed = 1;
-        } else if (SDL_strcmp(argv[i], "--expect-env") == 0) {
-            if (i + 1 < argc) {
-                expect_environment = argv[i + 1];
-                consumed = 2;
-            }
-        } else if (SDL_strcmp(argv[i], "--stdin-to-stdout") == 0) {
-            stdin_to_stdout = true;
-            consumed = 1;
-        } else if (SDL_strcmp(argv[i], "--stdin-to-stderr") == 0) {
-            stdin_to_stderr = true;
-            consumed = 1;
-        } else if (SDL_strcmp(argv[i], "--stdout") == 0) {
-            if (i + 1 < argc) {
-                fprintf(stdout, "%s", argv[i + 1]);
-                consumed = 2;
-            }
-        } else if (SDL_strcmp(argv[i], "--stderr") == 0) {
-            if (i + 1 < argc) {
-                fprintf(stderr, "%s", argv[i + 1]);
-                consumed = 2;
-            }
-        } else if (SDL_strcmp(argv[i], "--exit-code") == 0) {
-            if (i + 1 < argc) {
-                char *endptr = NULL;
-                exit_code = SDL_strtol(argv[i + 1], &endptr, 0);
-                if (endptr && *endptr == '\0') {
+        if (!consumed) {
+            if (SDL_strcmp(argv[i], "--print-arguments") == 0) {
+                print_arguments = true;
+                consumed = 1;
+            } else if (SDL_strcmp(argv[i], "--print-environment") == 0) {
+                print_environment = true;
+                consumed = 1;
+            } else if (SDL_strcmp(argv[i], "--stdin-to-stdout") == 0) {
+                stdin_to_stdout = true;
+                consumed = 1;
+            } else if (SDL_strcmp(argv[i], "--stdin-to-stderr") == 0) {
+                stdin_to_stderr = true;
+                consumed = 1;
+            } else if (SDL_strcmp(argv[i], "--stdin") == 0) {
+                read_stdin = true;
+                consumed = 1;
+            } else if (SDL_strcmp(argv[i], "--stdout") == 0) {
+                if (i + 1 < argc) {
+                    fprintf(stdout, "%s", argv[i + 1]);
                     consumed = 2;
                 }
+            } else if (SDL_strcmp(argv[i], "--stderr") == 0) {
+                if (i + 1 < argc) {
+                    fprintf(stderr, "%s", argv[i + 1]);
+                    consumed = 2;
+                }
+            } else if (SDL_strcmp(argv[i], "--exit-code") == 0) {
+                if (i + 1 < argc) {
+                    char *endptr = NULL;
+                    exit_code = SDL_strtol(argv[i + 1], &endptr, 0);
+                    if (endptr && *endptr == '\0') {
+                        consumed = 2;
+                    }
+                }
+            } else if (SDL_strcmp(argv[i], "--version") == 0) {
+                int version = SDL_GetVersion();
+                fprintf(stdout, "SDL version %d.%d.%d",
+                    SDL_VERSIONNUM_MAJOR(version),
+                    SDL_VERSIONNUM_MINOR(version),
+                    SDL_VERSIONNUM_MICRO(version));
+                fprintf(stderr, "SDL version %d.%d.%d",
+                    SDL_VERSIONNUM_MAJOR(version),
+                    SDL_VERSIONNUM_MINOR(version),
+                    SDL_VERSIONNUM_MICRO(version));
+                consumed = 1;
+                break;
+            } else if (SDL_strcmp(argv[i], "--") == 0) {
+                i++;
+                break;
             }
-        } else if (SDL_strcmp(argv[i], "--") == 0) {
-            i++;
-            break;
         }
         if (consumed <= 0) {
             const char *args[] = {
                 "[--print-arguments]",
                 "[--print-environment]",
-                "[--expect-env KEY=VAL]",
+                "[--stdin]",
                 "[--stdin-to-stdout]",
                 "[--stdout TEXT]",
                 "[--stdin-to-stderr]",
@@ -86,48 +103,52 @@ int main(int argc, char *argv[]) {
         }
     }
 
-    if (print_environment || expect_environment) {
+    if (print_environment) {
         char **env = SDL_GetEnvironmentVariables(SDL_GetEnvironment());
         if (env) {
             for (i = 0; env[i]; ++i) {
-                if (print_environment) {
-                    fprintf(stdout, "%s\n", env[i]);
-                }
-                if (expect_environment) {
-                    expect_environment_match |= SDL_strcmp(env[i], expect_environment) == 0;
-                }
+                fprintf(stdout, "%s\n", env[i]);
             }
             SDL_free(env);
         }
     }
 
-    if (stdin_to_stdout || stdin_to_stderr) {
+#ifdef SDL_PLATFORM_WINDOWS
+    {
+        DWORD mode;
+        HANDLE stdout_handle = GetStdHandle(STD_INPUT_HANDLE);
+        GetConsoleMode(stdout_handle, &mode);
+        SetConsoleMode(stdout_handle, mode & ~(ENABLE_LINE_INPUT));
+    }
+#else
+    fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) & ~(O_NONBLOCK));
+#endif
 
+    if (stdin_to_stdout || stdin_to_stderr || read_stdin) {
         for (;;) {
-            int c;
-            c = fgetc(stdin);
-            if (c == EOF) {
+            char buffer[4 * 4096];
+            size_t result;
+
+            result = fread(buffer, 1, sizeof(buffer), stdin);
+            if (result == 0) {
                 if (errno == EAGAIN) {
                     clearerr(stdin);
-                    SDL_Delay(10);
+                    SDL_Delay(20);
                     continue;
                 }
                 break;
             }
             if (stdin_to_stdout) {
-                fputc(c, stdout);
+                fwrite(buffer, 1, result, stdout);
                 fflush(stdout);
             }
             if (stdin_to_stderr) {
-                fputc(c, stderr);
+                fwrite(buffer, 1, result, stderr);
             }
         }
     }
 
     SDLTest_CommonDestroyState(state);
 
-    if (expect_environment && !expect_environment_match) {
-        exit_code |= 0x1;
-    }
     return exit_code;
 }
diff --git a/test/testprocess.c b/test/testprocess.c
index 0e482463c5338..97645d0249f1c 100644
--- a/test/testprocess.c
+++ b/test/testprocess.c
@@ -10,17 +10,7 @@
 
 /*
  * FIXME: Additional tests:
- * - stdin to stdout
  * - stdin to stderr
- * - read env, using env set by parent process
- * - exit codes
- * - kill process
- * - waiting twice on process
- * - executing a non-existing program
- * - executing a process linking to a shared library not in the search paths
- * - piping processes
- * - forwarding SDL_IOFromFile stream to process
- * - forwarding process to SDL_IOFromFile stream
  */
 
 typedef struct {
@@ -33,45 +23,53 @@ static void SDLCALL setUpProcess(void **arg) {
     *arg = &parsed_args;
 }
 
-static const char *options[] = { "/path/to/childprocess" EXE, NULL };
+static const char *options[] = {
+    "/path/to/childprocess" EXE,
+    NULL
+};
 
-static SDL_Environment *DuplicateEnvironment(const char *key0, ...)
-{
+static char **CreateArguments(int ignore, ...) {
     va_list ap;
-    const char *keyN;
-    SDL_Environment *env = SDL_GetEnvironment();
-    SDL_Environment *new_env = SDL_CreateEnvironment(false);
-
-    if (key0) {
-        char *sep = SDL_strchr(key0, '=');
-        if (sep) {
-            *sep = '\0';
-            SDL_SetEnvironmentVariable(new_env, key0, sep + 1, true);
-            *sep = '=';
-            SDL_SetEnvironmentVariable(new_env, key0, sep, true);
-        } else {
-            SDL_SetEnvironmentVariable(new_env, key0, SDL_GetEnvironmentVariable(env, key0), true);
+    size_t count = 1;
+    size_t i;
+    char **result;
+
+    va_start(ap, ignore);
+    for (;;) {
+        const char *keyN = va_arg(ap, const char *);
+        if (!keyN) {
+            break;
         }
-        va_start(ap, key0);
-        for (;;) {
-            keyN = va_arg(ap, const char *);
-            if (keyN) {
-                sep = SDL_strchr(keyN, '=');
-                if (sep) {
-                    *sep = '\0';
-                    SDL_SetEnvironmentVariable(new_env, keyN, sep + 1, true);
-                    *sep = '=';
-                } else {
-                    SDL_SetEnvironmentVariable(new_env, keyN, SDL_GetEnvironmentVariable(env, keyN), true);
-                }
-            } else {
-                break;
-            }
+        count += 1;
+    }
+    va_end(ap);
+
+    result = SDL_calloc(count, sizeof(char *));
+
+    i = 0;
+    va_start(ap, ignore);
+    for (;;) {
+        const char *keyN = va_arg(ap, const char *);
+        if (!keyN) {
+            break;
         }
-        va_end(ap);
+        result[i++] = SDL_strdup(keyN);
     }
+    va_end(ap);
+
+    return result;
+}
+
+static void DestroyStringArray(char **list) {
+    char **current;
 
-    return new_env;
+    if (!list) {
+        return;
+    }
+    for (current = list; *current; current++) {
+        SDL_free(*current);
+    }
+    SDL_free(list);
 }
 
 static int SDLCALL process_testArguments(void *arg)
@@ -98,6 +96,7 @@ static int SDLCALL process_testArguments(void *arg)
     char *buffer;
     int exit_code;
     int i;
+    size_t total_read = 0;
 
     process = SDL_CreateProcess(process_args, true);
     SDLTest_AssertCheck(process != NULL, "SDL_CreateProcess()");
@@ -106,12 +105,13 @@ static int SDLCALL process_testArguments(void *arg)
     }
 
     exit_code = 0xdeadbeef;
-    buffer = (char *)SDL_ReadProcess(process, NULL, &exit_code);
+    buffer = (char *)SDL_ReadProcess(process, &total_read, &exit_code);
     SDLTest_AssertCheck(buffer != NULL, "SDL_ReadProcess()");
     SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code);
     if (!buffer) {
         goto failed;
     }
+    SDLTest_LogEscapedString("stdout of process: ", buffer, total_read);
 
     for (i = 3; process_args[i]; i++) {
         char line[64];
@@ -128,30 +128,87 @@ static int SDLCALL process_testArguments(void *arg)
     return TEST_ABORTED;
 }
 
+static int SDLCALL process_testexitCode(void *arg)
+{
+    TestProcessData *data = (TestProcessData *)arg;
+    int i;
+    int exit_codes[] = {
+        0, 13, 31, 127, 255
+    };
+
+    for (i = 0; i < SDL_arraysize(exit_codes); i++) {
+        bool wait_result;
+        SDL_Process *process = NULL;
+        char **process_args = NULL;
+        char number_buffer[8];
+        int exit_code;
+
+        SDL_snprintf(number_buffer, sizeof(number_buffer), "%d", exit_codes[i]);
+
+        process_args = CreateArguments(0, data->childprocess_path, "--exit-code", number_buffer, NULL);
+
+        process = SDL_CreateProcess((const char * const *)process_args, false);
+        SDLTest_AssertCheck(process != NULL, "SDL_CreateProcess()");
+        if (!process) {
+            goto failed;
+        }
+
+        exit_code = 0xdeadbeef;
+        SDLTest_AssertPass("About to wait on process (first time)");
+        wait_result = SDL_WaitProcess(process, true, &exit_code);
+        SDLTest_AssertCheck(wait_result == true, "SDL_WaitProcess(): Process should have closed immediately");
+        SDLTest_AssertCheck(exit_code == exit_codes[i], "SDL_WaitProcess(): Exit code should be %d, is %d", exit_codes[i], exit_code);
+
+        exit_code = 0xdeadbeef;
+        SDLTest_AssertPass("About to wait on process (second time)");
+        wait_result = SDL_WaitProcess(process, true, &exit_code);
+        SDLTest_AssertCheck(wait_result == true, "SDL_WaitProcess(): Process should have closed immediately");
+        SDLTest_AssertCheck(exit_code == exit_codes[i], "SDL_WaitProcess(): Exit code should be %d, is %d", exit_codes[i], exit_code);
+
+        SDLTest_AssertPass("About to destroy process");
+        SDL_DestroyProcess(process);
+        DestroyStringArray(process_args);
+        continue;
+failed:
+        SDL_DestroyProcess(process);
+        DestroyStringArray(process_args);
+        return TEST_ABORTED;
+    }
+    return TEST_COMPLETED;
+#if 0
+failed:
+    SDL_DestroyProcess(process);
+    DestroyStringArray(process_args);
+    return TEST_ABORTED;
+#endif
+}
+
 static int SDLCALL process_testInheritedEnv(void *arg)
 {
     TestProcessData *data = (TestProcessData *)arg;
     const char *process_args[] = {
         data->childprocess_path,
         "--print-environment",
-        "--expect-env", NULL,
         NULL,
     };
     SDL_PropertiesID props;
     SDL_Process *process = NULL;
     Sint64 pid;
-    SDL_IOStream *process_stdout = NULL;
-    char buffer[256];
-    bool wait_result;
     int exit_code;
-    static const char *const TEST_ENV_KEY = "testprocess_environment";
-    char *test_env_val = NULL;
+    char random_env1[64];
+    char random_env2[64];
+    static const char *const TEST_ENV_KEY1 = "testprocess_inherited_var";
+    static const char *const TEST_ENV_KEY2 = "testprocess_other_var";
+    char *test_env_val1 = NULL;
+    char *test_env_val2 = NULL;
+    char *buffer = NULL;
+
+    test_env_val1 = SDLTest_RandomAsciiStringOfSize(32);
+    SDL_snprintf(random_env1, sizeof(random_env1), "%s=%s", TEST_ENV_KEY1, test_env_val1);
+    SDLTest_AssertPass("Setting parent environment variable %s=%s", TEST_ENV_KEY1, test_env_val1);
+    SDL_SetEnvironmentVariable(SDL_GetEnvironment(), TEST_ENV_KEY1, test_env_val1, true);
 
-    test_env_val = SDLTest_RandomAsciiStringOfSize(32);
-    SDLTest_AssertPass("Setting parent environment variable %s=%s", TEST_ENV_KEY, test_env_val);
-    SDL_SetEnvironmentVariable(SDL_GetEnvironment(), TEST_ENV_KEY, test_env_val, true);
-    SDL_snprintf(buffer, sizeof(buffer), "%s=%s", TEST_ENV_KEY, test_env_val);
-    process_args[3] = buffer;
+    SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), TEST_ENV_KEY2);
 
     props = SDL_CreateProperties();
     SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (void *)process_args);
@@ -163,44 +220,37 @@ static int SDLCALL process_testInheritedEnv(void *arg)
         goto failed;
     }
 
+    test_env_val2 = SDLTest_RandomAsciiStringOfSize(32);
+    SDL_snprintf(random_env2, sizeof(random_env2), "%s=%s", TEST_ENV_KEY2, test_env_val2);
+    SDLTest_AssertPass("Setting parent environment variable %s=%s", TEST_ENV_KEY2, test_env_val2);
+    SDL_SetEnvironmentVariable(SDL_GetEnvironment(),TEST_ENV_KEY2, test_env_val2, true);
+    SDLTest_AssertCheck(SDL_strcmp(test_env_val1, test_env_val2) != 0, "Sanity checking the 2 random environment variables are not identical");
+
     props = SDL_GetProcessProperties(process);
     SDLTest_AssertCheck(props != 0, "SDL_GetProcessProperties()");
 
     pid = SDL_GetNumberProperty(props, SDL_PROP_PROCESS_PID_NUMBER, 0);
     SDLTest_AssertCheck(pid != 0, "Checking process ID, expected non-zero, got %" SDL_PRIs64, pid);
 
-    process_stdout = SDL_GetProcessOutput(process);
-    SDLTest_AssertCheck(process_stdout != NULL, "SDL_GetPointerProperty(SDL_PROP_PROCESS_STDOUT_POINTER) returns a valid IO stream");
-    if (!process_stdout) {
-        goto failed;
-    }
-
-    for (;;) {
-        size_t amount_read;
-
-        amount_read = SDL_ReadIO(process_stdout, buffer, sizeof(buffer) - 1);
-        if (amount_read > 0) {
-            buffer[amount_read] = '\0';
-            SDLTest_Log("READ: %s", buffer);
-        } else if (SDL_GetIOStatus(process_stdout) != SDL_IO_STATUS_NOT_READY) {
-            break;
-        }
-        SDL_Delay(10);
-    }
-
-    SDLTest_AssertPass("About to wait on process");
     exit_code = 0xdeadbeef;
-    wait_result = SDL_WaitProcess(process, true, &exit_code);
-    SDLTest_AssertCheck(wait_result == true, "Process should have closed when closing stdin");
-    SDLTest_AssertPass("exit_code will be != 0 when environment variable was not set");
+    buffer = (char *)SDL_ReadProcess(process, NULL, &exit_code);
+    SDLTest_AssertCheck(buffer != NULL, "SDL_ReadProcess()");
     SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code);
+
+    SDLTest_AssertCheck(SDL_strstr(buffer, random_env1) != NULL, "Environment of child should contain \"%s\"", test_env_val1);
+    SDLTest_AssertCheck(SDL_strstr(buffer, random_env2) == NULL, "Environment of child should not contain \"%s\"", test_env_val2);
+
     SDLTest_AssertPass("About to destroy process");
     SDL_DestroyProcess(process);
-    SDL_free(test_env_val);
+    SDL_free(test_env_val1);
+    SDL_free(test_env_val2);
+    SDL_free(buffer);
     return TEST_COMPLETED;
 failed:
-    SDL_free(test_env_val);
+    SDL_free(test_env_val1);
+    SDL_free(test_env_val2);
     SDL_DestroyProcess(process);
+    SDL_free(buffer);
     return TEST_ABORTED;
 }
 
@@ -210,25 +260,38 @@ static int SDLCALL process_testNewEnv(void *arg)
     const char *process_args[] = {
         data->childprocess_path,
         "--print-environment",
-        "--expect-env", NULL,
         NULL,
     };
     SDL_Environment *process_env;
     SDL_PropertiesID props;
     SDL_Process *process = NULL;
     Sint64 pid;
-    SDL_IOStream *process_stdout = NULL;
-    char buffer[256];
-    bool wait_result;
     int exit_code;
-    static const char *const TEST_ENV_KEY = "testprocess_environment";
-    char *test_env_val = NULL;
-
-    test_env_val = SDLTest_RandomAsciiStringOfSize(32);
-    SDL_snprintf(buffer, sizeof(buffer), "%s=%s", TEST_ENV_KEY, test_env_val);
-    process_args[3] = buffer;
-
-    process_env = DuplicateEnvironment("PATH", "LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH", buffer, NULL);
+    char random_env1[64];
+    char random_env2[64];
+    static const char *const TEST_ENV_KEY1 = "testprocess_inherited_var";
+    static const char *const TEST_ENV_KEY2 = "testprocess_other_var";
+    char *test_env_val1 = NULL;
+    char *test_env_val2 = NULL;
+    char *buffer = NULL;
+    size_t total_read = 0;
+
+    test_env_val1 = SDLTest_RandomAsciiStringOfSize(32);
+    SDL_snprintf(random_env1, sizeof(random_env1), "%s=%s", TEST_ENV_KEY1, test_env_val1);
+    SDLTest_AssertPass("Unsetting parent environment variable %s", TEST_ENV_KEY1);
+    SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), TEST_ENV_KEY1);
+
+    process_env = SDL_CreateEnvironment(true);
+    SDL_SetEnvironmentVariable(process_env, "PATH", SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "PATH"), true);
+    SDL_SetEnvironmentVariable(process_env, "LD_LIBRARY_PATH", SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "LD_LIBRARY_PATH"), true);
+    SDL_SetEnvironmentVariable(process_env, "DYLD_LIBRARY_PATH", SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "DYLD_LIBRARY_PATH"), true);
+    SDL_SetEnvironmentVariable(process_env, TEST_ENV_KEY1, test_env_val1, true);
+
+    test_env_val2 = SDLTest_RandomAsciiStringOfSize(32);
+    SDL_snprintf(random_env2, sizeof(random_env2), "%s=%s", TEST_ENV_KEY2, test_env_val1);
+    SDLTest_AssertPass("Setting parent environment variable %s=%s", TEST_ENV_KEY2, test_env_val2);
+    SDL_SetEnvironmentVariable(SDL_GetEnvironment(), TEST_ENV_KEY2, test_env_val2, true);
+    SDLTest_AssertCheck(SDL_strcmp(test_env_val1, test_env_val2) != 0, "Sanity checking the 2 random environment variables are not identical");
 
     props = SDL_CreateProperties();
     SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (void *)process_args);
@@ -247,41 +310,79 @@ static int SDLCALL process_testNewEnv(void *arg)
     pid = SDL_GetNumberProperty(props, SDL_PROP_PROCESS_PID_NUMBER, 0);
     SDLTest_AssertCheck(pid != 0, "Checking process ID, expected non-zero, got %" SDL_PRIs64, pid);
 
-    process_stdout = SDL_GetProcessOutput(process);
-    SDLTest_AssertCheck(process_stdout != NULL, "SDL_GetPointerProperty(SDL_PROP_PROCESS_STDOUT_POINTER) returns a valid IO stream");
-    if (!process_stdout) {
+    exit_code = 0xdeadbeef;
+    buffer = (char *)SDL_ReadProcess(process, &total_read, &exit_code);
+    SDLTest_AssertCheck(buffer != NULL, "SDL_ReadProcess()");
+    SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code);
+    SDLTest_LogEscapedString("Text read from subprocess: ", buffer, total_read);
+
+    SDLTest_AssertCheck(SDL_strstr(buffer, random_env1) != NULL, "Environment of child should contain \"%s\"", random_env1);
+    SDLTest_AssertCheck(SDL_strstr(buffer, random_env2) == NULL, "Environment of child should not contain \"%s\"", random_env1);
+
+    SDLTest_AssertPass("About to destroy process");
+    SDL_DestroyProcess(process);
+    SDL_DestroyEnvironment(process_env);
+    SDL_free(test_env_val1);
+    SDL_free(test_env_val2);
+    SDL_free(buffer);
+    return TEST_COMPLETED;
+
+failed:
+    SDL_DestroyProcess(process);
+    SDL_DestroyEnvironment(process_env);
+    SDL_free(test_env_val1);
+    SDL_free(test_env_val2);
+    SDL_free(buffer);
+    return TEST_ABORTED;
+}
+
+static int SDLCALL process_testKill(void *arg)
+{
+    TestProcessData *data = (TestProcessData *)arg;
+    const char *process_args[] = {
+        data->childprocess_path,
+        "--stdin",
+        NULL,
+    };
+    SDL_Process *process = NULL;
+    SDL_PropertiesID props;
+    Sint64 pid;
+    int result;
+    int exit_code;
+
+    SDLTest_AssertPass("About to call SDL_CreateProcess(true)");
+    process = SDL_CreateProcess(process_args, true);
+    if (!process) {
         goto failed;
     }
 
-    for (;;) {
-        size_t amount_read;
+    props = SDL_GetProcessProperties(process);
+    SDLTest_AssertCheck(props != 0, "SDL_GetProcessProperties()");
 
-        amount_read = SDL_ReadIO(process_stdout, buffer, sizeof(buffer) - 1);
-        if (amount_read > 0) {
-            buffer[amount_read] = '\0';
-            SDLTest_Log("READ: %s", buffer);
-        } else if (SDL_GetIOStatus(process_stdout) != SDL_IO_STATUS_NOT_READY) {
-            break;
-        }
-        SDL_Delay(10);
-    }
+    pid = SDL_GetNumberProperty(props, SDL_PROP_PROCESS_PID_NUMBER, 0);
+    SDLTest_AssertCheck(pid != 0, "Checking process ID, expected non-zero, got %" SDL_PRIs64, pid);
 
-    SDLTest_AssertPass("About to wait on process");
     exit_code = 0xdeadbeef;
-    wait_result = SDL_WaitProcess(process, true, &exit_code);
-    SDLTest_AssertCheck(wait_result == true, "Process should have closed when closing stdin");
-    SDLTest_AssertPass("exit_code will be != 0 when environment variable was not set");
-    SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code);
+    SDLTest_AssertPass("About to call SDL_WaitProcess(false)");
+    result = SDL_WaitProcess(process, false, &exit_code);
+    SDLTest_AssertCheck(result == false, "Process should not have exited yet");
+
+    SDLTest_AssertPass("About to call SDL_KillProcess(false)");
+    result = SDL_KillProcess(process, false);
+    SDLTest_AssertCheck(result == true, "Process should have exited");
+
+    exit_code = 0;
+    SDLTest_AssertPass("About to call SDL_WaitProcess(true)");
+    result = SDL_WaitProcess(process, true, &exit_code);
+    SDLTest_AssertCheck(result == true, "Process should have exited");
+    SDLTest_AssertCheck(exit_code != 0, "Exit code should be non-zero, is %d", exit_code);
+
     SDLTest_AssertPass("About to destroy process");
-    SDL_free(test_env_val);
     SDL_DestroyProcess(process);
-    SDL_DestroyEnvironment(process_env);
     return TEST_COMPLETED;
 
 failed:
-    SDL_free(test_env_val);
     SDL_DestroyProcess(process);
-    SDL_DestroyEnvironment(process_env);
     return TEST_ABORTED;
 }
 
@@ -298,13 +399,31 @@ static int process_testStdinToStdout(void *arg)
     Sint64 pid;
     SDL_IOStream *process_stdin = NULL;
     SDL_IOStream *process_stdout = NULL;
-    const char *text_in = "Tests whether we can write to stdin and read from stdout\r\n{'succes': true, 'message': 'Success!'}\r\nYippie ka yee\r\nEOF";
-    size_t amount_written;
-    size_t amount_to_write;
-    char buffer[128];
+    SDL_IOStream *process_stderr = NULL;
+    size_t text_in_size = 1 * 1024 * 1024;
+    char *text_in = NULL;
+    size_t total_written;
     size_t total_read;
     bool wait_result;
     int exit_code;
+    SDL_IOStream *stdout_stream = NULL;
+    char *stdout_stream_buf;
+    int iteration_count = 0;
+
+    text_in = SDLTest_RandomAsciiStringOfSize((int)text_in_size);
+    /* Make sure text_in does not contain EOF */
+    for (;;) {
+        char *e = SDL_strstr(text_in, "EOF");
+        if (!e) {
+            break;
+        }
+        e[0] = 'N';
+    }
+    text_in[text_in_size - 3] = 'E';
+    text_in[text_in_size - 2] = 'O';
+    text_in[text_in_size - 1] = 'F';
+
+    stdout_stream = SDL_IOFromDynamicMem();
 
     props = SDL_CreateProperties();
     SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (void *)process_args);
@@ -327,41 +446,73 @@ static int process_testStdinToStdout(void *arg)
     SDLTest_AssertCheck(process_stdin != NULL, "SDL_GetPointerProperty(SDL_PROP_PROCESS_STDIN_POINTER) returns a valid IO stream");
     process_stdout = SDL_GetProcessOutput(process);
     SDLTest_AssertCheck(process_stdout != NULL, "SDL_GetPointerProperty(SDL_PROP_PROCESS_STDOUT_POINTER) returns a valid IO stream");
+    process_stderr = (SDL_IOStream *)SDL_GetPointerProperty(props, SDL_PROP_PROCESS_STDERR_POINTER, NULL);
+    SDLTest_AssertCheck(process_stderr == NULL, "SDL_GetPointerProperty(SDL_PROP_PROCESS_STDERR_POINTER) returns NULL");
     if (!process_stdin || !process_stdout) {
         goto failed;
     }
-    SDLTest_AssertPass("About to write to process");
-    amount_to_write = SDL_strlen(text_in);
-    amount_written = SDL_WriteIO(process_stdin, text_in, amount_to_write);
-    SDLTest_AssertCheck(amount_written == amount_to_write, "SDL_WriteIO(subprocess.stdin) wrote %" SDL_PRIu64 " bytes, expected %" SDL_PRIu64, (Uint64)amount_written, (Uint64)amount_to_write);
-    if (amount_to_write != amount_written) {
-        goto failed;
-    }
-    SDL_FlushIO(process_stdin);
 
+    total_written = 0;
     total_read = 0;
-    buffer[0] = '\0';
     for (;;) {
+        int log_this_iteration = (iteration_count % 32) == 32;
+        char local_buffer[16 * 4094];
         size_t amount_read;
-        if (total_read >= sizeof(buffer) - 1) {
-            SDLTest_AssertCheck(0, "Buffer is too small for input data.");
-            goto failed;
+        SDL_IOStatus io_status;
+        if (total_written != text_in_size) {
+            size_t amount_written;
+            if (log_this_iteration) {
+                SDLTest_AssertPass("About to SDL_WriteIO (%dth time)", iteration_count);
+            }
+            amount_written = SDL_WriteIO(process_stdin, text_in + total_written, text_in_size - total_written);
+            if (log_this_iteration) {
+                SDLTest_Log("SDL_WriteIO() -> %u (%dth time)", (unsigned)amount_written, iteration_count);
+            }
+            if (amount_written == 0) {
+                io_status = SDL_GetIOStatus(process_stdin);
+                if (io_status != SDL_IO_STATUS_NOT_READY) {
+                    SDLTest_Log("SDL_GetIOStatus(process_stdin) returns %d, breaking.", io_status);
+                    break;
+                }
+            }
+            total_written += amount_written;
+            SDL_FlushIO(process_stdin);
         }
 
-        SDLTest_AssertPass("About to read from process");
-        amount_read = SDL_ReadIO(process_stdout, buffer + total_read, sizeof(buffer) - total_read - 1);
-        if (amount_read == 0 && SDL_GetIOStatus(process_stdout) != SDL_IO_STATUS_NOT_READY) {
-            break;
+        /* FIXME: this needs a rate limit */
+        if (log_this_iteration) {
+            SDLTest_AssertPass("About to SDL_ReadIO (%dth time)", iteration_count);
         }
-        total_read += amount_read;
-        buffer[total_read] = '\0';
-        if (total_read >= sizeof(buffer) - 1 || SDL_strstr(buffer, "EOF")) {
-            break;
+        amount_read = SDL_ReadIO(process_stdout, local_buffer, sizeof(local_buffer));
+        if (log_this_iteration) {
+            SDLTest_Log("SDL_ReadIO() -> %u (%dth time)", (unsigned)amount_read, iteration_count);
+        }
+        if (amount_read == 0) {
+            io_status = SDL_GetIOStatus(process_stdout);
+            if (io_status != SDL_IO_STATUS_NOT_READY) {
+                SDLTest_Log("SDL_GetIOStatus(process_stdout) returned %d, breaking.", io_status);
+                break;
+            }
+        } else {
+            total_read += amount_read;
+            SDL_WriteIO(stdout_stream, local_buffer, amount_read);
+            stdout_stream_buf = SDL_GetPointerProperty(SDL_GetIOProperties(stdout_stream), SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_P

(Patch may be truncated, please check the link at the top of this post.)