/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * 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 OR COPYRIGHT HOLDERS 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. */ #include "runner-unix.h" #include "runner.h" #include /* uintptr_t */ #include #include /* usleep */ #include /* strdup */ #include #include #include #include #include #include #include #include #include /* Do platform-specific initialization. */ void platform_init(int argc, char **argv) { const char* tap; tap = getenv("UV_TAP_OUTPUT"); tap_output = (tap != NULL && atoi(tap) > 0); /* Disable stdio output buffering. */ setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); strncpy(executable_path, argv[0], sizeof(executable_path) - 1); signal(SIGPIPE, SIG_IGN); } /* Invoke "argv[0] test-name [test-part]". Store process info in *p. */ /* Make sure that all stdio output of the processes is buffered up. */ int process_start(char* name, char* part, process_info_t* p, int is_helper) { FILE* stdout_file; const char* arg; char* args[16]; int n; stdout_file = tmpfile(); if (!stdout_file) { perror("tmpfile"); return -1; } p->terminated = 0; p->status = 0; pid_t pid = fork(); if (pid < 0) { perror("fork"); return -1; } if (pid == 0) { /* child */ arg = getenv("UV_USE_VALGRIND"); n = 0; /* Disable valgrind for helpers, it complains about helpers leaking memory. * They're killed after the test and as such never get a chance to clean up. */ if (is_helper == 0 && arg != NULL && atoi(arg) != 0) { args[n++] = "valgrind"; args[n++] = "--quiet"; args[n++] = "--leak-check=full"; args[n++] = "--show-reachable=yes"; args[n++] = "--error-exitcode=125"; } args[n++] = executable_path; args[n++] = name; args[n++] = part; args[n++] = NULL; dup2(fileno(stdout_file), STDOUT_FILENO); dup2(fileno(stdout_file), STDERR_FILENO); execvp(args[0], args); perror("execvp()"); _exit(127); } /* parent */ p->pid = pid; p->name = strdup(name); p->stdout_file = stdout_file; return 0; } typedef struct { int pipe[2]; process_info_t* vec; int n; } dowait_args; /* This function is run inside a pthread. We do this so that we can possibly * timeout. */ static void* dowait(void* data) { dowait_args* args = data; int i, r; process_info_t* p; for (i = 0; i < args->n; i++) { p = (process_info_t*)(args->vec + i * sizeof(process_info_t)); if (p->terminated) continue; r = waitpid(p->pid, &p->status, 0); if (r < 0) { perror("waitpid"); return NULL; } p->terminated = 1; } if (args->pipe[1] >= 0) { /* Write a character to the main thread to notify it about this. */ ssize_t r; do r = write(args->pipe[1], "", 1); while (r == -1 && errno == EINTR); } return NULL; } /* Wait for all `n` processes in `vec` to terminate. */ /* Time out after `timeout` msec, or never if timeout == -1 */ /* Return 0 if all processes are terminated, -1 on error, -2 on timeout. */ int process_wait(process_info_t* vec, int n, int timeout) { int i; process_info_t* p; dowait_args args; args.vec = vec; args.n = n; args.pipe[0] = -1; args.pipe[1] = -1; /* The simple case is where there is no timeout */ if (timeout == -1) { dowait(&args); return 0; } /* Hard case. Do the wait with a timeout. * * Assumption: we are the only ones making this call right now. Otherwise * we'd need to lock vec. */ pthread_t tid; int retval; int r = pipe((int*)&(args.pipe)); if (r) { perror("pipe()"); return -1; } r = pthread_create(&tid, NULL, dowait, &args); if (r) { perror("pthread_create()"); retval = -1; goto terminate; } struct timeval tv; tv.tv_sec = timeout / 1000; tv.tv_usec = 0; fd_set fds; FD_ZERO(&fds); FD_SET(args.pipe[0], &fds); r = select(args.pipe[0] + 1, &fds, NULL, NULL, &tv); if (r == -1) { perror("select()"); retval = -1; } else if (r) { /* The thread completed successfully. */ retval = 0; } else { /* Timeout. Kill all the children. */ for (i = 0; i < n; i++) { p = (process_info_t*)(vec + i * sizeof(process_info_t)); kill(p->pid, SIGTERM); } retval = -2; /* Wait for thread to finish. */ r = pthread_join(tid, NULL); if (r) { perror("pthread_join"); retval = -1; } } terminate: close(args.pipe[0]); close(args.pipe[1]); return retval; } /* Returns the number of bytes in the stdio output buffer for process `p`. */ long int process_output_size(process_info_t *p) { /* Size of the p->stdout_file */ struct stat buf; int r = fstat(fileno(p->stdout_file), &buf); if (r < 0) { return -1; } return (long)buf.st_size; } /* Copy the contents of the stdio output buffer to `fd`. */ int process_copy_output(process_info_t *p, int fd) { int r = fseek(p->stdout_file, 0, SEEK_SET); if (r < 0) { perror("fseek"); return -1; } ssize_t nwritten; char buf[1024]; /* TODO: what if the line is longer than buf */ while (fgets(buf, sizeof(buf), p->stdout_file) != NULL) { /* TODO: what if write doesn't write the whole buffer... */ nwritten = 0; if (tap_output) nwritten += write(fd, "#", 1); nwritten += write(fd, buf, strlen(buf)); if (nwritten < 0) { perror("write"); return -1; } } if (ferror(p->stdout_file)) { perror("read"); return -1; } return 0; } /* Copy the last line of the stdio output buffer to `buffer` */ int process_read_last_line(process_info_t *p, char* buffer, size_t buffer_len) { char* ptr; int r = fseek(p->stdout_file, 0, SEEK_SET); if (r < 0) { perror("fseek"); return -1; } buffer[0] = '\0'; while (fgets(buffer, buffer_len, p->stdout_file) != NULL) { for (ptr = buffer; *ptr && *ptr != '\r' && *ptr != '\n'; ptr++); *ptr = '\0'; } if (ferror(p->stdout_file)) { perror("read"); buffer[0] = '\0'; return -1; } return 0; } /* Return the name that was specified when `p` was started by process_start */ char* process_get_name(process_info_t *p) { return p->name; } /* Terminate process `p`. */ int process_terminate(process_info_t *p) { return kill(p->pid, SIGTERM); } /* Return the exit code of process p. */ /* On error, return -1. */ int process_reap(process_info_t *p) { if (WIFEXITED(p->status)) { return WEXITSTATUS(p->status); } else { return p->status; /* ? */ } } /* Clean up after terminating process `p` (e.g. free the output buffer etc.). */ void process_cleanup(process_info_t *p) { fclose(p->stdout_file); free(p->name); } /* Move the console cursor one line up and back to the first column. */ void rewind_cursor(void) { fprintf(stderr, "\033[2K\r"); } /* Pause the calling thread for a number of milliseconds. */ void uv_sleep(int msec) { usleep(msec * 1000); }