diff --git a/test/main.c b/test/main.c
index f6c50cc..4b5bbcd 100644
--- a/test/main.c
+++ b/test/main.c
@@ -24,5 +24,50 @@
// For more information, please refer to
#include "utest.h"
+#include "process.h"
+
+UTEST(utest_cmdline, filter_with_list) {
+ struct process_s process;
+ const char *command[3] = {"utest_test", "--list-tests", 0};
+ int return_code;
+ FILE *stdout_file;
+ size_t index;
+
+// 64k should be enough for anyone
+#define MAX_CHARS (64 * 1024)
+ char buffer[MAX_CHARS] = {0};
+
+ ASSERT_EQ(0, process_create(command, process_option_combined_stdout_stderr,
+ &process));
+
+ stdout_file = process_stdout(&process);
+
+ for (index = 0; index < utest_state.tests_length; index++) {
+ if (buffer != fgets(buffer, MAX_CHARS, stdout_file)) {
+ break;
+ }
+
+#if defined(__clang__)
+#if __has_warning("-Wdisabled-macro-expansion")
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
+#endif
+#endif
+
+ ASSERT_EQ(0, strncmp(buffer, utest_state.tests[index].name,
+ strlen(utest_state.tests[index].name)));
+
+#if defined(__clang__)
+#if __has_warning("-Wdisabled-macro-expansion")
+#pragma clang diagnostic pop
+#endif
+#endif
+ }
+
+ ASSERT_EQ(0, process_join(&process, &return_code));
+ ASSERT_EQ(0, return_code);
+
+ ASSERT_EQ(0, process_destroy(&process));
+}
UTEST_MAIN()
diff --git a/test/main.c b/test/main.c
index f6c50cc..4b5bbcd 100644
--- a/test/main.c
+++ b/test/main.c
@@ -24,5 +24,50 @@
// For more information, please refer to
#include "utest.h"
+#include "process.h"
+
+UTEST(utest_cmdline, filter_with_list) {
+ struct process_s process;
+ const char *command[3] = {"utest_test", "--list-tests", 0};
+ int return_code;
+ FILE *stdout_file;
+ size_t index;
+
+// 64k should be enough for anyone
+#define MAX_CHARS (64 * 1024)
+ char buffer[MAX_CHARS] = {0};
+
+ ASSERT_EQ(0, process_create(command, process_option_combined_stdout_stderr,
+ &process));
+
+ stdout_file = process_stdout(&process);
+
+ for (index = 0; index < utest_state.tests_length; index++) {
+ if (buffer != fgets(buffer, MAX_CHARS, stdout_file)) {
+ break;
+ }
+
+#if defined(__clang__)
+#if __has_warning("-Wdisabled-macro-expansion")
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
+#endif
+#endif
+
+ ASSERT_EQ(0, strncmp(buffer, utest_state.tests[index].name,
+ strlen(utest_state.tests[index].name)));
+
+#if defined(__clang__)
+#if __has_warning("-Wdisabled-macro-expansion")
+#pragma clang diagnostic pop
+#endif
+#endif
+ }
+
+ ASSERT_EQ(0, process_join(&process, &return_code));
+ ASSERT_EQ(0, return_code);
+
+ ASSERT_EQ(0, process_destroy(&process));
+}
UTEST_MAIN()
diff --git a/test/process.h b/test/process.h
new file mode 100644
index 0000000..d3e1a97
--- /dev/null
+++ b/test/process.h
@@ -0,0 +1,568 @@
+/*
+ The latest version of this library is available on GitHub;
+ https://github.com/sheredom/process.h
+*/
+
+/*
+ This is free and unencumbered software released into the public domain.
+
+ Anyone is free to copy, modify, publish, use, compile, sell, or
+ distribute this software, either in source code form or as a compiled
+ binary, for any purpose, commercial or non-commercial, and by any
+ means.
+
+ In jurisdictions that recognize copyright laws, the author or authors
+ of this software dedicate any and all copyright interest in the
+ software to the public domain. We make this dedication for the benefit
+ of the public at large and to the detriment of our heirs and
+ successors. We intend this dedication to be an overt act of
+ relinquishment in perpetuity of all present and future rights to this
+ software under copyright law.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+
+ For more information, please refer to
+*/
+
+#ifndef SHEREDOM_PROCESS_H_INCLUDED
+#define SHEREDOM_PROCESS_H_INCLUDED
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#if defined(_MSC_VER)
+#pragma warning(push, 1)
+#endif
+
+#include
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+#if !defined(_MSC_VER)
+#include
+#include
+#include
+#include
+#endif
+
+#if defined(_MSC_VER)
+#ifdef _WIN64
+typedef __int64 intptr_t;
+typedef unsigned __int64 size_t;
+#else
+typedef int intptr_t;
+typedef unsigned int size_t;
+#endif
+
+typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION;
+typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES;
+typedef struct _STARTUPINFOA *LPSTARTUPINFOA;
+
+#pragma warning(push, 1)
+struct process_process_information_s {
+ void *hProcess;
+ void *hThread;
+ unsigned long dwProcessId;
+ unsigned long dwThreadId;
+};
+
+struct process_security_attributes_s {
+ unsigned long nLength;
+ void *lpSecurityDescriptor;
+ int bInheritHandle;
+};
+
+struct process_startup_info_s {
+ unsigned long cb;
+ char *lpReserved;
+ char *lpDesktop;
+ char *lpTitle;
+ unsigned long dwX;
+ unsigned long dwY;
+ unsigned long dwXSize;
+ unsigned long dwYSize;
+ unsigned long dwXCountChars;
+ unsigned long dwYCountChars;
+ unsigned long dwFillAttribute;
+ unsigned long dwFlags;
+ unsigned short wShowWindow;
+ unsigned short cbReserved2;
+ unsigned char *lpReserved2;
+ void *hStdInput;
+ void *hStdOutput;
+ void *hStdError;
+};
+#pragma warning(pop)
+
+__declspec(dllimport) int __stdcall SetHandleInformation(void *, unsigned long,
+ unsigned long);
+__declspec(dllimport) int __stdcall CreatePipe(void **, void **,
+ LPSECURITY_ATTRIBUTES,
+ unsigned long);
+__declspec(dllimport) int __stdcall CreateProcessA(
+ const char *, char *, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, int,
+ unsigned long, void *, const char *, LPSTARTUPINFOA, LPPROCESS_INFORMATION);
+__declspec(dllimport) int __stdcall CloseHandle(void *);
+__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(
+ void *, unsigned long);
+__declspec(dllimport) int __stdcall GetExitCodeProcess(
+ void *, unsigned long *lpExitCode);
+__declspec(dllimport) int __cdecl _open_osfhandle(intptr_t, int);
+void *__cdecl _alloca(size_t);
+#endif
+
+#if defined(__clang__) || defined(__GNUC__)
+#define process_pure __attribute__((pure))
+#define process_weak __attribute__((weak))
+#elif defined(_MSC_VER)
+#define process_pure
+#define process_weak __inline
+#else
+#error Non clang, non gcc, non MSVC compiler found!
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+struct process_s {
+ FILE *stdin_file;
+ FILE *stdout_file;
+ FILE *stderr_file;
+
+#if defined(_MSC_VER)
+ void *hProcess;
+ void *hStdInput;
+ void *hStdOutput;
+ void *hStdError;
+#else
+ pid_t child;
+#endif
+};
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+enum process_option_e {
+ // stdout and stderr are the same FILE.
+ process_option_combined_stdout_stderr = 0x1,
+
+ // The child process should inherit the environment variables of the parent.
+ process_option_inherit_environment = 0x2
+};
+
+/// @brief Create a process.
+/// @param command_line An array of strings for the command line to execute for
+/// this process. The last element must be NULL to signify the end of the array.
+/// @param options A bit field of process_option_e's to pass.
+/// @param out_process The newly created process.
+/// @return On success 0 is returned.
+process_weak int process_create(const char *const command_line[], int options,
+ struct process_s *const out_process);
+
+/// @brief Get the standard input file for a process.
+/// @param process The process to query.
+/// @return The file for standard input of the process.
+///
+/// The file returned can be written to by the parent process to feed data to
+/// the standard input of the process.
+process_pure process_weak FILE *
+process_stdin(const struct process_s *const process);
+
+/// @brief Get the standard output file for a process.
+/// @param process The process to query.
+/// @return The file for standard output of the process.
+///
+/// The file returned can be read from by the parent process to read data from
+/// the standard output of the child process.
+process_pure process_weak FILE *
+process_stdout(const struct process_s *const process);
+
+/// @brief Get the standard error file for a process.
+/// @param process The process to query.
+/// @return The file for standard error of the process.
+///
+/// The file returned can be read from by the parent process to read data from
+/// the standard error of the child process.
+///
+/// If the process was created with the process_option_combined_stdout_stderr
+/// option bit set, this function will return NULL, and the process_stdout
+/// function should be used for both the standard output and error combined.
+process_pure process_weak FILE *
+process_stderr(const struct process_s *const process);
+
+/// @brief Wait for a process to finish execution.
+/// @param process The process to wait for.
+/// @param out_return_code The return code of the returned process (can be
+/// NULL).
+/// @return On success 0 is returned.
+process_weak int process_join(struct process_s *const process,
+ int *const out_return_code);
+
+/// @brief Destroy a previously created process.
+/// @param process The process to destroy.
+///
+/// If the process to be destroyed had not finished execution, it may out live
+/// the parent process.
+process_weak int process_destroy(struct process_s *const process);
+
+int process_create(const char *const commandLine[], int options,
+ struct process_s *const out_process) {
+#if defined(_MSC_VER)
+ int fd;
+ void *rd, *wr;
+ char *commandLineCombined;
+ size_t len;
+ int i, j;
+ const unsigned long startFUseStdHandles = 0x00000100;
+ const unsigned long handleFlagInherit = 0x00000001;
+ struct process_process_information_s processInfo;
+ struct process_security_attributes_s saAttr = {sizeof(saAttr), 0, 1};
+ char *environment = 0;
+ struct process_startup_info_s startInfo = {0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+ startInfo.cb = sizeof(startInfo);
+ startInfo.dwFlags = startFUseStdHandles;
+
+ if (process_option_inherit_environment !=
+ (options & process_option_inherit_environment)) {
+ environment = "\0\0";
+ }
+
+ if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) {
+ return -1;
+ }
+
+ if (!SetHandleInformation(wr, handleFlagInherit, 0)) {
+ return -1;
+ }
+
+ fd = _open_osfhandle((intptr_t)wr, 0);
+
+ if (-1 != fd) {
+ out_process->stdin_file = _fdopen(fd, "wb");
+
+ if (0 == out_process->stdin_file) {
+ return -1;
+ }
+ }
+
+ startInfo.hStdInput = rd;
+
+ if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) {
+ return -1;
+ }
+
+ if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
+ return -1;
+ }
+
+ fd = _open_osfhandle((intptr_t)rd, 0);
+
+ if (-1 != fd) {
+ out_process->stdout_file = _fdopen(fd, "rb");
+
+ if (0 == out_process->stdout_file) {
+ return -1;
+ }
+ }
+
+ startInfo.hStdOutput = wr;
+
+ if (process_option_combined_stdout_stderr ==
+ (options & process_option_combined_stdout_stderr)) {
+ out_process->stderr_file = out_process->stdout_file;
+ startInfo.hStdError = startInfo.hStdOutput;
+ } else {
+ if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) {
+ return -1;
+ }
+
+ if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
+ return -1;
+ }
+
+ fd = _open_osfhandle((intptr_t)rd, 0);
+
+ if (-1 != fd) {
+ out_process->stderr_file = _fdopen(fd, "rb");
+
+ if (0 == out_process->stderr_file) {
+ return -1;
+ }
+ }
+
+ startInfo.hStdError = wr;
+ }
+
+ // Combine commandLine together into a single string
+ len = 0;
+ for (i = 0; commandLine[i]; i++) {
+ // For the ' ' between items and trailing '\0'
+ len++;
+
+ for (j = 0; '\0' != commandLine[i][j]; j++) {
+ len++;
+ }
+ }
+
+ commandLineCombined = (char *)_alloca(len);
+
+ if (!commandLineCombined) {
+ return -1;
+ }
+
+ // Gonna re-use len to store the write index into commandLineCombined
+ len = 0;
+
+ for (i = 0; commandLine[i]; i++) {
+ if (0 != i) {
+ commandLineCombined[len++] = ' ';
+ }
+
+ for (j = 0; '\0' != commandLine[i][j]; j++) {
+ commandLineCombined[len++] = commandLine[i][j];
+ }
+ }
+
+ commandLineCombined[len] = '\0';
+
+ if (!CreateProcessA(NULL,
+ commandLineCombined, // command line
+ NULL, // process security attributes
+ NULL, // primary thread security attributes
+ 1, // handles are inherited
+ 0, // creation flags
+ environment, // use parent's environment
+ NULL, // use parent's current directory
+ (LPSTARTUPINFOA)&startInfo, // STARTUPINFO pointer
+ (LPPROCESS_INFORMATION)&processInfo)) {
+ return -1;
+ }
+
+ out_process->hProcess = processInfo.hProcess;
+
+ out_process->hStdInput = startInfo.hStdInput;
+ out_process->hStdOutput = startInfo.hStdOutput;
+ out_process->hStdError = startInfo.hStdError;
+
+ // We don't need the handle of the primary thread in the called process.
+ CloseHandle(processInfo.hThread);
+
+ return 0;
+#else
+ int stdinfd[2];
+ int stdoutfd[2];
+ int stderrfd[2];
+ pid_t child;
+
+ if (0 != pipe(stdinfd)) {
+ return -1;
+ }
+
+ if (0 != pipe(stdoutfd)) {
+ return -1;
+ }
+
+ if (process_option_combined_stdout_stderr !=
+ (options & process_option_combined_stdout_stderr)) {
+ if (0 != pipe(stderrfd)) {
+ return -1;
+ }
+ }
+
+ child = fork();
+
+ if (-1 == child) {
+ return -1;
+ }
+
+ if (0 == child) {
+ // Close the stdin write end
+ close(stdinfd[1]);
+ // Map the read end to stdin
+ dup2(stdinfd[0], STDIN_FILENO);
+
+ // Close the stdout read end
+ close(stdoutfd[0]);
+ // Map the write end to stdout
+ dup2(stdoutfd[1], STDOUT_FILENO);
+
+ if (process_option_combined_stdout_stderr ==
+ (options & process_option_combined_stdout_stderr)) {
+ dup2(STDOUT_FILENO, STDERR_FILENO);
+ } else {
+ // Close the stderr read end
+ close(stderrfd[0]);
+ // Map the write end to stdout
+ dup2(stderrfd[1], STDERR_FILENO);
+ }
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wcast-qual"
+#pragma clang diagnostic ignored "-Wold-style-cast"
+#endif
+ if (process_option_inherit_environment !=
+ (options & process_option_inherit_environment)) {
+ char *const environment[1] = {0};
+ exit(execve(commandLine[0], (char *const *)commandLine, environment));
+ } else {
+ exit(execvp(commandLine[0], (char *const *)commandLine));
+ }
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+ } else {
+ // Close the stdin read end
+ close(stdinfd[0]);
+ // Store the stdin write end
+ out_process->stdin_file = fdopen(stdinfd[1], "wb");
+
+ // Close the stdout write end
+ close(stdoutfd[1]);
+ // Store the stdout read end
+ out_process->stdout_file = fdopen(stdoutfd[0], "rb");
+
+ if (process_option_combined_stdout_stderr ==
+ (options & process_option_combined_stdout_stderr)) {
+ out_process->stderr_file = out_process->stdout_file;
+ } else {
+ // Close the stderr write end
+ close(stderrfd[1]);
+ // Store the stderr read end
+ out_process->stderr_file = fdopen(stderrfd[0], "rb");
+ }
+
+ // Store the child's pid
+ out_process->child = child;
+
+ return 0;
+ }
+#endif
+}
+
+FILE *process_stdin(const struct process_s *const process) {
+ return process->stdin_file;
+}
+
+FILE *process_stdout(const struct process_s *const process) {
+ return process->stdout_file;
+}
+
+FILE *process_stderr(const struct process_s *const process) {
+ if (process->stdout_file != process->stderr_file) {
+ return process->stderr_file;
+ } else {
+ return 0;
+ }
+}
+
+int process_join(struct process_s *const process, int *const out_return_code) {
+#if defined(_MSC_VER)
+ const unsigned long infinite = 0xFFFFFFFF;
+
+ if (0 != process->stdin_file) {
+ fclose(process->stdin_file);
+ process->stdin_file = 0;
+ }
+ if (0 != process->hStdInput) {
+ CloseHandle(process->hStdInput);
+ process->hStdInput = NULL;
+ }
+
+ WaitForSingleObject(process->hProcess, infinite);
+
+ if (0 != process->hStdOutput) {
+ CloseHandle(process->hStdOutput);
+ if (process->hStdError == process->hStdOutput) {
+ process->hStdError = NULL;
+ }
+ process->hStdOutput = NULL;
+ }
+ if (0 != process->hStdError) {
+ CloseHandle(process->hStdError);
+ process->hStdError = NULL;
+ }
+
+ if (out_return_code) {
+ if (!GetExitCodeProcess(process->hProcess,
+ (unsigned long *)out_return_code)) {
+ return -1;
+ }
+ }
+
+ return 0;
+#else
+ int status;
+
+ if (0 != process->stdin_file) {
+ fclose(process->stdin_file);
+ process->stdin_file = 0;
+ }
+
+ if (process->child != waitpid(process->child, &status, 0)) {
+ return -1;
+ }
+
+ if (out_return_code) {
+ if (WIFEXITED(status)) {
+ *out_return_code = WEXITSTATUS(status);
+ } else {
+ *out_return_code = 0;
+ }
+ }
+
+ return 0;
+#endif
+}
+
+int process_destroy(struct process_s *const process) {
+ if (0 != process->stdin_file) {
+ fclose(process->stdin_file);
+ }
+
+ fclose(process->stdout_file);
+
+ if (process->stdout_file != process->stderr_file) {
+ fclose(process->stderr_file);
+ }
+
+#if defined(_MSC_VER)
+ CloseHandle(process->hProcess);
+
+ if (0 != process->hStdInput) {
+ CloseHandle(process->hStdInput);
+ process->hStdInput = NULL;
+ }
+ if (0 != process->hStdOutput) {
+ CloseHandle(process->hStdOutput);
+ if (process->hStdError == process->hStdOutput) {
+ process->hStdError = NULL;
+ }
+ process->hStdOutput = NULL;
+ }
+ if (0 != process->hStdError) {
+ CloseHandle(process->hStdError);
+ process->hStdError = NULL;
+ }
+#endif
+
+ return 0;
+}
+
+#if defined(__cplusplus)
+} // extern "C"
+#endif
+
+#endif /* SHEREDOM_PROCESS_H_INCLUDED */
diff --git a/test/main.c b/test/main.c
index f6c50cc..4b5bbcd 100644
--- a/test/main.c
+++ b/test/main.c
@@ -24,5 +24,50 @@
// For more information, please refer to
#include "utest.h"
+#include "process.h"
+
+UTEST(utest_cmdline, filter_with_list) {
+ struct process_s process;
+ const char *command[3] = {"utest_test", "--list-tests", 0};
+ int return_code;
+ FILE *stdout_file;
+ size_t index;
+
+// 64k should be enough for anyone
+#define MAX_CHARS (64 * 1024)
+ char buffer[MAX_CHARS] = {0};
+
+ ASSERT_EQ(0, process_create(command, process_option_combined_stdout_stderr,
+ &process));
+
+ stdout_file = process_stdout(&process);
+
+ for (index = 0; index < utest_state.tests_length; index++) {
+ if (buffer != fgets(buffer, MAX_CHARS, stdout_file)) {
+ break;
+ }
+
+#if defined(__clang__)
+#if __has_warning("-Wdisabled-macro-expansion")
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
+#endif
+#endif
+
+ ASSERT_EQ(0, strncmp(buffer, utest_state.tests[index].name,
+ strlen(utest_state.tests[index].name)));
+
+#if defined(__clang__)
+#if __has_warning("-Wdisabled-macro-expansion")
+#pragma clang diagnostic pop
+#endif
+#endif
+ }
+
+ ASSERT_EQ(0, process_join(&process, &return_code));
+ ASSERT_EQ(0, return_code);
+
+ ASSERT_EQ(0, process_destroy(&process));
+}
UTEST_MAIN()
diff --git a/test/process.h b/test/process.h
new file mode 100644
index 0000000..d3e1a97
--- /dev/null
+++ b/test/process.h
@@ -0,0 +1,568 @@
+/*
+ The latest version of this library is available on GitHub;
+ https://github.com/sheredom/process.h
+*/
+
+/*
+ This is free and unencumbered software released into the public domain.
+
+ Anyone is free to copy, modify, publish, use, compile, sell, or
+ distribute this software, either in source code form or as a compiled
+ binary, for any purpose, commercial or non-commercial, and by any
+ means.
+
+ In jurisdictions that recognize copyright laws, the author or authors
+ of this software dedicate any and all copyright interest in the
+ software to the public domain. We make this dedication for the benefit
+ of the public at large and to the detriment of our heirs and
+ successors. We intend this dedication to be an overt act of
+ relinquishment in perpetuity of all present and future rights to this
+ software under copyright law.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+
+ For more information, please refer to
+*/
+
+#ifndef SHEREDOM_PROCESS_H_INCLUDED
+#define SHEREDOM_PROCESS_H_INCLUDED
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#if defined(_MSC_VER)
+#pragma warning(push, 1)
+#endif
+
+#include
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+#if !defined(_MSC_VER)
+#include
+#include
+#include
+#include
+#endif
+
+#if defined(_MSC_VER)
+#ifdef _WIN64
+typedef __int64 intptr_t;
+typedef unsigned __int64 size_t;
+#else
+typedef int intptr_t;
+typedef unsigned int size_t;
+#endif
+
+typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION;
+typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES;
+typedef struct _STARTUPINFOA *LPSTARTUPINFOA;
+
+#pragma warning(push, 1)
+struct process_process_information_s {
+ void *hProcess;
+ void *hThread;
+ unsigned long dwProcessId;
+ unsigned long dwThreadId;
+};
+
+struct process_security_attributes_s {
+ unsigned long nLength;
+ void *lpSecurityDescriptor;
+ int bInheritHandle;
+};
+
+struct process_startup_info_s {
+ unsigned long cb;
+ char *lpReserved;
+ char *lpDesktop;
+ char *lpTitle;
+ unsigned long dwX;
+ unsigned long dwY;
+ unsigned long dwXSize;
+ unsigned long dwYSize;
+ unsigned long dwXCountChars;
+ unsigned long dwYCountChars;
+ unsigned long dwFillAttribute;
+ unsigned long dwFlags;
+ unsigned short wShowWindow;
+ unsigned short cbReserved2;
+ unsigned char *lpReserved2;
+ void *hStdInput;
+ void *hStdOutput;
+ void *hStdError;
+};
+#pragma warning(pop)
+
+__declspec(dllimport) int __stdcall SetHandleInformation(void *, unsigned long,
+ unsigned long);
+__declspec(dllimport) int __stdcall CreatePipe(void **, void **,
+ LPSECURITY_ATTRIBUTES,
+ unsigned long);
+__declspec(dllimport) int __stdcall CreateProcessA(
+ const char *, char *, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, int,
+ unsigned long, void *, const char *, LPSTARTUPINFOA, LPPROCESS_INFORMATION);
+__declspec(dllimport) int __stdcall CloseHandle(void *);
+__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(
+ void *, unsigned long);
+__declspec(dllimport) int __stdcall GetExitCodeProcess(
+ void *, unsigned long *lpExitCode);
+__declspec(dllimport) int __cdecl _open_osfhandle(intptr_t, int);
+void *__cdecl _alloca(size_t);
+#endif
+
+#if defined(__clang__) || defined(__GNUC__)
+#define process_pure __attribute__((pure))
+#define process_weak __attribute__((weak))
+#elif defined(_MSC_VER)
+#define process_pure
+#define process_weak __inline
+#else
+#error Non clang, non gcc, non MSVC compiler found!
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+struct process_s {
+ FILE *stdin_file;
+ FILE *stdout_file;
+ FILE *stderr_file;
+
+#if defined(_MSC_VER)
+ void *hProcess;
+ void *hStdInput;
+ void *hStdOutput;
+ void *hStdError;
+#else
+ pid_t child;
+#endif
+};
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+enum process_option_e {
+ // stdout and stderr are the same FILE.
+ process_option_combined_stdout_stderr = 0x1,
+
+ // The child process should inherit the environment variables of the parent.
+ process_option_inherit_environment = 0x2
+};
+
+/// @brief Create a process.
+/// @param command_line An array of strings for the command line to execute for
+/// this process. The last element must be NULL to signify the end of the array.
+/// @param options A bit field of process_option_e's to pass.
+/// @param out_process The newly created process.
+/// @return On success 0 is returned.
+process_weak int process_create(const char *const command_line[], int options,
+ struct process_s *const out_process);
+
+/// @brief Get the standard input file for a process.
+/// @param process The process to query.
+/// @return The file for standard input of the process.
+///
+/// The file returned can be written to by the parent process to feed data to
+/// the standard input of the process.
+process_pure process_weak FILE *
+process_stdin(const struct process_s *const process);
+
+/// @brief Get the standard output file for a process.
+/// @param process The process to query.
+/// @return The file for standard output of the process.
+///
+/// The file returned can be read from by the parent process to read data from
+/// the standard output of the child process.
+process_pure process_weak FILE *
+process_stdout(const struct process_s *const process);
+
+/// @brief Get the standard error file for a process.
+/// @param process The process to query.
+/// @return The file for standard error of the process.
+///
+/// The file returned can be read from by the parent process to read data from
+/// the standard error of the child process.
+///
+/// If the process was created with the process_option_combined_stdout_stderr
+/// option bit set, this function will return NULL, and the process_stdout
+/// function should be used for both the standard output and error combined.
+process_pure process_weak FILE *
+process_stderr(const struct process_s *const process);
+
+/// @brief Wait for a process to finish execution.
+/// @param process The process to wait for.
+/// @param out_return_code The return code of the returned process (can be
+/// NULL).
+/// @return On success 0 is returned.
+process_weak int process_join(struct process_s *const process,
+ int *const out_return_code);
+
+/// @brief Destroy a previously created process.
+/// @param process The process to destroy.
+///
+/// If the process to be destroyed had not finished execution, it may out live
+/// the parent process.
+process_weak int process_destroy(struct process_s *const process);
+
+int process_create(const char *const commandLine[], int options,
+ struct process_s *const out_process) {
+#if defined(_MSC_VER)
+ int fd;
+ void *rd, *wr;
+ char *commandLineCombined;
+ size_t len;
+ int i, j;
+ const unsigned long startFUseStdHandles = 0x00000100;
+ const unsigned long handleFlagInherit = 0x00000001;
+ struct process_process_information_s processInfo;
+ struct process_security_attributes_s saAttr = {sizeof(saAttr), 0, 1};
+ char *environment = 0;
+ struct process_startup_info_s startInfo = {0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+ startInfo.cb = sizeof(startInfo);
+ startInfo.dwFlags = startFUseStdHandles;
+
+ if (process_option_inherit_environment !=
+ (options & process_option_inherit_environment)) {
+ environment = "\0\0";
+ }
+
+ if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) {
+ return -1;
+ }
+
+ if (!SetHandleInformation(wr, handleFlagInherit, 0)) {
+ return -1;
+ }
+
+ fd = _open_osfhandle((intptr_t)wr, 0);
+
+ if (-1 != fd) {
+ out_process->stdin_file = _fdopen(fd, "wb");
+
+ if (0 == out_process->stdin_file) {
+ return -1;
+ }
+ }
+
+ startInfo.hStdInput = rd;
+
+ if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) {
+ return -1;
+ }
+
+ if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
+ return -1;
+ }
+
+ fd = _open_osfhandle((intptr_t)rd, 0);
+
+ if (-1 != fd) {
+ out_process->stdout_file = _fdopen(fd, "rb");
+
+ if (0 == out_process->stdout_file) {
+ return -1;
+ }
+ }
+
+ startInfo.hStdOutput = wr;
+
+ if (process_option_combined_stdout_stderr ==
+ (options & process_option_combined_stdout_stderr)) {
+ out_process->stderr_file = out_process->stdout_file;
+ startInfo.hStdError = startInfo.hStdOutput;
+ } else {
+ if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) {
+ return -1;
+ }
+
+ if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
+ return -1;
+ }
+
+ fd = _open_osfhandle((intptr_t)rd, 0);
+
+ if (-1 != fd) {
+ out_process->stderr_file = _fdopen(fd, "rb");
+
+ if (0 == out_process->stderr_file) {
+ return -1;
+ }
+ }
+
+ startInfo.hStdError = wr;
+ }
+
+ // Combine commandLine together into a single string
+ len = 0;
+ for (i = 0; commandLine[i]; i++) {
+ // For the ' ' between items and trailing '\0'
+ len++;
+
+ for (j = 0; '\0' != commandLine[i][j]; j++) {
+ len++;
+ }
+ }
+
+ commandLineCombined = (char *)_alloca(len);
+
+ if (!commandLineCombined) {
+ return -1;
+ }
+
+ // Gonna re-use len to store the write index into commandLineCombined
+ len = 0;
+
+ for (i = 0; commandLine[i]; i++) {
+ if (0 != i) {
+ commandLineCombined[len++] = ' ';
+ }
+
+ for (j = 0; '\0' != commandLine[i][j]; j++) {
+ commandLineCombined[len++] = commandLine[i][j];
+ }
+ }
+
+ commandLineCombined[len] = '\0';
+
+ if (!CreateProcessA(NULL,
+ commandLineCombined, // command line
+ NULL, // process security attributes
+ NULL, // primary thread security attributes
+ 1, // handles are inherited
+ 0, // creation flags
+ environment, // use parent's environment
+ NULL, // use parent's current directory
+ (LPSTARTUPINFOA)&startInfo, // STARTUPINFO pointer
+ (LPPROCESS_INFORMATION)&processInfo)) {
+ return -1;
+ }
+
+ out_process->hProcess = processInfo.hProcess;
+
+ out_process->hStdInput = startInfo.hStdInput;
+ out_process->hStdOutput = startInfo.hStdOutput;
+ out_process->hStdError = startInfo.hStdError;
+
+ // We don't need the handle of the primary thread in the called process.
+ CloseHandle(processInfo.hThread);
+
+ return 0;
+#else
+ int stdinfd[2];
+ int stdoutfd[2];
+ int stderrfd[2];
+ pid_t child;
+
+ if (0 != pipe(stdinfd)) {
+ return -1;
+ }
+
+ if (0 != pipe(stdoutfd)) {
+ return -1;
+ }
+
+ if (process_option_combined_stdout_stderr !=
+ (options & process_option_combined_stdout_stderr)) {
+ if (0 != pipe(stderrfd)) {
+ return -1;
+ }
+ }
+
+ child = fork();
+
+ if (-1 == child) {
+ return -1;
+ }
+
+ if (0 == child) {
+ // Close the stdin write end
+ close(stdinfd[1]);
+ // Map the read end to stdin
+ dup2(stdinfd[0], STDIN_FILENO);
+
+ // Close the stdout read end
+ close(stdoutfd[0]);
+ // Map the write end to stdout
+ dup2(stdoutfd[1], STDOUT_FILENO);
+
+ if (process_option_combined_stdout_stderr ==
+ (options & process_option_combined_stdout_stderr)) {
+ dup2(STDOUT_FILENO, STDERR_FILENO);
+ } else {
+ // Close the stderr read end
+ close(stderrfd[0]);
+ // Map the write end to stdout
+ dup2(stderrfd[1], STDERR_FILENO);
+ }
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wcast-qual"
+#pragma clang diagnostic ignored "-Wold-style-cast"
+#endif
+ if (process_option_inherit_environment !=
+ (options & process_option_inherit_environment)) {
+ char *const environment[1] = {0};
+ exit(execve(commandLine[0], (char *const *)commandLine, environment));
+ } else {
+ exit(execvp(commandLine[0], (char *const *)commandLine));
+ }
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+ } else {
+ // Close the stdin read end
+ close(stdinfd[0]);
+ // Store the stdin write end
+ out_process->stdin_file = fdopen(stdinfd[1], "wb");
+
+ // Close the stdout write end
+ close(stdoutfd[1]);
+ // Store the stdout read end
+ out_process->stdout_file = fdopen(stdoutfd[0], "rb");
+
+ if (process_option_combined_stdout_stderr ==
+ (options & process_option_combined_stdout_stderr)) {
+ out_process->stderr_file = out_process->stdout_file;
+ } else {
+ // Close the stderr write end
+ close(stderrfd[1]);
+ // Store the stderr read end
+ out_process->stderr_file = fdopen(stderrfd[0], "rb");
+ }
+
+ // Store the child's pid
+ out_process->child = child;
+
+ return 0;
+ }
+#endif
+}
+
+FILE *process_stdin(const struct process_s *const process) {
+ return process->stdin_file;
+}
+
+FILE *process_stdout(const struct process_s *const process) {
+ return process->stdout_file;
+}
+
+FILE *process_stderr(const struct process_s *const process) {
+ if (process->stdout_file != process->stderr_file) {
+ return process->stderr_file;
+ } else {
+ return 0;
+ }
+}
+
+int process_join(struct process_s *const process, int *const out_return_code) {
+#if defined(_MSC_VER)
+ const unsigned long infinite = 0xFFFFFFFF;
+
+ if (0 != process->stdin_file) {
+ fclose(process->stdin_file);
+ process->stdin_file = 0;
+ }
+ if (0 != process->hStdInput) {
+ CloseHandle(process->hStdInput);
+ process->hStdInput = NULL;
+ }
+
+ WaitForSingleObject(process->hProcess, infinite);
+
+ if (0 != process->hStdOutput) {
+ CloseHandle(process->hStdOutput);
+ if (process->hStdError == process->hStdOutput) {
+ process->hStdError = NULL;
+ }
+ process->hStdOutput = NULL;
+ }
+ if (0 != process->hStdError) {
+ CloseHandle(process->hStdError);
+ process->hStdError = NULL;
+ }
+
+ if (out_return_code) {
+ if (!GetExitCodeProcess(process->hProcess,
+ (unsigned long *)out_return_code)) {
+ return -1;
+ }
+ }
+
+ return 0;
+#else
+ int status;
+
+ if (0 != process->stdin_file) {
+ fclose(process->stdin_file);
+ process->stdin_file = 0;
+ }
+
+ if (process->child != waitpid(process->child, &status, 0)) {
+ return -1;
+ }
+
+ if (out_return_code) {
+ if (WIFEXITED(status)) {
+ *out_return_code = WEXITSTATUS(status);
+ } else {
+ *out_return_code = 0;
+ }
+ }
+
+ return 0;
+#endif
+}
+
+int process_destroy(struct process_s *const process) {
+ if (0 != process->stdin_file) {
+ fclose(process->stdin_file);
+ }
+
+ fclose(process->stdout_file);
+
+ if (process->stdout_file != process->stderr_file) {
+ fclose(process->stderr_file);
+ }
+
+#if defined(_MSC_VER)
+ CloseHandle(process->hProcess);
+
+ if (0 != process->hStdInput) {
+ CloseHandle(process->hStdInput);
+ process->hStdInput = NULL;
+ }
+ if (0 != process->hStdOutput) {
+ CloseHandle(process->hStdOutput);
+ if (process->hStdError == process->hStdOutput) {
+ process->hStdError = NULL;
+ }
+ process->hStdOutput = NULL;
+ }
+ if (0 != process->hStdError) {
+ CloseHandle(process->hStdError);
+ process->hStdError = NULL;
+ }
+#endif
+
+ return 0;
+}
+
+#if defined(__cplusplus)
+} // extern "C"
+#endif
+
+#endif /* SHEREDOM_PROCESS_H_INCLUDED */
diff --git a/test/test.c b/test/test.c
index 9426c1e..b85ae16 100644
--- a/test/test.c
+++ b/test/test.c
@@ -231,74 +231,3 @@
static const int data[4] = {42, 13, 6, -53};
UTEST(c, Array) { EXPECT_NE(data, data + 1); }
-
-#ifdef __linux__
-#include
-/** Check that the output from --list-tests matches our internal view of the
- * listed tests. This means calling ourselves recursively and filtering the
- * output, so it's hacky. To do this, we need to get the full path to the
- * current program, there is a different way to get this information on nearly
- * every OS, but the thing we're testing is platform independent, so we should
- * be able to get away with just Linux here
-*/
-static int testname_cmp(const void *x_, const void *y_) {
- const char *x = *(const char *const*)x_;
- const char *y = *(const char *const*)y_;
- return strcmp(x, y);
-}
-
-/* Some bright spark decided -Werror and -Weverything should be used together,
- * which breaks idiomatic code... */
-
-#ifdef __clang__
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wcomma"
-#endif
-UTEST(utest_cmdline, filter_with_list) {
- // 64k should be enough for anyone
- char exe_buf[64 * 1024] = {0}, cmd_buf[64 * 1024] = {0};
- ssize_t path_len = readlink("/proc/self/exe", exe_buf, sizeof exe_buf);
- int count, ret;
- FILE *ps;
- const char **test_names;
- size_t n = 0, i = 0;
- char *lineptr;
- ASSERT_NE(path_len, UTEST_CAST(ssize_t, -1));
- count = snprintf(cmd_buf, sizeof cmd_buf, "%s --list-tests", exe_buf);
- ASSERT_EQ(errno, 0);
- ASSERT_LT(UTEST_CAST(size_t, count), sizeof cmd_buf);
-
- ps = popen(cmd_buf, "r");
- ASSERT_TRUE(ps);
-
- test_names =
- malloc(utest_state.tests_length * sizeof test_names[0]);
- ASSERT_TRUE(test_names);
-
- for (i = 0; i < utest_state.tests_length; ++i) {
- test_names[i] = utest_state.tests[i].name;
- }
- qsort(test_names, utest_state.tests_length, sizeof test_names[0],
- testname_cmp);
-
- lineptr = malloc(1);
- ASSERT_TRUE(lineptr);
- for (i = 0, n = 0; getline(&lineptr, &n, ps) != (ssize_t)-1; n = 0, ++i) {
- /* remove the terminating newline */
- const char **name;
- char *nl = strchr(lineptr, '\n');
- EXPECT_TRUE(nl);
- *nl = '\0';
-
- name = bsearch(&lineptr, test_names, utest_state.tests_length,
- sizeof test_names[0], testname_cmp);
- EXPECT_TRUE(name);
- EXPECT_STREQ(*name, lineptr);
- }
- EXPECT_EQ(i, utest_state.tests_length);
- free(lineptr);
- free(test_names);
- ret = pclose(ps);
- ASSERT_EQ(ret, 0);
-}
-#endif
diff --git a/test/main.c b/test/main.c
index f6c50cc..4b5bbcd 100644
--- a/test/main.c
+++ b/test/main.c
@@ -24,5 +24,50 @@
// For more information, please refer to
#include "utest.h"
+#include "process.h"
+
+UTEST(utest_cmdline, filter_with_list) {
+ struct process_s process;
+ const char *command[3] = {"utest_test", "--list-tests", 0};
+ int return_code;
+ FILE *stdout_file;
+ size_t index;
+
+// 64k should be enough for anyone
+#define MAX_CHARS (64 * 1024)
+ char buffer[MAX_CHARS] = {0};
+
+ ASSERT_EQ(0, process_create(command, process_option_combined_stdout_stderr,
+ &process));
+
+ stdout_file = process_stdout(&process);
+
+ for (index = 0; index < utest_state.tests_length; index++) {
+ if (buffer != fgets(buffer, MAX_CHARS, stdout_file)) {
+ break;
+ }
+
+#if defined(__clang__)
+#if __has_warning("-Wdisabled-macro-expansion")
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
+#endif
+#endif
+
+ ASSERT_EQ(0, strncmp(buffer, utest_state.tests[index].name,
+ strlen(utest_state.tests[index].name)));
+
+#if defined(__clang__)
+#if __has_warning("-Wdisabled-macro-expansion")
+#pragma clang diagnostic pop
+#endif
+#endif
+ }
+
+ ASSERT_EQ(0, process_join(&process, &return_code));
+ ASSERT_EQ(0, return_code);
+
+ ASSERT_EQ(0, process_destroy(&process));
+}
UTEST_MAIN()
diff --git a/test/process.h b/test/process.h
new file mode 100644
index 0000000..d3e1a97
--- /dev/null
+++ b/test/process.h
@@ -0,0 +1,568 @@
+/*
+ The latest version of this library is available on GitHub;
+ https://github.com/sheredom/process.h
+*/
+
+/*
+ This is free and unencumbered software released into the public domain.
+
+ Anyone is free to copy, modify, publish, use, compile, sell, or
+ distribute this software, either in source code form or as a compiled
+ binary, for any purpose, commercial or non-commercial, and by any
+ means.
+
+ In jurisdictions that recognize copyright laws, the author or authors
+ of this software dedicate any and all copyright interest in the
+ software to the public domain. We make this dedication for the benefit
+ of the public at large and to the detriment of our heirs and
+ successors. We intend this dedication to be an overt act of
+ relinquishment in perpetuity of all present and future rights to this
+ software under copyright law.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
+
+ For more information, please refer to
+*/
+
+#ifndef SHEREDOM_PROCESS_H_INCLUDED
+#define SHEREDOM_PROCESS_H_INCLUDED
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#if defined(_MSC_VER)
+#pragma warning(push, 1)
+#endif
+
+#include
+
+#if defined(_MSC_VER)
+#pragma warning(pop)
+#endif
+
+#if !defined(_MSC_VER)
+#include
+#include
+#include
+#include
+#endif
+
+#if defined(_MSC_VER)
+#ifdef _WIN64
+typedef __int64 intptr_t;
+typedef unsigned __int64 size_t;
+#else
+typedef int intptr_t;
+typedef unsigned int size_t;
+#endif
+
+typedef struct _PROCESS_INFORMATION *LPPROCESS_INFORMATION;
+typedef struct _SECURITY_ATTRIBUTES *LPSECURITY_ATTRIBUTES;
+typedef struct _STARTUPINFOA *LPSTARTUPINFOA;
+
+#pragma warning(push, 1)
+struct process_process_information_s {
+ void *hProcess;
+ void *hThread;
+ unsigned long dwProcessId;
+ unsigned long dwThreadId;
+};
+
+struct process_security_attributes_s {
+ unsigned long nLength;
+ void *lpSecurityDescriptor;
+ int bInheritHandle;
+};
+
+struct process_startup_info_s {
+ unsigned long cb;
+ char *lpReserved;
+ char *lpDesktop;
+ char *lpTitle;
+ unsigned long dwX;
+ unsigned long dwY;
+ unsigned long dwXSize;
+ unsigned long dwYSize;
+ unsigned long dwXCountChars;
+ unsigned long dwYCountChars;
+ unsigned long dwFillAttribute;
+ unsigned long dwFlags;
+ unsigned short wShowWindow;
+ unsigned short cbReserved2;
+ unsigned char *lpReserved2;
+ void *hStdInput;
+ void *hStdOutput;
+ void *hStdError;
+};
+#pragma warning(pop)
+
+__declspec(dllimport) int __stdcall SetHandleInformation(void *, unsigned long,
+ unsigned long);
+__declspec(dllimport) int __stdcall CreatePipe(void **, void **,
+ LPSECURITY_ATTRIBUTES,
+ unsigned long);
+__declspec(dllimport) int __stdcall CreateProcessA(
+ const char *, char *, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, int,
+ unsigned long, void *, const char *, LPSTARTUPINFOA, LPPROCESS_INFORMATION);
+__declspec(dllimport) int __stdcall CloseHandle(void *);
+__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(
+ void *, unsigned long);
+__declspec(dllimport) int __stdcall GetExitCodeProcess(
+ void *, unsigned long *lpExitCode);
+__declspec(dllimport) int __cdecl _open_osfhandle(intptr_t, int);
+void *__cdecl _alloca(size_t);
+#endif
+
+#if defined(__clang__) || defined(__GNUC__)
+#define process_pure __attribute__((pure))
+#define process_weak __attribute__((weak))
+#elif defined(_MSC_VER)
+#define process_pure
+#define process_weak __inline
+#else
+#error Non clang, non gcc, non MSVC compiler found!
+#endif
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpadded"
+#endif
+struct process_s {
+ FILE *stdin_file;
+ FILE *stdout_file;
+ FILE *stderr_file;
+
+#if defined(_MSC_VER)
+ void *hProcess;
+ void *hStdInput;
+ void *hStdOutput;
+ void *hStdError;
+#else
+ pid_t child;
+#endif
+};
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+enum process_option_e {
+ // stdout and stderr are the same FILE.
+ process_option_combined_stdout_stderr = 0x1,
+
+ // The child process should inherit the environment variables of the parent.
+ process_option_inherit_environment = 0x2
+};
+
+/// @brief Create a process.
+/// @param command_line An array of strings for the command line to execute for
+/// this process. The last element must be NULL to signify the end of the array.
+/// @param options A bit field of process_option_e's to pass.
+/// @param out_process The newly created process.
+/// @return On success 0 is returned.
+process_weak int process_create(const char *const command_line[], int options,
+ struct process_s *const out_process);
+
+/// @brief Get the standard input file for a process.
+/// @param process The process to query.
+/// @return The file for standard input of the process.
+///
+/// The file returned can be written to by the parent process to feed data to
+/// the standard input of the process.
+process_pure process_weak FILE *
+process_stdin(const struct process_s *const process);
+
+/// @brief Get the standard output file for a process.
+/// @param process The process to query.
+/// @return The file for standard output of the process.
+///
+/// The file returned can be read from by the parent process to read data from
+/// the standard output of the child process.
+process_pure process_weak FILE *
+process_stdout(const struct process_s *const process);
+
+/// @brief Get the standard error file for a process.
+/// @param process The process to query.
+/// @return The file for standard error of the process.
+///
+/// The file returned can be read from by the parent process to read data from
+/// the standard error of the child process.
+///
+/// If the process was created with the process_option_combined_stdout_stderr
+/// option bit set, this function will return NULL, and the process_stdout
+/// function should be used for both the standard output and error combined.
+process_pure process_weak FILE *
+process_stderr(const struct process_s *const process);
+
+/// @brief Wait for a process to finish execution.
+/// @param process The process to wait for.
+/// @param out_return_code The return code of the returned process (can be
+/// NULL).
+/// @return On success 0 is returned.
+process_weak int process_join(struct process_s *const process,
+ int *const out_return_code);
+
+/// @brief Destroy a previously created process.
+/// @param process The process to destroy.
+///
+/// If the process to be destroyed had not finished execution, it may out live
+/// the parent process.
+process_weak int process_destroy(struct process_s *const process);
+
+int process_create(const char *const commandLine[], int options,
+ struct process_s *const out_process) {
+#if defined(_MSC_VER)
+ int fd;
+ void *rd, *wr;
+ char *commandLineCombined;
+ size_t len;
+ int i, j;
+ const unsigned long startFUseStdHandles = 0x00000100;
+ const unsigned long handleFlagInherit = 0x00000001;
+ struct process_process_information_s processInfo;
+ struct process_security_attributes_s saAttr = {sizeof(saAttr), 0, 1};
+ char *environment = 0;
+ struct process_startup_info_s startInfo = {0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+ startInfo.cb = sizeof(startInfo);
+ startInfo.dwFlags = startFUseStdHandles;
+
+ if (process_option_inherit_environment !=
+ (options & process_option_inherit_environment)) {
+ environment = "\0\0";
+ }
+
+ if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) {
+ return -1;
+ }
+
+ if (!SetHandleInformation(wr, handleFlagInherit, 0)) {
+ return -1;
+ }
+
+ fd = _open_osfhandle((intptr_t)wr, 0);
+
+ if (-1 != fd) {
+ out_process->stdin_file = _fdopen(fd, "wb");
+
+ if (0 == out_process->stdin_file) {
+ return -1;
+ }
+ }
+
+ startInfo.hStdInput = rd;
+
+ if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) {
+ return -1;
+ }
+
+ if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
+ return -1;
+ }
+
+ fd = _open_osfhandle((intptr_t)rd, 0);
+
+ if (-1 != fd) {
+ out_process->stdout_file = _fdopen(fd, "rb");
+
+ if (0 == out_process->stdout_file) {
+ return -1;
+ }
+ }
+
+ startInfo.hStdOutput = wr;
+
+ if (process_option_combined_stdout_stderr ==
+ (options & process_option_combined_stdout_stderr)) {
+ out_process->stderr_file = out_process->stdout_file;
+ startInfo.hStdError = startInfo.hStdOutput;
+ } else {
+ if (!CreatePipe(&rd, &wr, (LPSECURITY_ATTRIBUTES)&saAttr, 0)) {
+ return -1;
+ }
+
+ if (!SetHandleInformation(rd, handleFlagInherit, 0)) {
+ return -1;
+ }
+
+ fd = _open_osfhandle((intptr_t)rd, 0);
+
+ if (-1 != fd) {
+ out_process->stderr_file = _fdopen(fd, "rb");
+
+ if (0 == out_process->stderr_file) {
+ return -1;
+ }
+ }
+
+ startInfo.hStdError = wr;
+ }
+
+ // Combine commandLine together into a single string
+ len = 0;
+ for (i = 0; commandLine[i]; i++) {
+ // For the ' ' between items and trailing '\0'
+ len++;
+
+ for (j = 0; '\0' != commandLine[i][j]; j++) {
+ len++;
+ }
+ }
+
+ commandLineCombined = (char *)_alloca(len);
+
+ if (!commandLineCombined) {
+ return -1;
+ }
+
+ // Gonna re-use len to store the write index into commandLineCombined
+ len = 0;
+
+ for (i = 0; commandLine[i]; i++) {
+ if (0 != i) {
+ commandLineCombined[len++] = ' ';
+ }
+
+ for (j = 0; '\0' != commandLine[i][j]; j++) {
+ commandLineCombined[len++] = commandLine[i][j];
+ }
+ }
+
+ commandLineCombined[len] = '\0';
+
+ if (!CreateProcessA(NULL,
+ commandLineCombined, // command line
+ NULL, // process security attributes
+ NULL, // primary thread security attributes
+ 1, // handles are inherited
+ 0, // creation flags
+ environment, // use parent's environment
+ NULL, // use parent's current directory
+ (LPSTARTUPINFOA)&startInfo, // STARTUPINFO pointer
+ (LPPROCESS_INFORMATION)&processInfo)) {
+ return -1;
+ }
+
+ out_process->hProcess = processInfo.hProcess;
+
+ out_process->hStdInput = startInfo.hStdInput;
+ out_process->hStdOutput = startInfo.hStdOutput;
+ out_process->hStdError = startInfo.hStdError;
+
+ // We don't need the handle of the primary thread in the called process.
+ CloseHandle(processInfo.hThread);
+
+ return 0;
+#else
+ int stdinfd[2];
+ int stdoutfd[2];
+ int stderrfd[2];
+ pid_t child;
+
+ if (0 != pipe(stdinfd)) {
+ return -1;
+ }
+
+ if (0 != pipe(stdoutfd)) {
+ return -1;
+ }
+
+ if (process_option_combined_stdout_stderr !=
+ (options & process_option_combined_stdout_stderr)) {
+ if (0 != pipe(stderrfd)) {
+ return -1;
+ }
+ }
+
+ child = fork();
+
+ if (-1 == child) {
+ return -1;
+ }
+
+ if (0 == child) {
+ // Close the stdin write end
+ close(stdinfd[1]);
+ // Map the read end to stdin
+ dup2(stdinfd[0], STDIN_FILENO);
+
+ // Close the stdout read end
+ close(stdoutfd[0]);
+ // Map the write end to stdout
+ dup2(stdoutfd[1], STDOUT_FILENO);
+
+ if (process_option_combined_stdout_stderr ==
+ (options & process_option_combined_stdout_stderr)) {
+ dup2(STDOUT_FILENO, STDERR_FILENO);
+ } else {
+ // Close the stderr read end
+ close(stderrfd[0]);
+ // Map the write end to stdout
+ dup2(stderrfd[1], STDERR_FILENO);
+ }
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wcast-qual"
+#pragma clang diagnostic ignored "-Wold-style-cast"
+#endif
+ if (process_option_inherit_environment !=
+ (options & process_option_inherit_environment)) {
+ char *const environment[1] = {0};
+ exit(execve(commandLine[0], (char *const *)commandLine, environment));
+ } else {
+ exit(execvp(commandLine[0], (char *const *)commandLine));
+ }
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+ } else {
+ // Close the stdin read end
+ close(stdinfd[0]);
+ // Store the stdin write end
+ out_process->stdin_file = fdopen(stdinfd[1], "wb");
+
+ // Close the stdout write end
+ close(stdoutfd[1]);
+ // Store the stdout read end
+ out_process->stdout_file = fdopen(stdoutfd[0], "rb");
+
+ if (process_option_combined_stdout_stderr ==
+ (options & process_option_combined_stdout_stderr)) {
+ out_process->stderr_file = out_process->stdout_file;
+ } else {
+ // Close the stderr write end
+ close(stderrfd[1]);
+ // Store the stderr read end
+ out_process->stderr_file = fdopen(stderrfd[0], "rb");
+ }
+
+ // Store the child's pid
+ out_process->child = child;
+
+ return 0;
+ }
+#endif
+}
+
+FILE *process_stdin(const struct process_s *const process) {
+ return process->stdin_file;
+}
+
+FILE *process_stdout(const struct process_s *const process) {
+ return process->stdout_file;
+}
+
+FILE *process_stderr(const struct process_s *const process) {
+ if (process->stdout_file != process->stderr_file) {
+ return process->stderr_file;
+ } else {
+ return 0;
+ }
+}
+
+int process_join(struct process_s *const process, int *const out_return_code) {
+#if defined(_MSC_VER)
+ const unsigned long infinite = 0xFFFFFFFF;
+
+ if (0 != process->stdin_file) {
+ fclose(process->stdin_file);
+ process->stdin_file = 0;
+ }
+ if (0 != process->hStdInput) {
+ CloseHandle(process->hStdInput);
+ process->hStdInput = NULL;
+ }
+
+ WaitForSingleObject(process->hProcess, infinite);
+
+ if (0 != process->hStdOutput) {
+ CloseHandle(process->hStdOutput);
+ if (process->hStdError == process->hStdOutput) {
+ process->hStdError = NULL;
+ }
+ process->hStdOutput = NULL;
+ }
+ if (0 != process->hStdError) {
+ CloseHandle(process->hStdError);
+ process->hStdError = NULL;
+ }
+
+ if (out_return_code) {
+ if (!GetExitCodeProcess(process->hProcess,
+ (unsigned long *)out_return_code)) {
+ return -1;
+ }
+ }
+
+ return 0;
+#else
+ int status;
+
+ if (0 != process->stdin_file) {
+ fclose(process->stdin_file);
+ process->stdin_file = 0;
+ }
+
+ if (process->child != waitpid(process->child, &status, 0)) {
+ return -1;
+ }
+
+ if (out_return_code) {
+ if (WIFEXITED(status)) {
+ *out_return_code = WEXITSTATUS(status);
+ } else {
+ *out_return_code = 0;
+ }
+ }
+
+ return 0;
+#endif
+}
+
+int process_destroy(struct process_s *const process) {
+ if (0 != process->stdin_file) {
+ fclose(process->stdin_file);
+ }
+
+ fclose(process->stdout_file);
+
+ if (process->stdout_file != process->stderr_file) {
+ fclose(process->stderr_file);
+ }
+
+#if defined(_MSC_VER)
+ CloseHandle(process->hProcess);
+
+ if (0 != process->hStdInput) {
+ CloseHandle(process->hStdInput);
+ process->hStdInput = NULL;
+ }
+ if (0 != process->hStdOutput) {
+ CloseHandle(process->hStdOutput);
+ if (process->hStdError == process->hStdOutput) {
+ process->hStdError = NULL;
+ }
+ process->hStdOutput = NULL;
+ }
+ if (0 != process->hStdError) {
+ CloseHandle(process->hStdError);
+ process->hStdError = NULL;
+ }
+#endif
+
+ return 0;
+}
+
+#if defined(__cplusplus)
+} // extern "C"
+#endif
+
+#endif /* SHEREDOM_PROCESS_H_INCLUDED */
diff --git a/test/test.c b/test/test.c
index 9426c1e..b85ae16 100644
--- a/test/test.c
+++ b/test/test.c
@@ -231,74 +231,3 @@
static const int data[4] = {42, 13, 6, -53};
UTEST(c, Array) { EXPECT_NE(data, data + 1); }
-
-#ifdef __linux__
-#include
-/** Check that the output from --list-tests matches our internal view of the
- * listed tests. This means calling ourselves recursively and filtering the
- * output, so it's hacky. To do this, we need to get the full path to the
- * current program, there is a different way to get this information on nearly
- * every OS, but the thing we're testing is platform independent, so we should
- * be able to get away with just Linux here
-*/
-static int testname_cmp(const void *x_, const void *y_) {
- const char *x = *(const char *const*)x_;
- const char *y = *(const char *const*)y_;
- return strcmp(x, y);
-}
-
-/* Some bright spark decided -Werror and -Weverything should be used together,
- * which breaks idiomatic code... */
-
-#ifdef __clang__
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wcomma"
-#endif
-UTEST(utest_cmdline, filter_with_list) {
- // 64k should be enough for anyone
- char exe_buf[64 * 1024] = {0}, cmd_buf[64 * 1024] = {0};
- ssize_t path_len = readlink("/proc/self/exe", exe_buf, sizeof exe_buf);
- int count, ret;
- FILE *ps;
- const char **test_names;
- size_t n = 0, i = 0;
- char *lineptr;
- ASSERT_NE(path_len, UTEST_CAST(ssize_t, -1));
- count = snprintf(cmd_buf, sizeof cmd_buf, "%s --list-tests", exe_buf);
- ASSERT_EQ(errno, 0);
- ASSERT_LT(UTEST_CAST(size_t, count), sizeof cmd_buf);
-
- ps = popen(cmd_buf, "r");
- ASSERT_TRUE(ps);
-
- test_names =
- malloc(utest_state.tests_length * sizeof test_names[0]);
- ASSERT_TRUE(test_names);
-
- for (i = 0; i < utest_state.tests_length; ++i) {
- test_names[i] = utest_state.tests[i].name;
- }
- qsort(test_names, utest_state.tests_length, sizeof test_names[0],
- testname_cmp);
-
- lineptr = malloc(1);
- ASSERT_TRUE(lineptr);
- for (i = 0, n = 0; getline(&lineptr, &n, ps) != (ssize_t)-1; n = 0, ++i) {
- /* remove the terminating newline */
- const char **name;
- char *nl = strchr(lineptr, '\n');
- EXPECT_TRUE(nl);
- *nl = '\0';
-
- name = bsearch(&lineptr, test_names, utest_state.tests_length,
- sizeof test_names[0], testname_cmp);
- EXPECT_TRUE(name);
- EXPECT_STREQ(*name, lineptr);
- }
- EXPECT_EQ(i, utest_state.tests_length);
- free(lineptr);
- free(test_names);
- ret = pclose(ps);
- ASSERT_EQ(ret, 0);
-}
-#endif
diff --git a/utest.h b/utest.h
index 96c7476..7e7f04f 100644
--- a/utest.h
+++ b/utest.h
@@ -809,7 +809,7 @@
for (index = 0; index < utest_state.tests_length; index++) {
UTEST_PRINTF("%s\n", utest_state.tests[index].name);
}
- /* When printing the test list, don't actually run the tests */
+ /* when printing the test list, don't actually run the tests */
return 0;
}
}