#include #include #include #include #include "selftest.h" #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # include static char *read_full(HANDLE h, int is_pipe) { char *data = NULL; size_t data_size = 0; while (1) { CHAR buf[4096]; DWORD bytes_read; if (!ReadFile(h, buf, sizeof(buf), &bytes_read, NULL)) { if (!is_pipe) cl_fail("Failed reading file handle."); cl_assert_equal_i(GetLastError(), ERROR_BROKEN_PIPE); break; } if (!bytes_read) break; data = realloc(data, data_size + bytes_read); cl_assert(data); memcpy(data + data_size, buf, bytes_read); data_size += bytes_read; } data = realloc(data, data_size + 1); cl_assert(data); data[data_size] = '\0'; while (strstr(data, "\r\n")) { char *ptr = strstr(data, "\r\n"); memmove(ptr, ptr + 1, strlen(ptr)); } return data; } static char *read_file(const char *path) { char *content; HANDLE file; file = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); cl_assert(file != INVALID_HANDLE_VALUE); content = read_full(file, 0); cl_assert_equal_b(1, CloseHandle(file)); return content; } static char *execute(const char *suite, int expected_error_code, const char **args, size_t nargs) { SECURITY_ATTRIBUTES security_attributes = { 0 }; PROCESS_INFORMATION process_info = { 0 }; STARTUPINFO startup_info = { 0 }; char binary_path[4096] = { 0 }; char cmdline[4096] = { 0 }; char *output = NULL; HANDLE stdout_write; HANDLE stdout_read; DWORD exit_code; size_t i; snprintf(binary_path, sizeof(binary_path), "%s/%s_suite.exe", selftest_suite_directory, suite); /* * Assemble command line arguments. In theory we'd have to properly * quote them. In practice none of our tests actually care. */ snprintf(cmdline, sizeof(cmdline), suite); for (i = 0; i < nargs; i++) { size_t cmdline_len = strlen(cmdline); const char *arg = args[i]; cl_assert(cmdline_len + strlen(arg) < sizeof(cmdline)); snprintf(cmdline + cmdline_len, sizeof(cmdline) - cmdline_len, " %s", arg); } /* * Create a pipe that we will use to read data from the child process. * The writing side needs to be inheritable such that the child can use * it as stdout and stderr. The reading side should only be used by the * parent. */ security_attributes.nLength = sizeof(security_attributes); security_attributes.bInheritHandle = TRUE; cl_assert_equal_b(1, CreatePipe(&stdout_read, &stdout_write, &security_attributes, 0)); cl_assert_equal_b(1, SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0)); /* * Create the child process with our pipe. */ startup_info.cb = sizeof(startup_info); startup_info.hStdError = stdout_write; startup_info.hStdOutput = stdout_write; startup_info.dwFlags |= STARTF_USESTDHANDLES; cl_assert_equal_b(1, CreateProcess(binary_path, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &startup_info, &process_info)); cl_assert_equal_b(1, CloseHandle(stdout_write)); output = read_full(stdout_read, 1); cl_assert_equal_b(1, CloseHandle(stdout_read)); cl_assert_equal_b(1, GetExitCodeProcess(process_info.hProcess, &exit_code)); cl_assert_equal_i(exit_code, expected_error_code); return output; } static void assert_output(const char *suite, const char *expected_output_file, int expected_error_code, ...) { char *expected_output = NULL; char *output = NULL; const char *args[16]; va_list ap; size_t i; va_start(ap, expected_error_code); for (i = 0; ; i++) { const char *arg = va_arg(ap, const char *); if (!arg) break; cl_assert(i < sizeof(args) / sizeof(*args)); args[i] = arg; } va_end(ap); output = execute(suite, expected_error_code, args, i); expected_output = read_file(cl_fixture(expected_output_file)); cl_assert_equal_s(output, expected_output); free(expected_output); free(output); } #else # include # include # include # include # include static char *read_full(int fd) { size_t data_bytes = 0; char *data = NULL; while (1) { char buf[4096]; ssize_t n; n = read(fd, buf, sizeof(buf)); if (n < 0) { if (errno == EAGAIN || errno == EINTR) continue; cl_fail("Failed reading from child process."); } if (!n) break; data = realloc(data, data_bytes + n); cl_assert(data); memcpy(data + data_bytes, buf, n); data_bytes += n; } data = realloc(data, data_bytes + 1); cl_assert(data); data[data_bytes] = '\0'; return data; } static char *read_file(const char *path) { char *data; int fd; fd = open(path, O_RDONLY); if (fd < 0) cl_fail("Failed reading expected file."); data = read_full(fd); cl_must_pass(close(fd)); return data; } static char *execute(const char *suite, int expected_error_code, const char **args, size_t nargs) { int pipe_fds[2]; pid_t pid; cl_must_pass(pipe(pipe_fds)); pid = fork(); if (!pid) { const char *final_args[17] = { NULL }; char binary_path[4096]; size_t len = 0; size_t i; cl_assert(nargs < sizeof(final_args) / sizeof(*final_args)); final_args[0] = suite; for (i = 0; i < nargs; i++) final_args[i + 1] = args[i]; if (dup2(pipe_fds[1], STDOUT_FILENO) < 0 || dup2(pipe_fds[1], STDERR_FILENO) < 0 || close(0) < 0 || close(pipe_fds[0]) < 0 || close(pipe_fds[1]) < 0) exit(1); cl_assert(len + strlen(selftest_suite_directory) < sizeof(binary_path)); strcpy(binary_path, selftest_suite_directory); len += strlen(selftest_suite_directory); cl_assert(len + 1 < sizeof(binary_path)); binary_path[len] = '/'; len += 1; cl_assert(len + strlen(suite) < sizeof(binary_path)); strcpy(binary_path + len, suite); len += strlen(suite); cl_assert(len + strlen("_suite") < sizeof(binary_path)); strcpy(binary_path + len, "_suite"); len += strlen("_suite"); binary_path[len] = '\0'; execv(binary_path, (char **) final_args); exit(1); } else if (pid > 0) { pid_t waited_pid; char *output; int stat; cl_must_pass(close(pipe_fds[1])); output = read_full(pipe_fds[0]); waited_pid = waitpid(pid, &stat, 0); cl_assert_equal_i(pid, waited_pid); cl_assert(WIFEXITED(stat)); cl_assert_equal_i(WEXITSTATUS(stat), expected_error_code); return output; } else { cl_fail("Fork failed."); } return NULL; } static void assert_output(const char *suite, const char *expected_output_file, int expected_error_code, ...) { char *expected_output, *output; const char *args[16]; va_list ap; size_t i; va_start(ap, expected_error_code); for (i = 0; ; i++) { cl_assert(i < sizeof(args) / sizeof(*args)); args[i] = va_arg(ap, const char *); if (!args[i]) break; } va_end(ap); output = execute(suite, expected_error_code, args, i); expected_output = read_file(cl_fixture(expected_output_file)); cl_assert_equal_s(output, expected_output); free(expected_output); free(output); } #endif void test_selftest__help(void) { cl_invoke(assert_output("combined", "help", 1, "-h", NULL)); } void test_selftest__without_arguments(void) { cl_invoke(assert_output("combined", "without_arguments", 9, NULL)); } void test_selftest__specific_test(void) { cl_invoke(assert_output("combined", "specific_test", 1, "-scombined::bool", NULL)); } void test_selftest__stop_on_failure(void) { cl_invoke(assert_output("combined", "stop_on_failure", 1, "-Q", NULL)); } void test_selftest__quiet(void) { cl_invoke(assert_output("combined", "quiet", 9, "-q", NULL)); } void test_selftest__tap(void) { cl_invoke(assert_output("combined", "tap", 9, "-t", NULL)); } void test_selftest__suite_names(void) { cl_invoke(assert_output("combined", "suite_names", 0, "-l", NULL)); } void test_selftest__summary_without_filename(void) { struct stat st; cl_invoke(assert_output("combined", "summary_without_filename", 9, "-r", NULL)); /* The summary contains timestamps, so we cannot verify its contents. */ cl_must_pass(stat("summary.xml", &st)); } void test_selftest__summary_with_filename(void) { struct stat st; cl_invoke(assert_output("combined", "summary_with_filename", 9, "-rdifferent.xml", NULL)); /* The summary contains timestamps, so we cannot verify its contents. */ cl_must_pass(stat("different.xml", &st)); } void test_selftest__pointer_equal(void) { const char *args[] = { "-spointer::equal", "-t" }; char *output = execute("pointer", 0, args, 2); cl_assert_equal_s(output, "TAP version 13\n" "# start of suite 1: pointer\n" "ok 1 - pointer::equal\n" "1..1\n" ); free(output); } void test_selftest__pointer_unequal(void) { const char *args[] = { "-spointer::unequal", }; char *output = execute("pointer", 1, args, 1); cl_assert(output); cl_assert(strstr(output, "Pointer mismatch: ")); free(output); }