[Issue #13] Merge branch 'features/windows-compat' into bleeding

This commit is contained in:
Snaipe 2015-03-27 19:41:02 +01:00
commit 0184f8f674
15 changed files with 352 additions and 55 deletions

View file

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

View file

@ -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</dev/null; ./configure"'
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; ./configure CC=i686-w64-mingw32-gcc"'
build_script:
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; make"'
test_script:
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; make check"'
- '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER/samples; exec 0</dev/null; make check"'
after_test:
- '%CYG_BASH% -lc "cat $(find $APPVEYOR_BUILD_FOLDER/samples -iname \"*.log\") /dev/null'

View file

@ -20,6 +20,8 @@ AC_PROG_LN_S
AC_PROG_MAKE_SET
AC_SUBST([LIBTOOL_DEPS])
AC_FUNC_FNMATCH
AC_ARG_ENABLE([gcov],
[AS_HELP_STRING([--enable-gcov],
[Compile the project with converage enabled])],

2
dependencies/csptr vendored

@ -1 +1 @@
Subproject commit 15b825ffb8ffe7309bf524659eb900d6bb1d7c04
Subproject commit 1c96aaec456287dc63eeb31443f409b5476afa0e

View file

@ -25,8 +25,9 @@
# define CRITERION_EVENT_H_
# include <stddef.h>
# include <stdio.h>
extern int EVENT_PIPE;
extern FILE *g_event_pipe;
void send_event(int kind, void *data, size_t size);

View file

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

View file

@ -22,36 +22,37 @@
* THE SOFTWARE.
*/
#include <unistd.h>
#include <stdio.h>
#include <csptr/smart_ptr.h>
#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();
}

View file

@ -31,6 +31,6 @@ struct event {
void *data;
};
struct event *read_event(int fd);
struct event *read_event(FILE *f);
#endif /* !EVENT_H_ */

View file

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

196
src/posix-compat.c Normal file
View file

@ -0,0 +1,196 @@
#include "posix-compat.h"
#include "process.h"
#ifdef _WIN32
# define VC_EXTRALEAN
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <io.h>
# include <fcntl.h>
# include <winnt.h>
# include <stdint.h>
# define CREATE_SUSPENDED_(Filename, CmdLine, StartupInfo, Info) \
CreateProcessW(Filename, \
CmdLine, \
NULL, \
NULL, \
TRUE, \
CREATE_SUSPENDED, \
NULL, \
NULL, \
&(StartupInfo), \
&(Info))
#else
# include <unistd.h>
# include <sys/wait.h>
# include <sys/signal.h>
# include <sys/fcntl.h>
#endif
#include <csptr/smart_ptr.h>
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
}

52
src/posix-compat.h Normal file
View file

@ -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 <stdio.h>
# 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 <sys/wait.h>
# endif
#include <criterion/types.h>
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_ */

View file

@ -22,71 +22,84 @@
* THE SOFTWARE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <csptr/smart_ptr.h>
#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) };

View file

@ -25,6 +25,7 @@
# define PROCESS_H_
# include <stdbool.h>
# 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,

View file

@ -24,13 +24,17 @@
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <fnmatch.h>
#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 <fnmatch.h>
#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);
}

View file

@ -23,7 +23,6 @@
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <csptr/smart_ptr.h>
#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;
}