| 1 | #include <stdarg.h> |
| 2 | #include <stdio.h> |
| 3 | #include <string.h> |
| 4 | #include <sys/stat.h> |
| 5 | |
| 6 | #include "selftest.h" |
| 7 | |
| 8 | #ifdef _WIN32 |
| 9 | # define WIN32_LEAN_AND_MEAN |
| 10 | # include <windows.h> |
| 11 | |
| 12 | static char *read_full(HANDLE h, int is_pipe) |
| 13 | { |
| 14 | char *data = NULL; |
| 15 | size_t data_size = 0; |
| 16 | |
| 17 | while (1) { |
| 18 | CHAR buf[4096]; |
| 19 | DWORD bytes_read; |
| 20 | |
| 21 | if (!ReadFile(h, buf, sizeof(buf), &bytes_read, NULL)) { |
| 22 | if (!is_pipe) |
| 23 | cl_fail("Failed reading file handle."); |
| 24 | cl_assert_equal_i(GetLastError(), ERROR_BROKEN_PIPE); |
| 25 | break; |
| 26 | } |
| 27 | if (!bytes_read) |
| 28 | break; |
| 29 | |
| 30 | data = realloc(data, data_size + bytes_read); |
| 31 | cl_assert(data); |
| 32 | memcpy(data + data_size, buf, bytes_read); |
| 33 | data_size += bytes_read; |
| 34 | } |
| 35 | |
| 36 | data = realloc(data, data_size + 1); |
| 37 | cl_assert(data); |
| 38 | data[data_size] = '\0'; |
| 39 | |
| 40 | while (strstr(data, "\r\n")) { |
| 41 | char *ptr = strstr(data, "\r\n"); |
| 42 | memmove(ptr, ptr + 1, strlen(ptr)); |
| 43 | } |
| 44 | |
| 45 | return data; |
| 46 | } |
| 47 | |
| 48 | static char *read_file(const char *path) |
| 49 | { |
| 50 | char *content; |
| 51 | HANDLE file; |
| 52 | |
| 53 | file = CreateFile(path, GENERIC_READ, FILE_SHARE_READ, NULL, |
| 54 | OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); |
| 55 | cl_assert(file != INVALID_HANDLE_VALUE); |
| 56 | content = read_full(file, 0); |
| 57 | cl_assert_equal_b(1, CloseHandle(file)); |
| 58 | |
| 59 | return content; |
| 60 | } |
| 61 | |
| 62 | static char *execute(const char *suite, int expected_error_code, const char **args, size_t nargs) |
| 63 | { |
| 64 | SECURITY_ATTRIBUTES security_attributes = { 0 }; |
| 65 | PROCESS_INFORMATION process_info = { 0 }; |
| 66 | STARTUPINFO startup_info = { 0 }; |
| 67 | char binary_path[4096] = { 0 }; |
| 68 | char cmdline[4096] = { 0 }; |
| 69 | char *output = NULL; |
| 70 | HANDLE stdout_write; |
| 71 | HANDLE stdout_read; |
| 72 | DWORD exit_code; |
| 73 | size_t i; |
| 74 | |
| 75 | snprintf(binary_path, sizeof(binary_path), "%s/%s_suite.exe", |
| 76 | selftest_suite_directory, suite); |
| 77 | |
| 78 | /* |
| 79 | * Assemble command line arguments. In theory we'd have to properly |
| 80 | * quote them. In practice none of our tests actually care. |
| 81 | */ |
| 82 | snprintf(cmdline, sizeof(cmdline), suite); |
| 83 | for (i = 0; i < nargs; i++) { |
| 84 | size_t cmdline_len = strlen(cmdline); |
| 85 | const char *arg = args[i]; |
| 86 | cl_assert(cmdline_len + strlen(arg) < sizeof(cmdline)); |
| 87 | snprintf(cmdline + cmdline_len, sizeof(cmdline) - cmdline_len, |
| 88 | " %s", arg); |
| 89 | } |
| 90 | |
| 91 | /* |
| 92 | * Create a pipe that we will use to read data from the child process. |
| 93 | * The writing side needs to be inheritable such that the child can use |
| 94 | * it as stdout and stderr. The reading side should only be used by the |
| 95 | * parent. |
| 96 | */ |
| 97 | security_attributes.nLength = sizeof(security_attributes); |
| 98 | security_attributes.bInheritHandle = TRUE; |
| 99 | cl_assert_equal_b(1, CreatePipe(&stdout_read, &stdout_write, &security_attributes, 0)); |
| 100 | cl_assert_equal_b(1, SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0)); |
| 101 | |
| 102 | /* |
| 103 | * Create the child process with our pipe. |
| 104 | */ |
| 105 | startup_info.cb = sizeof(startup_info); |
| 106 | startup_info.hStdError = stdout_write; |
| 107 | startup_info.hStdOutput = stdout_write; |
| 108 | startup_info.dwFlags |= STARTF_USESTDHANDLES; |
| 109 | cl_assert_equal_b(1, CreateProcess(binary_path, cmdline, NULL, NULL, TRUE, |
| 110 | 0, NULL, NULL, &startup_info, &process_info)); |
| 111 | cl_assert_equal_b(1, CloseHandle(stdout_write)); |
| 112 | |
| 113 | output = read_full(stdout_read, 1); |
| 114 | cl_assert_equal_b(1, CloseHandle(stdout_read)); |
| 115 | cl_assert_equal_b(1, GetExitCodeProcess(process_info.hProcess, &exit_code)); |
| 116 | cl_assert_equal_i(exit_code, expected_error_code); |
| 117 | |
| 118 | return output; |
| 119 | } |
| 120 | |
| 121 | static void assert_output(const char *suite, const char *expected_output_file, int expected_error_code, ...) |
| 122 | { |
| 123 | char *expected_output = NULL; |
| 124 | char *output = NULL; |
| 125 | const char *args[16]; |
| 126 | va_list ap; |
| 127 | size_t i; |
| 128 | |
| 129 | va_start(ap, expected_error_code); |
| 130 | for (i = 0; ; i++) { |
| 131 | const char *arg = va_arg(ap, const char *); |
| 132 | if (!arg) |
| 133 | break; |
| 134 | cl_assert(i < sizeof(args) / sizeof(*args)); |
| 135 | args[i] = arg; |
| 136 | } |
| 137 | va_end(ap); |
| 138 | |
| 139 | output = execute(suite, expected_error_code, args, i); |
| 140 | expected_output = read_file(cl_fixture(expected_output_file)); |
| 141 | cl_assert_equal_s(output, expected_output); |
| 142 | |
| 143 | free(expected_output); |
| 144 | free(output); |
| 145 | } |
| 146 | |
| 147 | #else |
| 148 | # include <errno.h> |
| 149 | # include <fcntl.h> |
| 150 | # include <limits.h> |
| 151 | # include <unistd.h> |
| 152 | # include <sys/wait.h> |
| 153 | |
| 154 | static char *read_full(int fd) |
| 155 | { |
| 156 | size_t data_bytes = 0; |
| 157 | char *data = NULL; |
| 158 | |
| 159 | while (1) { |
| 160 | char buf[4096]; |
| 161 | ssize_t n; |
| 162 | |
| 163 | n = read(fd, buf, sizeof(buf)); |
| 164 | if (n < 0) { |
| 165 | if (errno == EAGAIN || errno == EINTR) |
| 166 | continue; |
| 167 | cl_fail("Failed reading from child process."); |
| 168 | } |
| 169 | if (!n) |
| 170 | break; |
| 171 | |
| 172 | data = realloc(data, data_bytes + n); |
| 173 | cl_assert(data); |
| 174 | |
| 175 | memcpy(data + data_bytes, buf, n); |
| 176 | data_bytes += n; |
| 177 | } |
| 178 | |
| 179 | data = realloc(data, data_bytes + 1); |
| 180 | cl_assert(data); |
| 181 | data[data_bytes] = '\0'; |
| 182 | |
| 183 | return data; |
| 184 | } |
| 185 | |
| 186 | static char *read_file(const char *path) |
| 187 | { |
| 188 | char *data; |
| 189 | int fd; |
| 190 | |
| 191 | fd = open(path, O_RDONLY); |
| 192 | if (fd < 0) |
| 193 | cl_fail("Failed reading expected file."); |
| 194 | |
| 195 | data = read_full(fd); |
| 196 | cl_must_pass(close(fd)); |
| 197 | |
| 198 | return data; |
| 199 | } |
| 200 | |
| 201 | static char *execute(const char *suite, int expected_error_code, const char **args, size_t nargs) |
| 202 | { |
| 203 | int pipe_fds[2]; |
| 204 | pid_t pid; |
| 205 | |
| 206 | cl_must_pass(pipe(pipe_fds)); |
| 207 | |
| 208 | pid = fork(); |
| 209 | if (!pid) { |
| 210 | const char *final_args[17] = { NULL }; |
| 211 | char binary_path[4096]; |
| 212 | size_t len = 0; |
| 213 | size_t i; |
| 214 | |
| 215 | cl_assert(nargs < sizeof(final_args) / sizeof(*final_args)); |
| 216 | final_args[0] = suite; |
| 217 | for (i = 0; i < nargs; i++) |
| 218 | final_args[i + 1] = args[i]; |
| 219 | |
| 220 | if (dup2(pipe_fds[1], STDOUT_FILENO) < 0 || |
| 221 | dup2(pipe_fds[1], STDERR_FILENO) < 0 || |
| 222 | close(0) < 0 || |
| 223 | close(pipe_fds[0]) < 0 || |
| 224 | close(pipe_fds[1]) < 0) |
| 225 | exit(1); |
| 226 | |
| 227 | cl_assert(len + strlen(selftest_suite_directory) < sizeof(binary_path)); |
| 228 | strcpy(binary_path, selftest_suite_directory); |
| 229 | len += strlen(selftest_suite_directory); |
| 230 | |
| 231 | cl_assert(len + 1 < sizeof(binary_path)); |
| 232 | binary_path[len] = '/'; |
| 233 | len += 1; |
| 234 | |
| 235 | cl_assert(len + strlen(suite) < sizeof(binary_path)); |
| 236 | strcpy(binary_path + len, suite); |
| 237 | len += strlen(suite); |
| 238 | |
| 239 | cl_assert(len + strlen("_suite") < sizeof(binary_path)); |
| 240 | strcpy(binary_path + len, "_suite"); |
| 241 | len += strlen("_suite"); |
| 242 | |
| 243 | binary_path[len] = '\0'; |
| 244 | |
| 245 | execv(binary_path, (char **) final_args); |
| 246 | exit(1); |
| 247 | } else if (pid > 0) { |
| 248 | pid_t waited_pid; |
| 249 | char *output; |
| 250 | int stat; |
| 251 | |
| 252 | cl_must_pass(close(pipe_fds[1])); |
| 253 | |
| 254 | output = read_full(pipe_fds[0]); |
| 255 | |
| 256 | waited_pid = waitpid(pid, &stat, 0); |
| 257 | cl_assert_equal_i(pid, waited_pid); |
| 258 | cl_assert(WIFEXITED(stat)); |
| 259 | cl_assert_equal_i(WEXITSTATUS(stat), expected_error_code); |
| 260 | |
| 261 | return output; |
| 262 | } else { |
| 263 | cl_fail("Fork failed."); |
| 264 | } |
| 265 | |
| 266 | return NULL; |
| 267 | } |
| 268 | |
| 269 | static void assert_output(const char *suite, const char *expected_output_file, int expected_error_code, ...) |
| 270 | { |
| 271 | char *expected_output, *output; |
| 272 | const char *args[16]; |
| 273 | va_list ap; |
| 274 | size_t i; |
| 275 | |
| 276 | va_start(ap, expected_error_code); |
| 277 | for (i = 0; ; i++) { |
| 278 | cl_assert(i < sizeof(args) / sizeof(*args)); |
| 279 | args[i] = va_arg(ap, const char *); |
| 280 | if (!args[i]) |
| 281 | break; |
| 282 | } |
| 283 | va_end(ap); |
| 284 | |
| 285 | output = execute(suite, expected_error_code, args, i); |
| 286 | expected_output = read_file(cl_fixture(expected_output_file)); |
| 287 | cl_assert_equal_s(output, expected_output); |
| 288 | |
| 289 | free(expected_output); |
| 290 | free(output); |
| 291 | } |
| 292 | #endif |
| 293 | |
| 294 | void test_selftest__help(void) |
| 295 | { |
| 296 | cl_invoke(assert_output("combined", "help", 1, "-h", NULL)); |
| 297 | } |
| 298 | |
| 299 | void test_selftest__without_arguments(void) |
| 300 | { |
| 301 | cl_invoke(assert_output("combined", "without_arguments", 15, NULL)); |
| 302 | } |
| 303 | |
| 304 | void test_selftest__specific_test(void) |
| 305 | { |
| 306 | cl_invoke(assert_output("combined", "specific_test", 1, "-scombined::bool", NULL)); |
| 307 | } |
| 308 | |
| 309 | void test_selftest__stop_on_failure(void) |
| 310 | { |
| 311 | cl_invoke(assert_output("combined", "stop_on_failure", 1, "-Q", NULL)); |
| 312 | } |
| 313 | |
| 314 | void test_selftest__quiet(void) |
| 315 | { |
| 316 | cl_invoke(assert_output("combined", "quiet", 15, "-q", NULL)); |
| 317 | } |
| 318 | |
| 319 | void test_selftest__tap(void) |
| 320 | { |
| 321 | cl_invoke(assert_output("combined", "tap", 15, "-t", NULL)); |
| 322 | } |
| 323 | |
| 324 | void test_selftest__suite_names(void) |
| 325 | { |
| 326 | cl_invoke(assert_output("combined", "suite_names", 0, "-l", NULL)); |
| 327 | } |
| 328 | |
| 329 | void test_selftest__summary_without_filename(void) |
| 330 | { |
| 331 | struct stat st; |
| 332 | cl_invoke(assert_output("combined", "summary_without_filename", 15, "-r", NULL)); |
| 333 | /* The summary contains timestamps, so we cannot verify its contents. */ |
| 334 | cl_must_pass(stat("summary.xml", &st)); |
| 335 | } |
| 336 | |
| 337 | void test_selftest__summary_with_filename(void) |
| 338 | { |
| 339 | struct stat st; |
| 340 | cl_invoke(assert_output("combined", "summary_with_filename", 15, "-rdifferent.xml", NULL)); |
| 341 | /* The summary contains timestamps, so we cannot verify its contents. */ |
| 342 | cl_must_pass(stat("different.xml", &st)); |
| 343 | } |
| 344 | |
| 345 | void test_selftest__pointer_equal(void) |
| 346 | { |
| 347 | const char *args[] = { |
| 348 | "-spointer::equal", |
| 349 | "-t" |
| 350 | }; |
| 351 | char *output = execute("pointer", 0, args, 2); |
| 352 | cl_assert_equal_s(output, |
| 353 | "TAP version 13\n" |
| 354 | "# start of suite 1: pointer\n" |
| 355 | "ok 1 - pointer::equal\n" |
| 356 | "1..1\n" |
| 357 | ); |
| 358 | free(output); |
| 359 | } |
| 360 | |
| 361 | void test_selftest__pointer_unequal(void) |
| 362 | { |
| 363 | const char *args[] = { |
| 364 | "-spointer::unequal", |
| 365 | }; |
| 366 | char *output = execute("pointer", 1, args, 1); |
| 367 | cl_assert(output); |
| 368 | cl_assert(strstr(output, "Pointer mismatch: ")); |
| 369 | free(output); |
| 370 | } |