| #include <stdarg.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| |
| #include "selftest.h" |
| |
| #ifdef _WIN32 |
| # define WIN32_LEAN_AND_MEAN |
| # include <windows.h> |
| |
| 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 <errno.h> |
| # include <fcntl.h> |
| # include <limits.h> |
| # include <unistd.h> |
| # include <sys/wait.h> |
| |
| 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); |
| } |