Mercurial
comparison third_party/libuv/test/runner-unix.c @ 160:948de3f54cea
[ThirdParty] Added libuv
| author | June Park <parkjune1995@gmail.com> |
|---|---|
| date | Wed, 14 Jan 2026 19:39:52 -0800 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 159:05cf9467a1c3 | 160:948de3f54cea |
|---|---|
| 1 /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. | |
| 2 * | |
| 3 * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| 4 * of this software and associated documentation files (the "Software"), to | |
| 5 * deal in the Software without restriction, including without limitation the | |
| 6 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
| 7 * sell copies of the Software, and to permit persons to whom the Software is | |
| 8 * furnished to do so, subject to the following conditions: | |
| 9 * | |
| 10 * The above copyright notice and this permission notice shall be included in | |
| 11 * all copies or substantial portions of the Software. | |
| 12 * | |
| 13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| 14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| 15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| 16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| 17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
| 18 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
| 19 * IN THE SOFTWARE. | |
| 20 */ | |
| 21 | |
| 22 #include "runner-unix.h" | |
| 23 #include "runner.h" | |
| 24 | |
| 25 #include <limits.h> | |
| 26 #include <stdint.h> /* uintptr_t */ | |
| 27 | |
| 28 #include <errno.h> | |
| 29 #include <unistd.h> /* usleep */ | |
| 30 #include <string.h> /* strdup */ | |
| 31 #include <stdio.h> | |
| 32 #include <stdlib.h> | |
| 33 #include <sys/types.h> | |
| 34 #include <signal.h> | |
| 35 #include <sys/wait.h> | |
| 36 #include <sys/stat.h> | |
| 37 #include <assert.h> | |
| 38 | |
| 39 #include <sys/select.h> | |
| 40 #include <sys/time.h> | |
| 41 #include <pthread.h> | |
| 42 | |
| 43 #ifdef __APPLE__ | |
| 44 #include <TargetConditionals.h> | |
| 45 #endif | |
| 46 | |
| 47 extern char** environ; | |
| 48 | |
| 49 static void closefd(int fd) { | |
| 50 if (close(fd) == 0 || errno == EINTR || errno == EINPROGRESS) | |
| 51 return; | |
| 52 | |
| 53 perror("close"); | |
| 54 abort(); | |
| 55 } | |
| 56 | |
| 57 | |
| 58 void notify_parent_process(void) { | |
| 59 char* arg; | |
| 60 int fd; | |
| 61 | |
| 62 arg = getenv("UV_TEST_RUNNER_FD"); | |
| 63 if (arg == NULL) | |
| 64 return; | |
| 65 | |
| 66 fd = atoi(arg); | |
| 67 assert(fd > STDERR_FILENO); | |
| 68 unsetenv("UV_TEST_RUNNER_FD"); | |
| 69 closefd(fd); | |
| 70 } | |
| 71 | |
| 72 | |
| 73 /* Do platform-specific initialization. */ | |
| 74 void platform_init(int argc, char **argv) { | |
| 75 /* Disable stdio output buffering. */ | |
| 76 setvbuf(stdout, NULL, _IONBF, 0); | |
| 77 setvbuf(stderr, NULL, _IONBF, 0); | |
| 78 signal(SIGPIPE, SIG_IGN); | |
| 79 snprintf(executable_path, sizeof(executable_path), "%s", argv[0]); | |
| 80 } | |
| 81 | |
| 82 | |
| 83 /* Invoke "argv[0] test-name [test-part]". Store process info in *p. Make sure | |
| 84 * that all stdio output of the processes is buffered up. */ | |
| 85 int process_start(char* name, char* part, process_info_t* p, int is_helper) { | |
| 86 FILE* stdout_file; | |
| 87 int stdout_fd; | |
| 88 const char* arg; | |
| 89 char* args[16]; | |
| 90 int pipefd[2]; | |
| 91 char fdstr[8]; | |
| 92 ssize_t rc; | |
| 93 int n; | |
| 94 pid_t pid; | |
| 95 | |
| 96 arg = getenv("UV_USE_VALGRIND"); | |
| 97 n = 0; | |
| 98 | |
| 99 /* Disable valgrind for helpers, it complains about helpers leaking memory. | |
| 100 * They're killed after the test and as such never get a chance to clean up. | |
| 101 */ | |
| 102 if (is_helper == 0 && arg != NULL && atoi(arg) != 0) { | |
| 103 args[n++] = "valgrind"; | |
| 104 args[n++] = "--quiet"; | |
| 105 args[n++] = "--leak-check=full"; | |
| 106 args[n++] = "--show-reachable=yes"; | |
| 107 args[n++] = "--error-exitcode=125"; | |
| 108 } | |
| 109 | |
| 110 args[n++] = executable_path; | |
| 111 args[n++] = name; | |
| 112 args[n++] = part; | |
| 113 args[n++] = NULL; | |
| 114 | |
| 115 stdout_file = tmpfile(); | |
| 116 stdout_fd = fileno(stdout_file); | |
| 117 if (!stdout_file) { | |
| 118 perror("tmpfile"); | |
| 119 return -1; | |
| 120 } | |
| 121 | |
| 122 if (is_helper) { | |
| 123 if (pipe(pipefd)) { | |
| 124 perror("pipe"); | |
| 125 return -1; | |
| 126 } | |
| 127 | |
| 128 snprintf(fdstr, sizeof(fdstr), "%d", pipefd[1]); | |
| 129 if (setenv("UV_TEST_RUNNER_FD", fdstr, /* overwrite */ 1)) { | |
| 130 perror("setenv"); | |
| 131 return -1; | |
| 132 } | |
| 133 } | |
| 134 | |
| 135 p->terminated = 0; | |
| 136 p->status = 0; | |
| 137 | |
| 138 #if defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH) | |
| 139 pid = -1; | |
| 140 #else | |
| 141 pid = fork(); | |
| 142 #endif | |
| 143 | |
| 144 if (pid < 0) { | |
| 145 perror("fork"); | |
| 146 return -1; | |
| 147 } | |
| 148 | |
| 149 if (pid == 0) { | |
| 150 /* child */ | |
| 151 if (is_helper) | |
| 152 closefd(pipefd[0]); | |
| 153 dup2(stdout_fd, STDOUT_FILENO); | |
| 154 dup2(stdout_fd, STDERR_FILENO); | |
| 155 #if !(defined(__APPLE__) && (TARGET_OS_TV || TARGET_OS_WATCH)) | |
| 156 execve(args[0], args, environ); | |
| 157 #endif | |
| 158 perror("execve()"); | |
| 159 _exit(127); | |
| 160 } | |
| 161 | |
| 162 /* parent */ | |
| 163 p->pid = pid; | |
| 164 p->name = strdup(name); | |
| 165 p->stdout_file = stdout_file; | |
| 166 | |
| 167 if (!is_helper) | |
| 168 return 0; | |
| 169 | |
| 170 closefd(pipefd[1]); | |
| 171 unsetenv("UV_TEST_RUNNER_FD"); | |
| 172 | |
| 173 do | |
| 174 rc = read(pipefd[0], &n, 1); | |
| 175 while (rc == -1 && errno == EINTR); | |
| 176 | |
| 177 closefd(pipefd[0]); | |
| 178 | |
| 179 if (rc == -1) { | |
| 180 perror("read"); | |
| 181 return -1; | |
| 182 } | |
| 183 | |
| 184 if (rc > 0) { | |
| 185 fprintf(stderr, "EOF expected but got data.\n"); | |
| 186 return -1; | |
| 187 } | |
| 188 | |
| 189 return 0; | |
| 190 } | |
| 191 | |
| 192 | |
| 193 typedef struct { | |
| 194 int pipe[2]; | |
| 195 process_info_t* vec; | |
| 196 int n; | |
| 197 } dowait_args; | |
| 198 | |
| 199 | |
| 200 /* This function is run inside a pthread. We do this so that we can possibly | |
| 201 * timeout. | |
| 202 */ | |
| 203 static void* dowait(void* data) { | |
| 204 dowait_args* args = data; | |
| 205 | |
| 206 int i, r; | |
| 207 process_info_t* p; | |
| 208 | |
| 209 for (i = 0; i < args->n; i++) { | |
| 210 p = &args->vec[i]; | |
| 211 if (p->terminated) continue; | |
| 212 r = waitpid(p->pid, &p->status, 0); | |
| 213 if (r < 0) { | |
| 214 perror("waitpid"); | |
| 215 return NULL; | |
| 216 } | |
| 217 p->terminated = 1; | |
| 218 } | |
| 219 | |
| 220 if (args->pipe[1] >= 0) { | |
| 221 /* Write a character to the main thread to notify it about this. */ | |
| 222 ssize_t r; | |
| 223 | |
| 224 do | |
| 225 r = write(args->pipe[1], "", 1); | |
| 226 while (r == -1 && errno == EINTR); | |
| 227 } | |
| 228 | |
| 229 return NULL; | |
| 230 } | |
| 231 | |
| 232 | |
| 233 /* Wait for all `n` processes in `vec` to terminate. Time out after `timeout` | |
| 234 * msec, or never if timeout == -1. Return 0 if all processes are terminated, | |
| 235 * -1 on error, -2 on timeout. */ | |
| 236 int process_wait(process_info_t* vec, int n, int timeout) { | |
| 237 int i; | |
| 238 int r; | |
| 239 int retval; | |
| 240 process_info_t* p; | |
| 241 dowait_args args; | |
| 242 pthread_t tid; | |
| 243 pthread_attr_t attr; | |
| 244 unsigned int elapsed_ms; | |
| 245 struct timeval timebase; | |
| 246 struct timeval tv; | |
| 247 fd_set fds; | |
| 248 | |
| 249 args.vec = vec; | |
| 250 args.n = n; | |
| 251 args.pipe[0] = -1; | |
| 252 args.pipe[1] = -1; | |
| 253 | |
| 254 /* The simple case is where there is no timeout */ | |
| 255 if (timeout == -1) { | |
| 256 dowait(&args); | |
| 257 return 0; | |
| 258 } | |
| 259 | |
| 260 /* Hard case. Do the wait with a timeout. | |
| 261 * | |
| 262 * Assumption: we are the only ones making this call right now. Otherwise | |
| 263 * we'd need to lock vec. | |
| 264 */ | |
| 265 | |
| 266 r = pipe((int*)&(args.pipe)); | |
| 267 if (r) { | |
| 268 perror("pipe()"); | |
| 269 return -1; | |
| 270 } | |
| 271 | |
| 272 if (pthread_attr_init(&attr)) | |
| 273 abort(); | |
| 274 | |
| 275 #if defined(__MVS__) | |
| 276 if (pthread_attr_setstacksize(&attr, 1024 * 1024)) | |
| 277 #else | |
| 278 if (pthread_attr_setstacksize(&attr, 256 * 1024)) | |
| 279 #endif | |
| 280 abort(); | |
| 281 | |
| 282 r = pthread_create(&tid, &attr, dowait, &args); | |
| 283 | |
| 284 if (pthread_attr_destroy(&attr)) | |
| 285 abort(); | |
| 286 | |
| 287 if (r) { | |
| 288 perror("pthread_create()"); | |
| 289 retval = -1; | |
| 290 goto terminate; | |
| 291 } | |
| 292 | |
| 293 if (gettimeofday(&timebase, NULL)) | |
| 294 abort(); | |
| 295 | |
| 296 tv = timebase; | |
| 297 for (;;) { | |
| 298 /* Check that gettimeofday() doesn't jump back in time. */ | |
| 299 assert(tv.tv_sec > timebase.tv_sec || | |
| 300 (tv.tv_sec == timebase.tv_sec && tv.tv_usec >= timebase.tv_usec)); | |
| 301 | |
| 302 elapsed_ms = | |
| 303 (tv.tv_sec - timebase.tv_sec) * 1000 + | |
| 304 (tv.tv_usec / 1000) - | |
| 305 (timebase.tv_usec / 1000); | |
| 306 | |
| 307 r = 0; /* Timeout. */ | |
| 308 if (elapsed_ms >= (unsigned) timeout) | |
| 309 break; | |
| 310 | |
| 311 tv.tv_sec = (timeout - elapsed_ms) / 1000; | |
| 312 tv.tv_usec = (timeout - elapsed_ms) % 1000 * 1000; | |
| 313 | |
| 314 FD_ZERO(&fds); | |
| 315 FD_SET(args.pipe[0], &fds); | |
| 316 | |
| 317 r = select(args.pipe[0] + 1, &fds, NULL, NULL, &tv); | |
| 318 if (!(r == -1 && errno == EINTR)) | |
| 319 break; | |
| 320 | |
| 321 if (gettimeofday(&tv, NULL)) | |
| 322 abort(); | |
| 323 } | |
| 324 | |
| 325 if (r == -1) { | |
| 326 perror("select()"); | |
| 327 retval = -1; | |
| 328 | |
| 329 } else if (r) { | |
| 330 /* The thread completed successfully. */ | |
| 331 retval = 0; | |
| 332 | |
| 333 } else { | |
| 334 /* Timeout. Kill all the children. */ | |
| 335 for (i = 0; i < n; i++) { | |
| 336 p = &vec[i]; | |
| 337 kill(p->pid, SIGTERM); | |
| 338 } | |
| 339 retval = -2; | |
| 340 } | |
| 341 | |
| 342 if (pthread_join(tid, NULL)) | |
| 343 abort(); | |
| 344 | |
| 345 terminate: | |
| 346 closefd(args.pipe[0]); | |
| 347 closefd(args.pipe[1]); | |
| 348 return retval; | |
| 349 } | |
| 350 | |
| 351 | |
| 352 /* Returns the number of bytes in the stdio output buffer for process `p`. */ | |
| 353 long int process_output_size(process_info_t *p) { | |
| 354 /* Size of the p->stdout_file */ | |
| 355 struct stat buf; | |
| 356 | |
| 357 memset(&buf, 0, sizeof(buf)); | |
| 358 int r = fstat(fileno(p->stdout_file), &buf); | |
| 359 if (r < 0) { | |
| 360 return -1; | |
| 361 } | |
| 362 | |
| 363 return (long)buf.st_size; | |
| 364 } | |
| 365 | |
| 366 | |
| 367 /* Copy the contents of the stdio output buffer to `fd`. */ | |
| 368 int process_copy_output(process_info_t* p, FILE* stream) { | |
| 369 char buf[1024]; | |
| 370 int partial; | |
| 371 int r; | |
| 372 | |
| 373 r = fseek(p->stdout_file, 0, SEEK_SET); | |
| 374 if (r < 0) { | |
| 375 perror("fseek"); | |
| 376 return -1; | |
| 377 } | |
| 378 | |
| 379 partial = 0; | |
| 380 while ((r = fread(buf, 1, sizeof(buf), p->stdout_file)) != 0) | |
| 381 partial = print_lines(buf, r, stream, partial); | |
| 382 | |
| 383 if (ferror(p->stdout_file)) { | |
| 384 perror("read"); | |
| 385 return -1; | |
| 386 } | |
| 387 | |
| 388 return 0; | |
| 389 } | |
| 390 | |
| 391 | |
| 392 /* Copy the last line of the stdio output buffer to `buffer` */ | |
| 393 int process_read_last_line(process_info_t *p, | |
| 394 char* buffer, | |
| 395 size_t buffer_len) { | |
| 396 char* ptr; | |
| 397 | |
| 398 int r = fseek(p->stdout_file, 0, SEEK_SET); | |
| 399 if (r < 0) { | |
| 400 perror("fseek"); | |
| 401 return -1; | |
| 402 } | |
| 403 | |
| 404 buffer[0] = '\0'; | |
| 405 | |
| 406 while (fgets(buffer, buffer_len, p->stdout_file) != NULL) { | |
| 407 for (ptr = buffer; *ptr && *ptr != '\r' && *ptr != '\n'; ptr++) | |
| 408 ; | |
| 409 *ptr = '\0'; | |
| 410 } | |
| 411 | |
| 412 if (ferror(p->stdout_file)) { | |
| 413 perror("read"); | |
| 414 buffer[0] = '\0'; | |
| 415 return -1; | |
| 416 } | |
| 417 return 0; | |
| 418 } | |
| 419 | |
| 420 | |
| 421 /* Return the name that was specified when `p` was started by process_start */ | |
| 422 char* process_get_name(process_info_t *p) { | |
| 423 return p->name; | |
| 424 } | |
| 425 | |
| 426 | |
| 427 /* Terminate process `p`. */ | |
| 428 int process_terminate(process_info_t *p) { | |
| 429 return kill(p->pid, SIGTERM); | |
| 430 } | |
| 431 | |
| 432 | |
| 433 /* Return the exit code of process p. On error, return -1. */ | |
| 434 int process_reap(process_info_t *p) { | |
| 435 if (WIFEXITED(p->status)) { | |
| 436 return WEXITSTATUS(p->status); | |
| 437 } else { | |
| 438 return p->status; /* ? */ | |
| 439 } | |
| 440 } | |
| 441 | |
| 442 | |
| 443 /* Clean up after terminating process `p` (e.g. free the output buffer etc.). */ | |
| 444 void process_cleanup(process_info_t *p) { | |
| 445 fclose(p->stdout_file); | |
| 446 free(p->name); | |
| 447 } | |
| 448 | |
| 449 | |
| 450 /* Move the console cursor one line up and back to the first column. */ | |
| 451 void rewind_cursor(void) { | |
| 452 #if defined(__MVS__) | |
| 453 fprintf(stderr, "\047[2K\r"); | |
| 454 #else | |
| 455 fprintf(stderr, "\033[2K\r"); | |
| 456 #endif | |
| 457 } |