diff --git a/Makefile.am b/Makefile.am index e880bad..482175a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -52,6 +52,7 @@ libcriterion_la_SOURCES = \ src/timer.c \ src/timer.h \ src/ordered-set.c \ + src/posix-compat.c \ src/main.c TARGET = $(PACKAGE)-$(VERSION) diff --git a/appveyor.yml b/appveyor.yml index 5ef46fa..cc59949 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,6 +14,7 @@ init: -P mingw-gcc-core \ -P mingw-pthreads \ -P mingw-w32api \ + -P mingw64-i686-gcc-core \ -P libtool \ -P make \ -P python \ @@ -52,13 +53,13 @@ configuration: Release install: - "%CYG_BASH% -lc 'cd $APPVEYOR_BUILD_FOLDER; ./autogen.sh'" - - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0 +# include -extern int EVENT_PIPE; +extern FILE *g_event_pipe; void send_event(int kind, void *data, size_t size); diff --git a/include/criterion/types.h b/include/criterion/types.h index 3a4672f..6e7dae2 100644 --- a/include/criterion/types.h +++ b/include/criterion/types.h @@ -29,9 +29,9 @@ struct criterion_test_extra_data { int sentinel_; - const char *const identifier_; - const char *const file_; - const unsigned line_; + const char *identifier_; + const char *file_; + unsigned line_; void (*init)(void); void (*fini)(void); int signal; @@ -44,12 +44,14 @@ struct criterion_test { const char *name; const char *category; void (*test)(void); - struct criterion_test_extra_data *const data; + struct criterion_test_extra_data *data; }; struct criterion_suite { const char *name; - struct criterion_test_extra_data *const data; + struct criterion_test_extra_data *data; }; +typedef void (*f_worker_func)(struct criterion_test *, struct criterion_suite *); + #endif /* !CRITERION_TYPES_H_ */ diff --git a/src/event.c b/src/event.c index 339c477..a6a8b3d 100644 --- a/src/event.c +++ b/src/event.c @@ -22,36 +22,37 @@ * THE SOFTWARE. */ -#include +#include #include #include "criterion/stats.h" +#include "criterion/common.h" #include "criterion/hooks.h" #include "event.h" -int EVENT_PIPE = -1; +FILE *g_event_pipe = NULL; void destroy_event(void *ptr, UNUSED void *meta) { struct event *ev = ptr; free(ev->data); } -struct event *read_event(int fd) { +struct event *read_event(FILE *f) { unsigned kind; - if (read(fd, &kind, sizeof (unsigned)) < (ssize_t) sizeof (unsigned)) + if (fread(&kind, sizeof (unsigned), 1, f) == 0) return NULL; switch (kind) { case ASSERT: { const size_t assert_size = sizeof (struct criterion_assert_stats); unsigned char *buf = malloc(assert_size); - if (read(fd, buf, assert_size) < (ssize_t) assert_size) + if (fread(buf, assert_size, 1, f) == 0) return NULL; return unique_ptr(struct event, { .kind = kind, .data = buf }, destroy_event); } case POST_TEST: { double *elapsed_time = malloc(sizeof (double)); - if (read(fd, elapsed_time, sizeof (double)) < (ssize_t) sizeof (double)) + if (fread(elapsed_time, sizeof (double), 1, f) == 0) return NULL; return unique_ptr(struct event, { .kind = kind, .data = elapsed_time }, destroy_event); @@ -65,5 +66,6 @@ void send_event(int kind, void *data, size_t size) { unsigned char buf[sizeof (int) + size]; memcpy(buf, &kind, sizeof (int)); memcpy(buf + sizeof (int), data, size); - write(EVENT_PIPE, buf, sizeof (int) + size); + if (fwrite(buf, sizeof (int) + size, 1, g_event_pipe) == 0) + abort(); } diff --git a/src/event.h b/src/event.h index 405df73..fc8ce4f 100644 --- a/src/event.h +++ b/src/event.h @@ -31,6 +31,6 @@ struct event { void *data; }; -struct event *read_event(int fd); +struct event *read_event(FILE *f); #endif /* !EVENT_H_ */ diff --git a/src/main.c b/src/main.c index 467e49d..832a28b 100644 --- a/src/main.c +++ b/src/main.c @@ -33,6 +33,14 @@ # define VERSION_MSG "Tests compiled with Criterion v" VERSION "\n" +#ifdef HAVE_FNMATCH +# define PATTERN_USAGE \ + " --pattern [PATTERN]: run tests matching the " \ + "given pattern\n" +#else +# define PATTERN_USAGE +#endif + # define USAGE \ VERSION_MSG "\n" \ "usage: %s OPTIONS\n" \ @@ -44,8 +52,7 @@ " -f or --fail-fast: exit after the first failure\n" \ " --ascii: don't use fancy unicode symbols " \ "or colors in the output\n" \ - " --pattern [PATTERN]: run tests matching the " \ - "given pattern\n" \ + PATTERN_USAGE \ " --tap: enables TAP formatting\n" \ " --always-succeed: always exit with 0\n" \ " --no-early-exit: do not exit the test worker " \ @@ -112,7 +119,9 @@ int main(int argc, char *argv[]) { {"list", no_argument, 0, 'l'}, {"ascii", no_argument, 0, 'k'}, {"fail-fast", no_argument, 0, 'f'}, +#ifdef HAVE_FNMATCH {"pattern", required_argument, 0, 'p'}, +#endif {"always-succeed", no_argument, 0, 'y'}, {"no-early-exit", no_argument, 0, 'z'}, {0, 0, 0, 0 } @@ -124,7 +133,9 @@ int main(int argc, char *argv[]) { .fail_fast = !strcmp("1", getenv("CRITERION_FAIL_FAST") ?: "0"), .use_ascii = !strcmp("1", getenv("CRITERION_USE_ASCII") ?: "0"), .logging_threshold = atoi(getenv("CRITERION_VERBOSITY_LEVEL") ?: "2"), +#ifdef HAVE_FNMATCH .pattern = getenv("CRITERION_TEST_PATTERN"), +#endif .output_provider = NORMAL_LOGGING, }; @@ -140,7 +151,9 @@ int main(int argc, char *argv[]) { case 'z': criterion_options.no_early_exit = true; break; case 'k': criterion_options.use_ascii = true; break; case 'f': criterion_options.fail_fast = true; break; +#ifdef HAVE_FNMATCH case 'p': criterion_options.pattern = optarg; break; +#endif case 't': use_tap = true; break; case 'l': do_list_tests = true; break; case 'v': do_print_version = true; break; diff --git a/src/posix-compat.c b/src/posix-compat.c new file mode 100644 index 0000000..ec1e315 --- /dev/null +++ b/src/posix-compat.c @@ -0,0 +1,196 @@ +#include "posix-compat.h" +#include "process.h" + +#ifdef _WIN32 +# define VC_EXTRALEAN +# define WIN32_LEAN_AND_MEAN +# include +# include +# include +# include +# include + +# define CREATE_SUSPENDED_(Filename, CmdLine, StartupInfo, Info) \ + CreateProcessW(Filename, \ + CmdLine, \ + NULL, \ + NULL, \ + TRUE, \ + CREATE_SUSPENDED, \ + NULL, \ + NULL, \ + &(StartupInfo), \ + &(Info)) + +#else +# include +# include +# include +# include +#endif + +#include + +struct proc_handle { +#ifdef _WIN32 + HANDLE handle; +#else + pid_t pid; +#endif +}; + +struct pipe_handle { +#ifdef _WIN32 + HANDLE fhs[2]; +#else + int fds[2]; +#endif +}; + +struct worker_context g_worker_context = {.test = NULL}; + +#ifdef _WIN32 +static struct criterion_test child_test; +static struct criterion_test_extra_data child_test_data; +static struct criterion_suite child_suite; +static struct criterion_test_extra_data child_suite_data; +static struct pipe_handle child_pipe; +#endif + +int resume_child(void) { + if (g_worker_context.test) { + run_worker(&g_worker_context); + return 1; + } + return 0; +} + +s_proc_handle *fork_process() { +#ifdef _WIN32 + PROCESS_INFORMATION info; + STARTUPINFOW si = { .cb = sizeof (STARTUPINFOW) }; + + ZeroMemory(&info, sizeof (info)); + + // Create the suspended child process + wchar_t filename[MAX_PATH]; + GetModuleFileNameW(NULL, filename, MAX_PATH); + + if (!CREATE_SUSPENDED_(filename, GetCommandLineW(), si, info)) + return (void *) -1; + + // Copy context over + f_worker_func child_func = g_worker_context.func; + + child_test = *g_worker_context.test; + child_test_data = *g_worker_context.test->data; + child_suite = *g_worker_context.suite; + child_pipe = *g_worker_context.pipe; + + g_worker_context = (struct worker_context) { &child_test, &child_suite, child_func, &child_pipe }; + child_test.data = &child_test_data; + + if (g_worker_context.suite->data) { + child_suite_data = *g_worker_context.suite->data; + child_suite.data = &child_suite_data; + WriteProcessMemory(info.hProcess, &child_suite_data, &child_suite_data, sizeof (child_suite_data), NULL); + } + + WriteProcessMemory(info.hProcess, &child_test, &child_test, sizeof (child_test), NULL); + WriteProcessMemory(info.hProcess, &child_test_data, &child_test_data, sizeof (child_test_data), NULL); + WriteProcessMemory(info.hProcess, &child_suite, &child_suite, sizeof (child_suite), NULL); + WriteProcessMemory(info.hProcess, &child_pipe, &child_pipe, sizeof (child_pipe), NULL); + WriteProcessMemory(info.hProcess, &g_worker_context, &g_worker_context, sizeof (struct worker_context), NULL); + + ResumeThread(info.hThread); + CloseHandle(info.hThread); + + return unique_ptr(s_proc_handle, { info.hProcess }); +#else + pid_t pid = fork(); + if (pid == -1) + return (void *) -1; + if (pid == 0) + return NULL; + return unique_ptr(s_proc_handle, { pid }); +#endif +} + +void wait_process(s_proc_handle *handle, int *status) { +#ifdef _WIN32 + WaitForSingleObject(handle->handle, INFINITE); + DWORD exit_code; + GetExitCodeProcess(handle->handle, &exit_code); + CloseHandle(handle->handle); + *status = exit_code << 8; +#else + waitpid(handle->pid, status, 0); +#endif +} + +FILE *pipe_in(s_pipe_handle *p) { +#ifdef _WIN32 + CloseHandle(p->fhs[1]); + int fd = _open_osfhandle((intptr_t) p->fhs[0], _O_RDONLY); + if (fd == -1) + return NULL; + FILE *in = _fdopen(fd, "r"); +#else + close(p->fds[1]); + FILE *in = fdopen(p->fds[0], "r"); +#endif + if (!in) + return NULL; + + setvbuf(in, NULL, _IONBF, 0); + return in; +} + +FILE *pipe_out(s_pipe_handle *p) { +#ifdef _WIN32 + CloseHandle(p->fhs[0]); + int fd = _open_osfhandle((intptr_t) p->fhs[1], _O_WRONLY); + if (fd == -1) + return NULL; + FILE *out = _fdopen(fd, "w"); +#else + close(p->fds[0]); + FILE *out = fdopen(p->fds[1], "w"); +#endif + if (!out) + return NULL; + + setvbuf(out, NULL, _IONBF, 0); + return out; +} + +s_pipe_handle *stdpipe() { +#ifdef _WIN32 + HANDLE fhs[2]; + SECURITY_ATTRIBUTES attr = { .nLength = sizeof (SECURITY_ATTRIBUTES), .bInheritHandle = TRUE }; + if (!CreatePipe(fhs, fhs + 1, &attr, 0)) + return NULL; + return unique_ptr(s_pipe_handle, {{ fhs[0], fhs[1] }}); +#else + int fds[2] = { -1, -1 }; + if (pipe(fds) == -1) + return NULL; + return unique_ptr(s_pipe_handle, {{ fds[0], fds[1] }}); +#endif +} + +s_proc_handle *get_current_process() { +#ifdef _WIN32 + return unique_ptr(s_proc_handle, { GetCurrentProcess() }); +#else + return unique_ptr(s_proc_handle, { getpid() }); +#endif +} + +bool is_current_process(s_proc_handle *proc) { +#ifdef _WIN32 + return GetProcessId(proc->handle) == GetProcessId(GetCurrentProcess()); +#else + return proc->pid == getpid(); +#endif +} diff --git a/src/posix-compat.h b/src/posix-compat.h new file mode 100644 index 0000000..3c32e9c --- /dev/null +++ b/src/posix-compat.h @@ -0,0 +1,52 @@ +#ifndef POSIX_COMPAT_H_ +# define POSIX_COMPAT_H_ + +# if !defined(_POSIX_SOURCE) +# define _POSIX_SOURCE 1 +# define TMP_POSIX +# endif +# include +# ifdef TMP_POSIX +# undef _POSIX_SOURCE +# undef TMP_POSIX +# endif + +# ifdef _WIN32 +# define WEXITSTATUS(Status) (((Status) & 0xFF00) >> 8) +# define WTERMSIG(Status) ((Status) & 0x7F) +# define WIFEXITED(Status) (WTERMSIG(Status) == 0) +# define WIFSIGNALED(Status) (((signed char) (WTERMSIG(Status) + 1) >> 1) > 0) +# else +# include +# endif + +#include + +struct proc_handle; +typedef struct proc_handle s_proc_handle; + +struct pipe_handle; +typedef struct pipe_handle s_pipe_handle; + +struct worker_context { + struct criterion_test *test; + struct criterion_suite *suite; + f_worker_func func; + struct pipe_handle *pipe; +}; + +extern struct worker_context g_worker_context; + +int resume_child(void); + +s_pipe_handle *stdpipe(); +FILE *pipe_in(s_pipe_handle *p); +FILE *pipe_out(s_pipe_handle *p); + +s_proc_handle *fork_process(); +void wait_process(s_proc_handle *handle, int *status); + +s_proc_handle *get_current_process(); +bool is_current_process(s_proc_handle *proc); + +#endif /* !POSIX_COMPAT_H_ */ diff --git a/src/process.c b/src/process.c index 0d5689c..83afc73 100644 --- a/src/process.c +++ b/src/process.c @@ -22,71 +22,84 @@ * THE SOFTWARE. */ #include -#include -#include -#include +#include #include #include "criterion/types.h" #include "criterion/options.h" #include "process.h" #include "event.h" +#include "posix-compat.h" struct process { - pid_t pid; - int in; + s_proc_handle *proc; + FILE *in; }; -static pid_t g_runner_pid; +static s_proc_handle *g_current_proc; -void set_runner_pid(void) { - g_runner_pid = getpid(); +void set_runner_process(void) { + g_current_proc = get_current_process(); +} + +void unset_runner_process(void) { + sfree(g_current_proc); } bool is_runner(void) { - return g_runner_pid == getpid(); + return is_current_process(g_current_proc); } static void close_process(void *ptr, UNUSED void *meta) { - close(((struct process *) ptr)->in); + struct process *proc = ptr; + fclose(proc->in); + sfree(proc->proc); } struct event *worker_read_event(struct process *proc) { return read_event(proc->in); } +void run_worker(struct worker_context *ctx) { + fclose(stdin); + g_event_pipe = pipe_out(ctx->pipe); + + ctx->func(ctx->test, ctx->suite); + fclose(g_event_pipe); + + fflush(NULL); // flush all opened streams + if (criterion_options.no_early_exit) + return; + _Exit(0); +} + struct process *spawn_test_worker(struct criterion_test *test, struct criterion_suite *suite, - void (*func)(struct criterion_test *, struct criterion_suite *)) { - int fds[2]; - if (pipe(fds) == -1) + f_worker_func func) { + smart s_pipe_handle *pipe = stdpipe(); + if (pipe == NULL) abort(); - pid_t pid = fork(); - if (pid == -1) { + g_worker_context = (struct worker_context) { + .test = test, + .suite = suite, + .func = func, + .pipe = pipe + }; + s_proc_handle *proc = fork_process(); + if (proc == (void *) -1) { + return NULL; + } else if (proc == NULL) { + run_worker(&g_worker_context); return NULL; - } else if (!pid) { - close(STDIN_FILENO); - close(fds[0]); - EVENT_PIPE = fds[1]; - - func(test, suite); - close(fds[1]); - - fflush(NULL); // flush all opened streams - if (criterion_options.no_early_exit) - return NULL; - else - _exit(0); } - close(fds[1]); - return unique_ptr(struct process, { .pid = pid, .in = fds[0] }, close_process); + return unique_ptr(struct process, { .proc = proc, .in = pipe_in(pipe) }, close_process); } struct process_status wait_proc(struct process *proc) { int status; - waitpid(proc->pid, &status, 0); + wait_process(proc->proc, &status); if (WIFEXITED(status)) return (struct process_status) { .kind = EXIT_STATUS, .status = WEXITSTATUS(status) }; diff --git a/src/process.h b/src/process.h index 02f480e..5e0a6c3 100644 --- a/src/process.h +++ b/src/process.h @@ -25,6 +25,7 @@ # define PROCESS_H_ # include +# include "posix-compat.h" struct process; @@ -39,7 +40,9 @@ struct process_status { int status; }; -void set_runner_pid(void); +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 *spawn_test_worker(struct criterion_test *test, diff --git a/src/report.c b/src/report.c index 5c70807..542f813 100644 --- a/src/report.c +++ b/src/report.c @@ -24,13 +24,17 @@ #define _GNU_SOURCE #include #include -#include #include "criterion/types.h" #include "criterion/stats.h" #include "criterion/logging.h" #include "criterion/options.h" #include "criterion/ordered-set.h" #include "report.h" +#include "config.h" + +#ifdef HAVE_FNMATCH +#include +#endif #define IMPL_CALL_REPORT_HOOKS(Kind) \ IMPL_SECTION_LIMITS(f_report_hook, crit_ ## Kind); \ @@ -53,6 +57,7 @@ __attribute__((always_inline)) static inline void nothing() {} IMPL_REPORT_HOOK(PRE_ALL)(struct criterion_test_set *set) { +#ifdef HAVE_FNMATCH if (criterion_options.pattern) { FOREACH_SET(struct criterion_suite_set *s, set->suites) { if ((s->suite.data && s->suite.data->disabled) || !s->tests) @@ -64,6 +69,7 @@ IMPL_REPORT_HOOK(PRE_ALL)(struct criterion_test_set *set) { } } } +#endif log(pre_all, set); } diff --git a/src/runner.c b/src/runner.c index c4a535e..8c84b1e 100644 --- a/src/runner.c +++ b/src/runner.c @@ -23,7 +23,6 @@ */ #include #include -#include #include #include "criterion/criterion.h" #include "criterion/options.h" @@ -34,6 +33,7 @@ #include "event.h" #include "process.h" #include "timer.h" +#include "posix-compat.h" IMPL_SECTION_LIMITS(struct criterion_test, criterion_tests); IMPL_SECTION_LIMITS(struct criterion_suite, crit_suites); @@ -200,10 +200,12 @@ static void run_test(struct criterion_global_stats *stats, } static int criterion_run_all_tests_impl(void) { + if (resume_child()) // (windows only) resume from the fork + return -1; + smart struct criterion_test_set *set = criterion_init(); report(PRE_ALL, set); - set_runner_pid(); smart struct criterion_global_stats *stats = stats_init(); map_tests(set, stats, run_test); @@ -216,9 +218,12 @@ static int criterion_run_all_tests_impl(void) { } int criterion_run_all_tests(void) { + set_runner_process(); int res = criterion_run_all_tests_impl(); + unset_runner_process(); + if (res == -1) // if this is the test worker terminating - _exit(0); + _Exit(0); return criterion_options.always_succeed || res; }