diff --git a/src/compat/pipe.c b/src/compat/pipe.c index 0a7e4e9..39167d9 100644 --- a/src/compat/pipe.c +++ b/src/compat/pipe.c @@ -27,18 +27,23 @@ #include "criterion/assert.h" #include "pipe-internal.h" -FILE *pipe_in(s_pipe_handle *p, int do_close) { +FILE *pipe_in(s_pipe_handle *p, enum pipe_opt opts) { #ifdef VANILLA_WIN32 - if (do_close) + if (opts & PIPE_CLOSE) CloseHandle(p->fhs[1]); int fd = _open_osfhandle((intptr_t) p->fhs[0], _O_RDONLY); if (fd == -1) return NULL; + if (opts & PIPE_DUP) + fd = _dup(fd); FILE *in = _fdopen(fd, "r"); #else - if (do_close) + if (opts & PIPE_CLOSE) close(p->fds[1]); - FILE *in = fdopen(p->fds[0], "r"); + int fd = p->fds[0]; + if (opts & PIPE_DUP) + fd = dup(fd); + FILE *in = fdopen(fd, "r"); #endif if (!in) return NULL; @@ -47,18 +52,23 @@ FILE *pipe_in(s_pipe_handle *p, int do_close) { return in; } -FILE *pipe_out(s_pipe_handle *p, int do_close) { +FILE *pipe_out(s_pipe_handle *p, enum pipe_opt opts) { #ifdef VANILLA_WIN32 - if (do_close) + if (opts & PIPE_CLOSE) CloseHandle(p->fhs[0]); int fd = _open_osfhandle((intptr_t) p->fhs[1], _O_WRONLY); if (fd == -1) return NULL; + if (opts & PIPE_DUP) + fd = _dup(fd); FILE *out = _fdopen(fd, "w"); #else - if (do_close) + if (opts & PIPE_CLOSE) close(p->fds[0]); - FILE *out = fdopen(p->fds[1], "w"); + int fd = p->fds[1]; + if (opts & PIPE_DUP) + fd = dup(fd); + FILE *out = fdopen(fd, "w"); #endif if (!out) return NULL; diff --git a/src/compat/pipe.h b/src/compat/pipe.h index 2aa9d8d..83aa63d 100644 --- a/src/compat/pipe.h +++ b/src/compat/pipe.h @@ -24,6 +24,7 @@ #ifndef PIPE_H_ # define PIPE_H_ +# include # include "common.h" struct pipe_handle; @@ -40,9 +41,14 @@ enum criterion_std_fd { CR_STDERR = 2, }; +enum pipe_opt { + PIPE_DUP = 1 << 0, + PIPE_CLOSE = 1 << 1, +}; + s_pipe_handle *stdpipe(); -FILE *pipe_in(s_pipe_handle *p, int do_close); -FILE *pipe_out(s_pipe_handle *p, int do_close); +FILE *pipe_in(s_pipe_handle *p, enum pipe_opt opts); +FILE *pipe_out(s_pipe_handle *p, enum pipe_opt opts); int stdpipe_options(s_pipe_handle *pipe, int id, int noblock); void pipe_std_redirect(s_pipe_handle *pipe, enum criterion_std_fd fd); diff --git a/src/compat/process.c b/src/compat/process.c index 5204e3e..2adc7f8 100644 --- a/src/compat/process.c +++ b/src/compat/process.c @@ -21,12 +21,18 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#include +#include #include #include "core/worker.h" +#include "core/runner.h" +#include "io/event.h" #include "process.h" #include "internal.h" #include "pipe-internal.h" +#include + #ifdef VANILLA_WIN32 # define CREATE_SUSPENDED_(Filename, CmdLine, StartupInfo, Info) \ CreateProcessW(Filename, \ @@ -44,14 +50,6 @@ WriteProcessMemory(Proc, &What, &What, Size, NULL); #endif -struct proc_handle { -#ifdef VANILLA_WIN32 - HANDLE handle; -#else - pid_t pid; -#endif -}; - struct worker_context g_worker_context = {.test = NULL}; #ifdef VANILLA_WIN32 @@ -71,6 +69,32 @@ static TCHAR g_mapping_name[] = TEXT("WinCriterionWorker"); static struct full_context local_ctx; #endif +#ifdef __unix__ +# ifndef __GNUC__ +# error Unsupported compiler. Use GCC or Clang under *nixes. +# endif + +static void handle_sigchld(int sig) { + assert(sig == SIGCHLD); + + int fd = g_worker_pipe->fds[1]; + pid_t pid; + int status; + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + int kind = WORKER_TERMINATED; + struct worker_status ws = { + (s_proc_handle) { pid }, get_status(status) + }; + + char buf[sizeof (int) + sizeof (struct worker_status)]; + memcpy(buf, &kind, sizeof (kind)); + memcpy(buf + sizeof (kind), &ws, sizeof (ws)); + + write(fd, &buf, sizeof (buf)); + } +} +#endif + int resume_child(void) { #ifdef VANILLA_WIN32 HANDLE sharedMem = OpenFileMapping( @@ -111,6 +135,16 @@ int resume_child(void) { run_worker(&g_worker_context); return 1; #else +# ifdef __unix__ + struct sigaction sa; + sa.sa_handler = &handle_sigchld; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sa, 0) == -1) { + perror(0); + exit(1); + } +# endif return 0; #endif } diff --git a/src/compat/process.h b/src/compat/process.h index 2d5d2ef..1698522 100644 --- a/src/compat/process.h +++ b/src/compat/process.h @@ -25,8 +25,16 @@ # define COMPAT_PROCESS_H_ # include "criterion/types.h" +# include "compat/posix.h" + +struct proc_handle { +#ifdef VANILLA_WIN32 + HANDLE handle; +#else + pid_t pid; +#endif +}; -struct proc_handle; typedef struct proc_handle s_proc_handle; struct worker_context { diff --git a/src/core/runner.c b/src/core/runner.c index f085921..2635010 100644 --- a/src/core/runner.c +++ b/src/core/runner.c @@ -208,13 +208,92 @@ static INLINE bool is_disabled(struct criterion_test *t, #define push_event(Kind, ...) \ do { \ - stat_push_event(stats, \ - suite_stats, \ - test_stats, \ + stat_push_event(ctx->stats, \ + ctx->suite_stats, \ + ctx->test_stats, \ &(struct event) { .kind = Kind, __VA_ARGS__ }); \ - report(Kind, test_stats); \ + report(Kind, ctx->test_stats); \ } while (0) +s_pipe_handle *g_worker_pipe; + +struct execution_context { + bool test_started; + bool normal_finish; + bool cleaned_up; + struct criterion_global_stats *stats; + struct criterion_test *test; + struct criterion_test_stats *test_stats; + struct criterion_suite *suite; + struct criterion_suite_stats *suite_stats; +}; + +static void handle_worker_terminated(struct event *ev, + struct execution_context *ctx) { + + struct worker_status *ws = ev->data; + struct process_status status = ws->status; + + if (status.kind == SIGNAL) { + if (status.status == SIGPROF) { + ctx->test_stats->timed_out = true; + double elapsed_time = ctx->test->data->timeout; + if (elapsed_time == 0 && ctx->suite->data) + elapsed_time = ctx->suite->data->timeout; + push_event(POST_TEST, .data = &elapsed_time); + push_event(POST_FINI); + log(test_timeout, ctx->test_stats); + return; + } + + if (ctx->normal_finish || !ctx->test_started) { + log(other_crash, ctx->test_stats); + if (!ctx->test_started) { + stat_push_event(ctx->stats, + ctx->suite_stats, + ctx->test_stats, + &(struct event) { .kind = TEST_CRASH }); + } + return; + } + ctx->test_stats->signal = status.status; + if (ctx->test->data->signal == 0) { + push_event(TEST_CRASH); + log(test_crash, ctx->test_stats); + } else { + double elapsed_time = 0; + push_event(POST_TEST, .data = &elapsed_time); + log(post_test, ctx->test_stats); + push_event(POST_FINI); + log(post_fini, ctx->test_stats); + } + } else { + if ((ctx->normal_finish && !ctx->cleaned_up) || !ctx->test_started) { + log(abnormal_exit, ctx->test_stats); + if (!ctx->test_started) { + stat_push_event(ctx->stats, + ctx->suite_stats, + ctx->test_stats, + &(struct event) { .kind = TEST_CRASH }); + } + return; + } + ctx->test_stats->exit_code = status.status; + if (!ctx->normal_finish) { + if (ctx->test->data->exit_code == 0) { + push_event(TEST_CRASH); + log(abnormal_exit, ctx->test_stats); + } else { + double elapsed_time = 0; + push_event(POST_TEST, .data = &elapsed_time); + log(post_test, ctx->test_stats); + push_event(POST_FINI); + log(post_fini, ctx->test_stats); + } + } + } +} + static void run_test(struct criterion_global_stats *stats, struct criterion_suite_stats *suite_stats, struct criterion_test *test, @@ -231,16 +310,21 @@ static void run_test(struct criterion_global_stats *stats, goto cleanup; } - proc = spawn_test_worker(test, suite, run_test_child); + proc = spawn_test_worker(test, suite, run_test_child, g_worker_pipe); if (proc == NULL && !is_runner()) goto cleanup; - bool test_started = false; - bool normal_finish = false; - bool cleaned_up = false; + struct execution_context ctx = { + .stats = stats, + .test = test, + .test_stats = test_stats, + .suite = suite, + .suite_stats = suite_stats, + }; struct event *ev; while ((ev = worker_read_event(proc)) != NULL) { - stat_push_event(stats, suite_stats, test_stats, ev); + if (ev->kind < WORKER_TERMINATED) + stat_push_event(stats, suite_stats, test_stats, ev); switch (ev->kind) { case PRE_INIT: report(PRE_INIT, test); @@ -249,7 +333,7 @@ static void run_test(struct criterion_global_stats *stats, case PRE_TEST: report(PRE_TEST, test); log(pre_test, test); - test_started = true; + ctx.test_started = true; break; case THEORY_FAIL: { struct criterion_theory_stats ths = { @@ -266,77 +350,21 @@ static void run_test(struct criterion_global_stats *stats, case POST_TEST: report(POST_TEST, test_stats); log(post_test, test_stats); - normal_finish = true; + ctx.normal_finish = true; break; case POST_FINI: report(POST_FINI, test_stats); log(post_fini, test_stats); - cleaned_up = true; + ctx.cleaned_up = true; break; + case WORKER_TERMINATED: + handle_worker_terminated(ev, &ctx); + sfree(ev); + goto cleanup; } sfree(ev); } - struct process_status status = wait_proc(proc); - if (status.kind == SIGNAL) { - if (status.status == SIGPROF) { - test_stats->timed_out = true; - double elapsed_time = test->data->timeout; - if (elapsed_time == 0 && suite->data) - elapsed_time = suite->data->timeout; - push_event(POST_TEST, .data = &elapsed_time); - push_event(POST_FINI); - log(test_timeout, test_stats); - goto cleanup; - } - - if (normal_finish || !test_started) { - log(other_crash, test_stats); - if (!test_started) { - stat_push_event(stats, - suite_stats, - test_stats, - &(struct event) { .kind = TEST_CRASH }); - } - goto cleanup; - } - test_stats->signal = status.status; - if (test->data->signal == 0) { - push_event(TEST_CRASH); - log(test_crash, test_stats); - } else { - double elapsed_time = 0; - push_event(POST_TEST, .data = &elapsed_time); - log(post_test, test_stats); - push_event(POST_FINI); - log(post_fini, test_stats); - } - } else { - if ((normal_finish && !cleaned_up) || !test_started) { - log(abnormal_exit, test_stats); - if (!test_started) { - stat_push_event(stats, - suite_stats, - test_stats, - &(struct event) { .kind = TEST_CRASH }); - } - goto cleanup; - } - test_stats->exit_code = status.status; - if (!normal_finish) { - if (test->data->exit_code == 0) { - push_event(TEST_CRASH); - log(abnormal_exit, test_stats); - } else { - double elapsed_time = 0; - push_event(POST_TEST, .data = &elapsed_time); - log(post_test, test_stats); - push_event(POST_FINI); - log(post_fini, test_stats); - } - } - } - cleanup: sfree(test_stats); sfree(proc); @@ -381,6 +409,10 @@ static int criterion_run_all_tests_impl(struct criterion_test_set *set) { fflush(NULL); // flush everything before forking + g_worker_pipe = stdpipe(); + if (g_worker_pipe == NULL) + abort(); + struct criterion_global_stats *stats = stats_init(); map_tests(set, stats, run_test); diff --git a/src/core/runner.h b/src/core/runner.h index 75e12dc..74a3356 100644 --- a/src/core/runner.h +++ b/src/core/runner.h @@ -25,6 +25,7 @@ # define CRITERION_RUNNER_H_ # include "criterion/types.h" +# include "compat/pipe.h" DECL_SECTION_LIMITS(struct criterion_test, cr_tst); DECL_SECTION_LIMITS(struct criterion_suite, cr_sts); @@ -41,4 +42,6 @@ struct criterion_test_set *criterion_init(void); Suite < (struct criterion_suite*) GET_SECTION_END(cr_sts); \ ++Suite) +extern s_pipe_handle *g_worker_pipe; + #endif /* !CRITERION_RUNNER_H_ */ diff --git a/src/core/worker.c b/src/core/worker.c index f6833f8..7f53279 100644 --- a/src/core/worker.c +++ b/src/core/worker.c @@ -63,7 +63,7 @@ struct event *worker_read_event(struct process *proc) { void run_worker(struct worker_context *ctx) { cr_redirect_stdin(); - g_event_pipe = pipe_out(ctx->pipe, 1); + g_event_pipe = pipe_out(ctx->pipe, PIPE_CLOSE); ctx->func(ctx->test, ctx->suite); fclose(g_event_pipe); @@ -76,11 +76,8 @@ void run_worker(struct worker_context *ctx) { struct process *spawn_test_worker(struct criterion_test *test, struct criterion_suite *suite, - f_worker_func func) { - s_pipe_handle *pipe = stdpipe(); - if (pipe == NULL) - abort(); - + f_worker_func func, + s_pipe_handle *pipe) { g_worker_context = (struct worker_context) { .test = test, .suite = suite, @@ -92,10 +89,10 @@ struct process *spawn_test_worker(struct criterion_test *test, s_proc_handle *proc = fork_process(); if (proc == (void *) -1) { - goto cleanup; + return NULL; } else if (proc == NULL) { run_worker(&g_worker_context); - goto cleanup; + return NULL; } ptr = smalloc( @@ -103,16 +100,11 @@ struct process *spawn_test_worker(struct criterion_test *test, .kind = UNIQUE, .dtor = close_process); - *ptr = (struct process) { .proc = proc, .in = pipe_in(pipe, 1) }; -cleanup: - sfree(pipe); + *ptr = (struct process) { .proc = proc, .in = pipe_in(pipe, PIPE_DUP) }; return ptr; } -struct process_status wait_proc(struct process *proc) { - int status; - wait_process(proc->proc, &status); - +struct process_status get_status(int status) { if (WIFEXITED(status)) return (struct process_status) { .kind = EXIT_STATUS, @@ -127,3 +119,10 @@ struct process_status wait_proc(struct process *proc) { return (struct process_status) { .kind = STOPPED }; } + +struct process_status wait_proc(struct process *proc) { + int status; + wait_process(proc->proc, &status); + + return get_status(status); +} diff --git a/src/core/worker.h b/src/core/worker.h index 2f63406..5cc7af7 100644 --- a/src/core/worker.h +++ b/src/core/worker.h @@ -25,7 +25,9 @@ # define PROCESS_H_ # include +# include "criterion/types.h" # include "compat/process.h" +# include "compat/pipe.h" struct process; @@ -40,14 +42,21 @@ struct process_status { int status; }; +struct worker_status { + s_proc_handle proc; + struct process_status status; +}; + void run_worker(struct worker_context *ctx); void set_runner_process(void); void unset_runner_process(void); bool is_runner(void); struct process_status wait_proc(struct process *proc); +struct process_status get_status(int status); struct process *spawn_test_worker(struct criterion_test *test, struct criterion_suite *suite, - void (*func)(struct criterion_test *, struct criterion_suite *)); + f_worker_func func, + s_pipe_handle *pipe); struct event *worker_read_event(struct process *proc); #endif /* !PROCESS_H_ */ diff --git a/src/io/event.c b/src/io/event.c index abb85a6..1ba6a26 100644 --- a/src/io/event.c +++ b/src/io/event.c @@ -28,6 +28,7 @@ #include "criterion/stats.h" #include "criterion/common.h" #include "criterion/hooks.h" +#include "core/worker.h" #include "event.h" FILE *g_event_pipe = NULL; @@ -112,6 +113,20 @@ fail_assert: *ev = (struct event) { .kind = kind, .data = elapsed_time }; return ev; } + case WORKER_TERMINATED: { + struct worker_status *status = malloc(sizeof (struct worker_status)); + if (fread(status, sizeof (struct worker_status), 1, f) == 0) { + free(status); + return NULL; + } + + struct event *ev = smalloc( + .size = sizeof (struct event), + .dtor = destroy_event + ); + *ev = (struct event) { .kind = kind, .data = status }; + return ev; + } default: { struct event *ev = smalloc(sizeof (struct event)); *ev = (struct event) { .kind = kind, .data = NULL }; diff --git a/src/io/event.h b/src/io/event.h index 21d9b21..64c30ac 100644 --- a/src/io/event.h +++ b/src/io/event.h @@ -34,6 +34,10 @@ struct event { void *data; }; +enum other_event_kinds { + WORKER_TERMINATED = 1 << 30, +}; + struct event *read_event(FILE *f); #endif /* !EVENT_H_ */