Merge branch 'features/async-io' into bleeding

This commit is contained in:
Snaipe 2015-09-17 15:39:32 +02:00
commit 864b600038
11 changed files with 314 additions and 145 deletions

View file

@ -24,9 +24,7 @@
#ifndef INTERNAL_H_
# define INTERNAL_H_
# include "posix.h"
# ifdef VANILLA_WIN32
# if defined(_WIN32) && !defined(__CYGWIN__)
# define VC_EXTRALEAN
# define WIN32_LEAN_AND_MEAN
# undef _WIN32_WINNT
@ -44,4 +42,6 @@
# include <sys/fcntl.h>
# endif
# include "posix.h"
#endif /* !INTERNAL_H_ */

View file

@ -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;

View file

@ -24,6 +24,7 @@
#ifndef PIPE_H_
# define PIPE_H_
# include <stdio.h>
# 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);

View file

@ -21,12 +21,18 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <assert.h>
#include <string.h>
#include <csptr/smalloc.h>
#include "core/worker.h"
#include "core/runner.h"
#include "io/event.h"
#include "process.h"
#include "internal.h"
#include "pipe-internal.h"
#include <signal.h>
#ifdef VANILLA_WIN32
# define CREATE_SUSPENDED_(Filename, CmdLine, StartupInfo, Info) \
CreateProcessW(Filename, \
@ -42,15 +48,46 @@
# define WRITE_PROCESS_(Proc, What, Size) \
WriteProcessMemory(Proc, &What, &What, Size, NULL);
static int get_win_status(HANDLE handle) {
DWORD exit_code;
GetExitCodeProcess(handle, &exit_code);
unsigned int sig = 0;
switch (exit_code) {
case STATUS_FLOAT_DENORMAL_OPERAND:
case STATUS_FLOAT_DIVIDE_BY_ZERO:
case STATUS_FLOAT_INEXACT_RESULT:
case STATUS_FLOAT_INVALID_OPERATION:
case STATUS_FLOAT_OVERFLOW:
case STATUS_FLOAT_STACK_CHECK:
case STATUS_FLOAT_UNDERFLOW:
case STATUS_INTEGER_DIVIDE_BY_ZERO:
case STATUS_INTEGER_OVERFLOW: sig = SIGFPE; break;
case STATUS_ILLEGAL_INSTRUCTION:
case STATUS_PRIVILEGED_INSTRUCTION:
case STATUS_NONCONTINUABLE_EXCEPTION: sig = SIGILL; break;
case CR_EXCEPTION_TIMEOUT: sig = SIGPROF; break;
case STATUS_ACCESS_VIOLATION:
case STATUS_DATATYPE_MISALIGNMENT:
case STATUS_ARRAY_BOUNDS_EXCEEDED:
case STATUS_GUARD_PAGE_VIOLATION:
case STATUS_IN_PAGE_ERROR:
case STATUS_NO_MEMORY:
case STATUS_INVALID_DISPOSITION:
case STATUS_STACK_OVERFLOW: sig = SIGSEGV; break;
case STATUS_CONTROL_C_EXIT: sig = SIGINT; break;
default: break;
}
return sig ? sig : exit_code << 8;
}
#endif
struct proc_handle {
#ifdef VANILLA_WIN32
HANDLE handle;
#else
pid_t pid;
#endif
};
struct worker_context g_worker_context = {.test = NULL};
@ -71,6 +108,64 @@ static TCHAR g_mapping_name[] = TEXT("WinCriterionWorker");
static struct full_context local_ctx;
#endif
#if defined(__unix__) || defined(__APPLE__)
# 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
#ifdef VANILLA_WIN32
struct wait_context {
HANDLE wait_handle;
HANDLE proc_handle;
};
static void CALLBACK handle_child_terminated(PVOID lpParameter,
BOOLEAN TimerOrWaitFired) {
assert(!TimerOrWaitFired);
struct wait_context *wctx = lpParameter;
int status = get_win_status(wctx->proc_handle);
int kind = WORKER_TERMINATED;
struct worker_status ws = {
(s_proc_handle) { wctx->proc_handle }, get_status(status)
};
char buf[sizeof (int) + sizeof (struct worker_status)];
memcpy(buf, &kind, sizeof (kind));
memcpy(buf + sizeof (kind), &ws, sizeof (ws));
DWORD written;
WriteFile(g_worker_pipe->fhs[1], buf, sizeof (buf), &written, NULL);
HANDLE whandle = wctx->wait_handle;
free(lpParameter);
UnregisterWaitEx(whandle, NULL);
}
#endif
int resume_child(void) {
#ifdef VANILLA_WIN32
HANDLE sharedMem = OpenFileMapping(
@ -111,6 +206,16 @@ int resume_child(void) {
run_worker(&g_worker_context);
return 1;
#else
# if defined(__unix__) || defined(__APPLE__)
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
}
@ -172,6 +277,19 @@ s_proc_handle *fork_process() {
UnmapViewOfFile(ctx);
CloseHandle(sharedMem);
struct wait_context *wctx = malloc(sizeof (struct wait_context));
*wctx = (struct wait_context) {
.proc_handle = info.hProcess,
};
RegisterWaitForSingleObject(
&wctx->wait_handle,
info.hProcess,
handle_child_terminated,
wctx,
INFINITE,
WT_EXECUTELONGFUNCTION | WT_EXECUTEONLYONCE);
s_proc_handle *handle = smalloc(sizeof (s_proc_handle));
*handle = (s_proc_handle) { info.hProcess };
return handle;
@ -191,43 +309,8 @@ s_proc_handle *fork_process() {
void wait_process(s_proc_handle *handle, int *status) {
#ifdef VANILLA_WIN32
WaitForSingleObject(handle->handle, INFINITE);
DWORD exit_code;
GetExitCodeProcess(handle->handle, &exit_code);
*status = get_win_status(handle->handle);
CloseHandle(handle->handle);
unsigned int sig = 0;
switch (exit_code) {
case STATUS_FLOAT_DENORMAL_OPERAND:
case STATUS_FLOAT_DIVIDE_BY_ZERO:
case STATUS_FLOAT_INEXACT_RESULT:
case STATUS_FLOAT_INVALID_OPERATION:
case STATUS_FLOAT_OVERFLOW:
case STATUS_FLOAT_STACK_CHECK:
case STATUS_FLOAT_UNDERFLOW:
case STATUS_INTEGER_DIVIDE_BY_ZERO:
case STATUS_INTEGER_OVERFLOW: sig = SIGFPE; break;
case STATUS_ILLEGAL_INSTRUCTION:
case STATUS_PRIVILEGED_INSTRUCTION:
case STATUS_NONCONTINUABLE_EXCEPTION: sig = SIGILL; break;
case CR_EXCEPTION_TIMEOUT: sig = SIGPROF; break;
case STATUS_ACCESS_VIOLATION:
case STATUS_DATATYPE_MISALIGNMENT:
case STATUS_ARRAY_BOUNDS_EXCEEDED:
case STATUS_GUARD_PAGE_VIOLATION:
case STATUS_IN_PAGE_ERROR:
case STATUS_NO_MEMORY:
case STATUS_INVALID_DISPOSITION:
case STATUS_STACK_OVERFLOW: sig = SIGSEGV; break;
case STATUS_CONTROL_C_EXIT: sig = SIGINT; break;
default: break;
}
*status = sig ? sig : exit_code << 8;
#else
waitpid(handle->pid, status, 0);
#endif

View file

@ -25,8 +25,16 @@
# define COMPAT_PROCESS_H_
# include "criterion/types.h"
# include "internal.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 {

View file

@ -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);

View file

@ -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_ */

View file

@ -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);
}

View file

@ -25,7 +25,9 @@
# define PROCESS_H_
# include <stdbool.h>
# 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_ */

View file

@ -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 };

View file

@ -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_ */