diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 034a297e1381b..03804e7883c54 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -363,9 +363,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 $ 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) @@ -405,6 +402,10 @@ 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 $ 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 d66bf95164c10..3de25034d1acc 100644 --- a/test/childprocess.c +++ b/test/childprocess.c @@ -5,15 +5,20 @@ #include #include +#ifdef SDL_PLATFORM_WINDOWS +#include +#else +#include +#include +#endif int main(int argc, char *argv[]) { SDLTest_CommonState *state; int i; - const char *expect_environment = NULL; - SDL_bool expect_environment_match = SDL_FALSE; SDL_bool print_arguments = SDL_FALSE; SDL_bool print_environment = SDL_FALSE; SDL_bool stdin_to_stdout = SDL_FALSE; + SDL_bool read_stdin = SDL_FALSE; SDL_bool stdin_to_stderr = SDL_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 = SDL_TRUE; - consumed = 1; - } else if (SDL_strcmp(argv[i], "--print-environment") == 0) { - print_environment = SDL_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 = SDL_TRUE; - consumed = 1; - } else if (SDL_strcmp(argv[i], "--stdin-to-stderr") == 0) { - stdin_to_stderr = SDL_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 = SDL_TRUE; + consumed = 1; + } else if (SDL_strcmp(argv[i], "--print-environment") == 0) { + print_environment = SDL_TRUE; + consumed = 1; + } else if (SDL_strcmp(argv[i], "--stdin-to-stdout") == 0) { + stdin_to_stdout = SDL_TRUE; + consumed = 1; + } else if (SDL_strcmp(argv[i], "--stdin-to-stderr") == 0) { + stdin_to_stderr = SDL_TRUE; + consumed = 1; + } else if (SDL_strcmp(argv[i], "--stdin") == 0) { + read_stdin = SDL_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 7fb53eeabb2cc..f81a9d6e7a996 100644 --- a/test/testprocess.c +++ b/test/testprocess.c @@ -10,15 +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 */ @@ -33,45 +25,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(SDL_FALSE); - - if (key0) { - char *sep = SDL_strchr(key0, '='); - if (sep) { - *sep = '\0'; - SDL_SetEnvironmentVariable(new_env, key0, sep + 1, SDL_TRUE); - *sep = '='; - SDL_SetEnvironmentVariable(new_env, key0, sep, SDL_TRUE); - } else { - SDL_SetEnvironmentVariable(new_env, key0, SDL_GetEnvironmentVariable(env, key0), SDL_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, SDL_TRUE); - *sep = '='; - } else { - SDL_SetEnvironmentVariable(new_env, keyN, SDL_GetEnvironmentVariable(env, keyN), SDL_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; +} - return new_env; +static void DestroyStringArray(char **list) { + char **current; + + if (!list) { + return; + } + for (current = list; *current; current++) { + SDL_free(*current); + } + SDL_free(list); } static int SDLCALL process_testArguments(void *arg) @@ -98,6 +98,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, SDL_TRUE); SDLTest_AssertCheck(process != NULL, "SDL_CreateProcess()"); @@ -106,12 +107,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 +130,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++) { + SDL_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, SDL_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, SDL_TRUE, &exit_code); + SDLTest_AssertCheck(wait_result == SDL_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, SDL_TRUE, &exit_code); + SDLTest_AssertCheck(wait_result == SDL_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]; - SDL_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_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, SDL_TRUE); - SDL_snprintf(buffer, sizeof(buffer), "%s=%s", TEST_ENV_KEY, test_env_val); - process_args[3] = buffer; + 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, SDL_TRUE); + + SDL_UnsetEnvironmentVariable(SDL_GetEnvironment(), TEST_ENV_KEY2); props = SDL_CreateProperties(); SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, (void *)process_args); @@ -163,44 +222,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, SDL_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, SDL_TRUE, &exit_code); - SDLTest_AssertCheck(wait_result == SDL_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 +262,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]; - SDL_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(SDL_TRUE); + SDL_SetEnvironmentVariable(process_env, "PATH", SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "PATH"), SDL_TRUE); + SDL_SetEnvironmentVariable(process_env, "LD_LIBRARY_PATH", SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "LD_LIBRARY_PATH"), SDL_TRUE); + SDL_SetEnvironmentVariable(process_env, "DYLD_LIBRARY_PATH", SDL_GetEnvironmentVariable(SDL_GetEnvironment(), "DYLD_LIBRARY_PATH"), SDL_TRUE); + SDL_SetEnvironmentVariable(process_env, TEST_ENV_KEY1, test_env_val1, SDL_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, SDL_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 +312,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(SDL_TRUE)"); + process = SDL_CreateProcess(process_args, SDL_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, SDL_TRUE, &exit_code); - SDLTest_AssertCheck(wait_result == SDL_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(SDL_FALSE)"); + result = SDL_WaitProcess(process, SDL_FALSE, &exit_code); + SDLTest_AssertCheck(result == SDL_FALSE, "Process should not have exited yet"); + + SDLTest_AssertPass("About to call SDL_KillProcess(SDL_FALSE)"); + result = SDL_KillProcess(process, SDL_FALSE); + SDLTest_AssertCheck(result == SDL_TRUE, "Process should have exited"); + + exit_code = 0; + SDLTest_AssertPass("About to call SDL_WaitProcess(SDL_TRUE)"); + result = SDL_WaitProcess(process, SDL_TRUE, &exit_code); + SDLTest_AssertCheck(result == SDL_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 +401,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; SDL_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 +448,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_POINTER, NULL); + if (SDL_strstr(stdout_stream_buf, "EOF")) { + SDLTest_Log("Found EOF in stdout"); + break; + } } SDL_Delay(10); } - SDLTest_Log("Text read from subprocess: %s", buffer); - SDLTest_AssertCheck(SDL_strcmp(buffer, text_in) == 0, "Subprocess stdout should match text written to stdin"); + SDLTest_Log("Wrote %" SDL_PRIu64 " bytes to process.stdin", (Uint64)total_written); + SDLTest_Log("Read %" SDL_PRIu64 " bytes from process.stdout",(Uint64)total_read); + + stdout_stream_buf = SDL_GetPointerProperty(SDL_GetIOProperties(stdout_stream), SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, NULL); + SDLTest_CompareMemory(stdout_stream_buf, total_written, text_in, text_in_size); + + exit_code = 0xdeadbeef; + wait_result = SDL_WaitProcess(process, SDL_FALSE, &exit_code); + SDLTest_AssertCheck(wait_result == SDL_FALSE, "Process should not have closed yet"); SDLTest_AssertPass("About to close stdin"); /* Closing stdin of `subprocessstdin --stdin-to-stdout` should close the process */ @@ -383,10 +536,14 @@ static int process_testStdinToStdout(void *arg) } SDLTest_AssertPass("About to destroy process"); SDL_DestroyProcess(process); + SDL_CloseIO(stdout_stream); + SDL_free(text_in); return TEST_COMPLETED; failed: SDL_DestroyProcess(process); + SDL_CloseIO(stdout_stream); + SDL_free(text_in); return TEST_ABORTED; } @@ -404,6 +561,7 @@ static int process_testSimpleStdinToStdout(void *arg) char *buffer; size_t result; int exit_code; + size_t total_read = 0; process = SDL_CreateProcess(process_args, SDL_TRUE); SDLTest_AssertCheck(process != NULL, "SDL_CreateProcess()"); @@ -422,14 +580,16 @@ static int process_testSimpleStdinToStdout(void *arg) SDLTest_AssertCheck(input == NULL, "SDL_GetProcessInput() after close"); 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_Log("Text read from subprocess: %s", buffer); + SDLTest_LogEscapedString("Expected text read from subprocess: %s", text_in, SDL_strlen(text_in)); + SDLTest_LogEscapedString("Actual text read from subprocess: %s", buffer, total_read); + SDLTest_AssertCheck(total_read == SDL_strlen(text_in), "Expected to read %u bytes, actually read %u bytes", (unsigned)SDL_strlen(text_in), (unsigned)total_read); SDLTest_AssertCheck(SDL_strcmp(buffer, text_in) == 0, "Subprocess stdout should match text written to stdin"); SDL_free(buffer); @@ -448,6 +608,7 @@ static int process_testMultiprocessStdinToStdout(void *arg) const char *process_args[] = { data->childprocess_path, "--stdin-to-stdout", + "--exit-code", "3", NULL, }; SDL_Process *process1 = NULL; @@ -458,6 +619,7 @@ static int process_testMultiprocessStdinToStdout(void *arg) char *buffer; size_t result; int exit_code; + size_t total_read = 0; process1 = SDL_CreateProcess(process_args, SDL_TRUE); SDLTest_AssertCheck(process1 != NULL, "SDL_CreateProcess()"); @@ -470,6 +632,7 @@ static int process_testMultiprocessStdinToStdout(void *arg) SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_REDIRECT); SDL_SetPointerProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_POINTER, SDL_GetPointerProperty(SDL_GetProcessProperties(process1), SDL_PROP_PROCESS_STDOUT_POINTER, NULL)); SDL_SetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_APP); + SDLTest_AssertPass("About to call SDL_CreateProcessWithProperties"); process2 = SDL_CreateProcessWithProperties(props); SDL_DestroyProperties(props); SDLTest_AssertCheck(process2 != NULL, "SDL_CreateProcess()"); @@ -485,14 +648,16 @@ static int process_testMultiprocessStdinToStdout(void *arg) SDL_CloseIO(input); exit_code = 0xdeadbeef; - buffer = (char *)SDL_ReadProcess(process2, NULL, &exit_code); + buffer = (char *)SDL_ReadProcess(process2, &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_AssertCheck(exit_code == 3, "Exit code should be 3, is %d", exit_code); if (!buffer) { goto failed; } - SDLTest_Log("Text read from subprocess: %s", buffer); + SDLTest_LogEscapedString("Expected text read from subprocess: ", text_in, SDL_strlen(text_in)); + SDLTest_LogEscapedString("Actual text read from subprocess: ", buffer, total_read); + SDLTest_AssertCheck(total_read == SDL_strlen(text_in), "Expected to read %u bytes, actually read %u bytes", (unsigned)SDL_strlen(text_in), (unsigned)total_read); SDLTest_AssertCheck(SDL_strcmp(buffer, text_in) == 0, "Subprocess stdout should match text written to stdin"); SDL_free(buffer); SDLTest_AssertPass("About to destroy processes"); @@ -506,11 +671,121 @@ static int process_testMultiprocessStdinToStdout(void *arg) return TEST_ABORTED; } +static int process_testWriteToFinishedProcess(void *arg) +{ + TestProcessData *data = (TestProcessData *)arg; + const char *process_args[] = { + data->childprocess_path, + NULL, + }; + SDL_Process *process = NULL; + SDL_bool result; + int exit_code; + SDL_IOStream *process_stdin; + const char *text_in = "text_in"; + + SDLTest_AssertPass("About to call SDL_CreateProcess"); + process = SDL_CreateProcess(process_args, SDL_TRUE); + SDLTest_AssertCheck(process != NULL, "SDL_CreateProcess()"); + if (!process) { + goto failed; + } + + exit_code = 0xdeadbeef; + SDLTest_AssertPass("About to call SDL_WaitProcess"); + result = SDL_WaitProcess(process, SDL_TRUE, &exit_code); + SDLTest_AssertCheck(result, "SDL_WaitProcess()"); + SDLTest_AssertCheck(exit_code == 0, "Exit code should be 0, is %d", exit_code); + + process_stdin = SDL_GetProcessInput(process); + SDLTest_AssertCheck(process_stdin != NULL, "SDL_GetProcessInput returns non-Null SDL_IOStream"); + SDLTest_AssertPass("About to call SDL_WriteIO on dead child process"); + SDL_WriteIO(process_stdin, text_in, SDL_strlen(text_in)); + + SDLTest_AssertPass("About to destroy process"); + SDL_DestroyProcess(process); + return TEST_COMPLETED; + +failed: + SDL_DestroyProcess(process); + return TEST_ABORTED; +} + +static int process_testNonExistingExecutable(void *arg) +{ + static const int STEM_LENGTH = 16; + char **process_args; + char *random_stem; + char *random_path; + SDL_Process *process = NULL; + + random_stem = SDLTest_RandomAsciiStringOfSize(STEM_LENGTH); + random_path = SDL_malloc(STEM_LENGTH + SDL_strlen(EXE) + 1); + SDL_snprintf(random_path, STEM_LENGTH + SDL_strlen(EXE) + 1, "%s%s", random_stem, EXE); + SDL_free(random_stem); + SDLTest_AssertCheck(!SDL_GetPathInfo(random_path, NULL), "%s does not exist", random_path); + + process_args = CreateArguments(0, random_path, NULL); + SDL_free(random_path); + + SDLTest_AssertPass("About to call SDL_CreateProcess"); + process = SDL_CreateProcess((const char * const *)process_args, SDL_FALSE); + SDLTest_AssertCheck(process == NULL, "SDL_CreateProcess() should have failed (%s)", SDL_GetError()); + + DestroyStringArray(process_args); + return TEST_COMPLETED; +} + +static int process_testBatBadButVulnerability(void *arg) +{ + TestProcessData *data = (TestProcessData *)arg; + char *inject_arg = NULL; + char **process_args = NULL; + char *text_out = NULL; + size_t len_text_out; + int exitcode; + SDL_Process *process = NULL; + SDL_IOStream *child_bat; + +#ifndef SDL_PLATFORM_WINDOWS + SDLTest_AssertPass("The BatBadBut vulnerability only applied to Windows"); + return TEST_SKIPPED; +#endif + /* FIXME: remove child.bat at end of loop and/or create in temporary directory */ + child_bat = SDL_IOFromFile("child.bat", "w"); + SDL_IOprintf(child_bat, "@echo off\necho \"Hello from child.bat\"\n"); + SDL_CloseIO(child_bat); + + inject_arg = SDL_malloc(SDL_strlen(data->childprocess_path) + 100); + SDL_snprintf(inject_arg, SDL_strlen(data->childprocess_path) + 100, "\"&%s --version --stdout OWNEDSTDOUT\"", data->childprocess_path); + process_args = CreateArguments(0, "child.bat", inject_arg, NULL); + + SDLTest_AssertPass("About to call SDL_CreateProcess"); + process = SDL_CreateProcess((const char * const*)process_args, SDL_TRUE); + SDLTest_AssertCheck(process != NULL, "SDL_CreateProcess"); + text_out = SDL_ReadProcess(process, &len_text_out, &exitcode); + SDLTest_AssertCheck(exitcode == 0, "process exited with exitcode 0, was %d", exitcode); + SDLTest_LogEscapedString("Output: ", text_out, len_text_out); + + SDLTest_AssertCheck(SDL_strstr(text_out, "Hello from child") != NULL, "stdout contains 'Hello from child'"); + SDLTest_AssertCheck(SDL_strstr(text_out, "SDL version") == NULL, "stdout should not contain SDL version"); + + SDL_free(text_out); + SDL_DestroyProcess(process); + SDL_free(inject_arg); + DestroyStringArray(process_args); + return TEST_COMPLETED; +} + static const SDLTest_TestCaseReference processTestArguments = { process_testArguments, "process_testArguments", "Test passing arguments to child process", TEST_ENABLED }; -static const SDLTest_TestCaseReference processTestIneritedEnv = { +static const SDLTest_TestCaseReference processTestExitCode = { + process_testexitCode, "process_testExitCode", "Test exit codes", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference processTestInheritedEnv = { process_testInheritedEnv, "process_testInheritedEnv", "Test inheriting environment from parent process", TEST_ENABLED }; @@ -518,6 +793,10 @@ static const SDLTest_TestCaseReference processTestNewEnv = { process_testNewEnv, "process_testNewEnv", "Test creating new environment for child process", TEST_ENABLED }; +static const SDLTest_TestCaseReference processTestKill = { + process_testKill, "process_testKill", "Test Killing a child process", TEST_ENABLED +}; + static const SDLTest_TestCaseReference processTestStdinToStdout = { process_testStdinToStdout, "process_testStdinToStdout", "Test writing to stdin and reading from stdout", TEST_ENABLED }; @@ -530,13 +809,30 @@ static const SDLTest_TestCaseReference processTestMultiprocessStdinToStdout = { process_testMultiprocessStdinToStdout, "process_testMultiprocessStdinToStdout", "Test writing to stdin and reading from stdout using the simplified API", TEST_ENABLED }; +static const SDLTest_TestCaseReference processTestWriteToFinishedProcess = { + process_testWriteToFinishedProcess, "process_testWriteToFinishedProcess", "Test writing to stdin of terminated process", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference processTestNonExistingExecutable = { + process_testNonExistingExecutable, "process_testNonExistingExecutable", "Test running a non-existing executable", TEST_ENABLED +}; + +static const SDLTest_TestCaseReference processTestBatBadButVulnerability = { + process_testBatBadButVulnerability, "process_testBatBadButVulnerability", "Test BatBadBut vulnerability: command injection through cmd.exe", TEST_ENABLED +}; + static const SDLTest_TestCaseReference *processTests[] = { &processTestArguments, - &processTestIneritedEnv, + &processTestExitCode, + &processTestInheritedEnv, &processTestNewEnv, + &processTestKill, &processTestStdinToStdout, &processTestSimpleStdinToStdout, &processTestMultiprocessStdinToStdout, + &processTestWriteToFinishedProcess, + &processTestNonExistingExecutable, + &processTestBatBadButVulnerability, NULL };