Merge branch 'features/parallel' into bleeding

This commit is contained in:
Snaipe 2015-09-23 01:21:10 +02:00
commit 525e69a5ab
18 changed files with 634 additions and 202 deletions

View file

@ -104,6 +104,8 @@ set(SOURCE_FILES
src/compat/posix.h
src/compat/alloc.c
src/compat/alloc.h
src/compat/processor.c
src/compat/processor.h
src/io/redirect.c
src/io/event.c
src/io/event.h

View file

@ -36,6 +36,7 @@ struct criterion_options {
bool fail_fast;
const char *pattern;
bool short_filename;
size_t jobs;
};
CR_BEGIN_C_API

View file

@ -65,6 +65,7 @@ macro(add_samples DIR_ SAMPLES_)
ENVIRONMENT "CRITERION_ALWAYS_SUCCEED=1"
ENVIRONMENT "CRITERION_SHORT_FILENAME=1"
ENVIRONMENT "CRITERION_NO_EARLY_EXIT=1" # for coverage
ENVIRONMENT "CRITERION_JOBS=1" # for output ordering
)
endif ()
endforeach()
@ -87,6 +88,7 @@ foreach(script ${SCRIPTS})
ENVIRONMENT "CRITERION_ALWAYS_SUCCEED=1"
ENVIRONMENT "CRITERION_SHORT_FILENAME=1"
ENVIRONMENT "CRITERION_NO_EARLY_EXIT=1" # for coverage
ENVIRONMENT "CRITERION_JOBS=1" # for output ordering
)
endforeach()

View file

@ -40,6 +40,15 @@
# include <sys/wait.h>
# include <sys/signal.h>
# include <sys/fcntl.h>
# include <sys/param.h>
# ifdef BSD
# include <sys/types.h>
typedef unsigned long u_long;
typedef unsigned int u_int;
typedef unsigned short u_short;
typedef unsigned char u_char;
# include <sys/sysctl.h>
# endif
# endif
# include "posix.h"

View file

@ -55,7 +55,6 @@
# define SIGPROF 27
# define CR_EXCEPTION_TIMEOUT 0xC0001042
# else
# include <sys/param.h>
# include <sys/wait.h>
# endif

View file

@ -126,9 +126,12 @@ static void handle_sigchld(UNUSED int sig) {
(s_proc_handle) { pid }, get_status(status)
};
char buf[sizeof (int) + sizeof (struct worker_status)];
unsigned long long pid_ull = (unsigned long long) pid;
char buf[sizeof (int) + sizeof (pid_ull) + sizeof (struct worker_status)];
memcpy(buf, &kind, sizeof (kind));
memcpy(buf + sizeof (kind), &ws, sizeof (ws));
memcpy(buf + sizeof (kind), &pid_ull, sizeof (pid_ull));
memcpy(buf + sizeof (kind) + sizeof (pid_ull), &ws, sizeof (ws));
if (write(fd, &buf, sizeof (buf)) < (ssize_t) sizeof (buf))
abort();
@ -155,9 +158,12 @@ static void CALLBACK handle_child_terminated(PVOID lpParameter,
(s_proc_handle) { wctx->proc_handle }, get_status(status)
};
char buf[sizeof (int) + sizeof (struct worker_status)];
unsigned long long pid_ull = (unsigned long long) GetProcessId(wctx->proc_handle);
char buf[sizeof (int) + sizeof (pid_ull) + sizeof (struct worker_status)];
memcpy(buf, &kind, sizeof (kind));
memcpy(buf + sizeof (kind), &ws, sizeof (ws));
memcpy(buf + sizeof (kind), &pid_ull, sizeof (pid_ull));
memcpy(buf + sizeof (kind) + sizeof (pid_ull), &ws, sizeof (ws));
DWORD written;
WriteFile(g_worker_pipe->fhs[1], buf, sizeof (buf), &written, NULL);
@ -406,3 +412,19 @@ bool is_current_process(s_proc_handle *proc) {
return proc->pid == getpid();
#endif
}
unsigned long long get_process_id(void) {
#ifdef VANILLA_WIN32
return (unsigned long long) GetCurrentProcessId();
#else
return (unsigned long long) getpid();
#endif
}
unsigned long long get_process_id_of(s_proc_handle *proc) {
#ifdef VANILLA_WIN32
return (unsigned long long) GetProcessId(proc->handle);
#else
return (unsigned long long) proc->pid;
#endif
}

View file

@ -55,4 +55,7 @@ void wait_process(s_proc_handle *handle, int *status);
s_proc_handle *get_current_process();
bool is_current_process(s_proc_handle *proc);
unsigned long long get_process_id(void);
unsigned long long get_process_id_of(s_proc_handle *proc);
#endif /* !COMPAT_PROCESS_H_ */

54
src/compat/processor.c Normal file
View file

@ -0,0 +1,54 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* 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 "internal.h"
size_t get_processor_count(void) {
#ifdef _WIN32
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
return (size_t) sysinfo.dwNumberOfProcessors;
#elif defined(BSD)
int mib[2] = { CTL_HW, HW_AVAILCPU };
long long count = 0;
size_t len = sizeof (count);
/* get the number of CPUs from the system */
int res = sysctl(mib, 2, &count, &len, NULL, 0);
if (count < 1 || res == -1) {
mib[1] = HW_NCPU;
res = sysctl(mib, 2, &count, &len, NULL, 0);
if (count < 1 || res == -1)
count = 1;
}
return (size_t) count;
#elif defined(__linux__)
return sysconf(_SC_NPROCESSORS_ONLN);
#else
# error System not supported
#endif
}

31
src/compat/processor.h Normal file
View file

@ -0,0 +1,31 @@
/*
* The MIT License (MIT)
*
* Copyright © 2015 Franklin "Snaipe" Mathieu <http://snai.pe/>
*
* 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.
*/
#ifndef COMPAT_PROCESSOR_H_
# define COMPAT_PROCESSOR_H_
# include <stddef.h>
size_t get_processor_count(void);
#endif /* !COMPAT_PROCESSOR_H_ */

181
src/core/coroutine.h Normal file
View file

@ -0,0 +1,181 @@
/* coroutine.h
*
* Coroutine mechanics, implemented on top of standard ANSI C. See
* http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html for
* a full discussion of the theory behind this.
*
* To use these macros to define a coroutine, you need to write a
* function that looks something like this.
*
* [Simple version using static variables (scr macros)]
* int ascending (void) {
* static int i;
*
* scrBegin;
* for (i=0; i<10; i++) {
* scrReturn(i);
* }
* scrFinish(-1);
* }
*
* [Re-entrant version using an explicit context structure (ccr macros)]
* int ascending (ccrContParam) {
* ccrBeginContext;
* int i;
* ccrEndContext(foo);
*
* ccrBegin(foo);
* for (foo->i=0; foo->i<10; foo->i++) {
* ccrReturn(foo->i);
* }
* ccrFinish(-1);
* }
*
* In the static version, you need only surround the function body
* with `scrBegin' and `scrFinish', and then you can do `scrReturn'
* within the function and on the next call control will resume
* just after the scrReturn statement. Any local variables you need
* to be persistent across an `scrReturn' must be declared static.
*
* In the re-entrant version, you need to declare your persistent
* variables between `ccrBeginContext' and `ccrEndContext'. These
* will be members of a structure whose name you specify in the
* parameter to `ccrEndContext'.
*
* The re-entrant macros will malloc() the state structure on first
* call, and free() it when `ccrFinish' is reached. If you want to
* abort in the middle, you can use `ccrStop' to free the state
* structure immediately (equivalent to an explicit return() in a
* caller-type routine).
*
* A coroutine returning void type may call `ccrReturnV',
* `ccrFinishV' and `ccrStopV', or `scrReturnV', to avoid having to
* specify an empty parameter to the ordinary return macros.
*
* Ground rules:
* - never put `ccrReturn' or `scrReturn' within an explicit `switch'.
* - never put two `ccrReturn' or `scrReturn' statements on the same
* source line.
*
* The caller of a static coroutine calls it just as if it were an
* ordinary function:
*
* void main(void) {
* int i;
* do {
* i = ascending();
* printf("got number %d\n", i);
* } while (i != -1);
* }
*
* The caller of a re-entrant coroutine must provide a context
* variable:
*
* void main(void) {
* ccrContext z = 0;
* do {
* printf("got number %d\n", ascending (&z));
* } while (z);
* }
*
* Note that the context variable is set back to zero when the
* coroutine terminates (by crStop, or by control reaching
* crFinish). This can make the re-entrant coroutines more useful
* than the static ones, because you can tell when they have
* finished.
*
* If you need to dispose of a crContext when it is non-zero (that
* is, if you want to stop calling a coroutine without suffering a
* memory leak), the caller should call `ccrAbort(ctx)' where `ctx'
* is the context variable.
*
* This mechanism could have been better implemented using GNU C
* and its ability to store pointers to labels, but sadly this is
* not part of the ANSI C standard and so the mechanism is done by
* case statements instead. That's why you can't put a crReturn()
* inside a switch() statement.
*/
/*
* coroutine.h is copyright 1995,2000 Simon Tatham.
*
* 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 SIMON TATHAM 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.
*
* $Id$
*/
#ifndef COROUTINE_H
#define COROUTINE_H
#include <stdlib.h>
/*
* `scr' macros for static coroutines.
*/
#define scrBegin static int scrLine = 0; switch(scrLine) { case 0:;
#define scrFinish(z) } return (z)
#define scrFinishV } return
#define scrReturn(z) \
do {\
scrLine=__LINE__;\
return (z); case __LINE__:;\
} while (0)
#define scrReturnV \
do {\
scrLine=__LINE__;\
return; case __LINE__:;\
} while (0)
/*
* `ccr' macros for re-entrant coroutines.
*/
#define ccrContParam void **ccrParam
#define ccrBeginContext struct ccrContextTag { int ccrLine
#define ccrEndContext(x) } *x = (struct ccrContextTag *)*ccrParam
#define ccrBegin(x) if(!x) {x= *ccrParam=malloc(sizeof(*x)); x->ccrLine=0;}\
if (x) switch(x->ccrLine) { case 0:;
#define ccrFinish(z) } free(*ccrParam); *ccrParam=0; return (z)
#define ccrFinishV } free(*ccrParam); *ccrParam=0; return
#define ccrReturn(z) \
do {\
((struct ccrContextTag *)*ccrParam)->ccrLine=__LINE__;\
return (z); case __LINE__:;\
} while (0)
#define ccrReturnV \
do {\
((struct ccrContextTag *)*ccrParam)->ccrLine=__LINE__;\
return; case __LINE__:;\
} while (0)
#define ccrStop(z) do{ free(*ccrParam); *ccrParam=0; return (z); }while(0)
#define ccrStopV do{ free(*ccrParam); *ccrParam=0; return; }while(0)
#define ccrContext void *
#define ccrAbort(ctx) do { free (ctx); ctx = 0; } while (0)
#endif /* COROUTINE_H */

View file

@ -30,6 +30,7 @@
#include "criterion/logging.h"
#include "compat/time.h"
#include "compat/posix.h"
#include "compat/processor.h"
#include "string/i18n.h"
#include "io/event.h"
#include "stats.h"
@ -39,6 +40,7 @@
#include "abort.h"
#include "config.h"
#include "common.h"
#include "coroutine.h"
#ifdef HAVE_PCRE
#include "string/extmatch.h"
@ -130,45 +132,6 @@ struct criterion_test_set *criterion_init(void) {
return set;
}
typedef void (*f_test_run)(struct criterion_global_stats *,
struct criterion_suite_stats *,
struct criterion_test *,
struct criterion_suite *);
static void map_tests(struct criterion_test_set *set,
struct criterion_global_stats *stats,
f_test_run fun) {
FOREACH_SET(struct criterion_suite_set *s, set->suites) {
if (!s->tests)
continue;
report(PRE_SUITE, s);
log(pre_suite, s);
struct criterion_suite_stats *suite_stats = suite_stats_init(&s->suite);
struct event ev = { .kind = PRE_SUITE };
stat_push_event(stats, suite_stats, NULL, &ev);
FOREACH_SET(struct criterion_test *t, s->tests) {
fun(stats, suite_stats, t, &s->suite);
if (criterion_options.fail_fast && stats->tests_failed > 0)
break;
if (!is_runner()) {
sfree(suite_stats);
return;
}
}
report(POST_SUITE, suite_stats);
log(post_suite, suite_stats);
sfree(suite_stats);
}
}
static void run_test_child(struct criterion_test *test,
struct criterion_suite *suite) {
@ -224,17 +187,6 @@ static INLINE bool is_disabled(struct criterion_test *t,
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) {
@ -301,120 +253,63 @@ static void handle_worker_terminated(struct event *ev,
}
}
static void run_test(struct criterion_global_stats *stats,
static void handle_event(struct event *ev) {
struct execution_context *ctx = &ev->worker->ctx;
if (ev->kind < WORKER_TERMINATED)
stat_push_event(ctx->stats, ctx->suite_stats, ctx->test_stats, ev);
switch (ev->kind) {
case PRE_INIT:
report(PRE_INIT, ctx->test);
log(pre_init, ctx->test);
break;
case PRE_TEST:
report(PRE_TEST, ctx->test);
log(pre_test, ctx->test);
ctx->test_started = true;
break;
case THEORY_FAIL: {
struct criterion_theory_stats ths = {
.formatted_args = (char*) ev->data,
.stats = ctx->test_stats,
};
report(THEORY_FAIL, &ths);
log(theory_fail, &ths);
} break;
case ASSERT:
report(ASSERT, ev->data);
log(assert, ev->data);
break;
case POST_TEST:
report(POST_TEST, ctx->test_stats);
log(post_test, ctx->test_stats);
ctx->normal_finish = true;
break;
case POST_FINI:
report(POST_FINI, ctx->test_stats);
log(post_fini, ctx->test_stats);
ctx->cleaned_up = true;
break;
case WORKER_TERMINATED:
handle_worker_terminated(ev, ctx);
break;
}
}
static struct process *run_test(struct criterion_global_stats *stats,
struct criterion_suite_stats *suite_stats,
struct criterion_test *test,
struct criterion_suite *suite,
struct criterion_test_stats *test_stats,
struct test_single_param *param) {
struct criterion_test_stats *test_stats = test_stats_init(test);
struct process *proc = NULL;
if (is_disabled(test, suite)) {
stat_push_event(stats,
suite_stats,
test_stats,
&(struct event) { .kind = PRE_INIT });
goto cleanup;
}
proc = spawn_test_worker(test, suite, run_test_child, g_worker_pipe, param);
if (proc == NULL && !is_runner())
goto cleanup;
struct execution_context ctx = {
.stats = stats,
.test = test,
.test_stats = test_stats,
.suite = suite,
.suite_stats = suite_stats,
.stats = sref(stats),
.test = test_stats->test,
.test_stats = sref(test_stats),
.suite = suite_stats->suite,
.suite_stats = sref(suite_stats),
.param = param,
};
struct event *ev;
while ((ev = worker_read_event(proc)) != NULL) {
if (ev->kind < WORKER_TERMINATED)
stat_push_event(stats, suite_stats, test_stats, ev);
switch (ev->kind) {
case PRE_INIT:
report(PRE_INIT, test);
log(pre_init, test);
break;
case PRE_TEST:
report(PRE_TEST, test);
log(pre_test, test);
ctx.test_started = true;
break;
case THEORY_FAIL: {
struct criterion_theory_stats ths = {
.formatted_args = (char*) ev->data,
.stats = test_stats,
};
report(THEORY_FAIL, &ths);
log(theory_fail, &ths);
} break;
case ASSERT:
report(ASSERT, ev->data);
log(assert, ev->data);
break;
case POST_TEST:
report(POST_TEST, test_stats);
log(post_test, test_stats);
ctx.normal_finish = true;
break;
case POST_FINI:
report(POST_FINI, test_stats);
log(post_fini, test_stats);
ctx.cleaned_up = true;
break;
case WORKER_TERMINATED:
handle_worker_terminated(ev, &ctx);
sfree(ev);
goto cleanup;
}
sfree(ev);
}
return spawn_test_worker(&ctx, run_test_child, g_worker_pipe);
cleanup:
sfree(test_stats);
sfree(proc);
}
static void run_test_param(struct criterion_global_stats *stats,
struct criterion_suite_stats *suite_stats,
struct criterion_test *test,
struct criterion_suite *suite) {
if (!test->data->param_)
return;
struct criterion_test_params params = test->data->param_();
for (size_t i = 0; i < params.length; ++i) {
struct test_single_param param = { params.size, (char *) params.params + i * params.size };
run_test(stats, suite_stats, test, suite, &param);
if (criterion_options.fail_fast && stats->tests_failed > 0)
break;
if (!is_runner())
break;
}
if (params.cleanup)
params.cleanup(&params);
}
static void run_test_switch(struct criterion_global_stats *stats,
struct criterion_suite_stats *suite_stats,
struct criterion_test *test,
struct criterion_suite *suite) {
switch (test->data->kind_) {
case CR_TEST_NORMAL:
run_test(stats, suite_stats, test, suite, NULL);
break;
case CR_TEST_PARAMETERIZED:
run_test_param(stats, suite_stats, test, suite);
break;
default: break;
}
}
#ifdef HAVE_PCRE
@ -450,6 +345,179 @@ void criterion_finalize(struct criterion_test_set *set) {
sfree(set);
}
static struct process *run_next_test(struct criterion_test_set *p_set,
struct criterion_global_stats *p_stats,
ccrContParam) {
ccrBeginContext;
struct criterion_suite_set *suite_set;
struct criterion_test *test;
struct criterion_suite_stats *suite_stats;
struct criterion_test_stats *test_stats;
struct criterion_test_set *set;
struct criterion_global_stats *stats;
struct criterion_test_params params;
struct criterion_ordered_set_node *ns;
struct criterion_ordered_set_node *nt;
size_t i;
ccrEndContext(ctx);
ccrBegin(ctx);
ctx->set = p_set;
ctx->stats = p_stats;
ccrReturn(NULL);
for (ctx->ns = ctx->set->suites->first; ctx->ns; ctx->ns = ctx->ns->next) {
ctx->suite_set = (void*) ctx->ns->data;
if (!ctx->suite_set->tests)
continue;
report(PRE_SUITE, ctx->suite_set);
log(pre_suite, ctx->suite_set);
ctx->suite_stats = suite_stats_init(&ctx->suite_set->suite);
struct event ev = { .kind = PRE_SUITE };
stat_push_event(ctx->stats, ctx->suite_stats, NULL, &ev);
for (ctx->nt = ctx->suite_set->tests->first; ctx->nt; ctx->nt = ctx->nt->next) {
ctx->test = (void*) ctx->nt->data;
if (ctx->test->data->kind_ == CR_TEST_PARAMETERIZED
&& ctx->test->data->param_) {
if (is_disabled(ctx->test, ctx->suite_stats->suite)) {
ctx->test_stats = test_stats_init(ctx->test);
stat_push_event(ctx->stats,
ctx->suite_stats,
ctx->test_stats,
&(struct event) { .kind = PRE_INIT });
sfree(ctx->test_stats);
continue;
}
ctx->params = ctx->test->data->param_();
for (ctx->i = 0; ctx->i < ctx->params.length; ++ctx->i) {
ctx->test_stats = test_stats_init(ctx->test);
struct test_single_param param = {
ctx->params.size,
(char *) ctx->params.params + ctx->i * ctx->params.size
};
struct process *worker = run_test(ctx->stats,
ctx->suite_stats,
ctx->test_stats,
&param);
if (!is_runner()) {
sfree(ctx->suite_stats);
sfree(ctx->test_stats);
ccrReturn(NULL);
} else {
ccrReturn(worker);
}
}
if (ctx->params.cleanup)
ctx->params.cleanup(&ctx->params);
} else {
ctx->test_stats = test_stats_init(ctx->test);
if (is_disabled(ctx->test, ctx->suite_stats->suite)) {
stat_push_event(ctx->stats,
ctx->suite_stats,
ctx->test_stats,
&(struct event) { .kind = PRE_INIT });
sfree(ctx->test_stats);
continue;
}
struct process *worker = run_test(ctx->stats,
ctx->suite_stats,
ctx->test_stats,
NULL);
sfree(ctx->test_stats);
if (!is_runner()) {
sfree(ctx->suite_stats);
ccrReturn(NULL);
} else {
ccrReturn(worker);
}
}
}
report(POST_SUITE, ctx->suite_stats);
log(post_suite, ctx->suite_stats);
sfree(ctx->suite_stats);
}
ccrFinish(NULL);
}
static void run_tests_async(struct criterion_test_set *set,
struct criterion_global_stats *stats) {
ccrContext ctx = 0;
size_t nb_workers = DEF(criterion_options.jobs, get_processor_count());
struct worker_set workers = {
.max_workers = nb_workers,
.workers = calloc(nb_workers, sizeof (struct process*)),
};
size_t active_workers = 0;
FILE *event_pipe = pipe_in(g_worker_pipe, PIPE_DUP);
struct event *ev = NULL;
// initialization of coroutine
run_next_test(set, stats, &ctx);
for (size_t i = 0; i < nb_workers; ++i) {
workers.workers[i] = run_next_test(NULL, NULL, &ctx);
if (!is_runner())
goto cleanup;
if (!ctx)
break;
++active_workers;
}
if (!active_workers)
goto cleanup;
while ((ev = worker_read_event(&workers, event_pipe)) != NULL) {
handle_event(ev);
size_t wi = ev->worker_index;
if (ev->kind == WORKER_TERMINATED) {
sfree(workers.workers[wi]);
workers.workers[wi] = ctx ? run_next_test(NULL, NULL, &ctx) : NULL;
if (!is_runner())
goto cleanup;
if (workers.workers[wi] == NULL)
--active_workers;
}
sfree(ev);
if (!active_workers)
break;
}
ev = NULL;
cleanup:
fclose(event_pipe);
sfree(ev);
free(workers.workers);
ccrAbort(ctx);
}
static int criterion_run_all_tests_impl(struct criterion_test_set *set) {
report(PRE_ALL, set);
log(pre_all, set);
@ -461,7 +529,7 @@ static int criterion_run_all_tests_impl(struct criterion_test_set *set) {
abort();
struct criterion_global_stats *stats = stats_init();
map_tests(set, stats, run_test_switch);
run_tests_async(set, stats);
int result = is_runner() ? stats->tests_failed == 0 : -1;

View file

@ -42,6 +42,4 @@ 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

@ -72,6 +72,7 @@ static void destroy_stats(void *ptr, UNUSED void *meta) {
s_glob_stats *stats_init(void) {
s_glob_stats *stats = smalloc(
.size = sizeof (s_glob_stats),
.kind = SHARED,
.dtor = destroy_stats
);
*stats = (s_glob_stats) { .suites = NULL };

View file

@ -32,11 +32,6 @@
#include "compat/posix.h"
#include "worker.h"
struct process {
s_proc_handle *proc;
FILE *in;
};
static s_proc_handle *g_current_proc;
void set_runner_process(void) {
@ -54,11 +49,29 @@ bool is_runner(void) {
static void close_process(void *ptr, UNUSED void *meta) {
struct process *proc = ptr;
fclose(proc->in);
sfree(proc->ctx.suite_stats);
sfree(proc->ctx.test_stats);
sfree(proc->ctx.stats);
sfree(proc->proc);
}
struct event *worker_read_event(struct process *proc) {
return read_event(proc->in);
struct event *worker_read_event(struct worker_set *workers, FILE *pipe) {
struct event *ev = read_event(pipe);
if (ev) {
ev->worker_index = -1;
for (size_t i = 0; i < workers->max_workers; ++i) {
if (!workers->workers[i])
continue;
if (get_process_id_of(workers->workers[i]->proc) == ev->pid) {
ev->worker = workers->workers[i];
ev->worker_index = i;
return ev;
}
}
abort();
}
return NULL;
}
void run_worker(struct worker_context *ctx) {
@ -74,17 +87,15 @@ void run_worker(struct worker_context *ctx) {
_Exit(0);
}
struct process *spawn_test_worker(struct criterion_test *test,
struct criterion_suite *suite,
struct process *spawn_test_worker(struct execution_context *ctx,
f_worker_func func,
s_pipe_handle *pipe,
struct test_single_param *param) {
s_pipe_handle *pipe) {
g_worker_context = (struct worker_context) {
.test = test,
.suite = suite,
.test = ctx->test,
.suite = ctx->suite,
.func = func,
.pipe = pipe,
.param = param,
.param = ctx->param,
};
struct process *ptr = NULL;
@ -94,15 +105,22 @@ struct process *spawn_test_worker(struct criterion_test *test,
abort();
} else if (proc == NULL) {
run_worker(&g_worker_context);
sfree(ctx->test_stats);
sfree(ctx->suite_stats);
sfree(ctx->stats);
return NULL;
}
ptr = smalloc(
.size = sizeof (struct process),
.kind = UNIQUE,
.kind = SHARED,
.dtor = close_process);
*ptr = (struct process) { .proc = proc, .in = pipe_in(pipe, PIPE_DUP) };
*ptr = (struct process) {
.proc = proc,
.in = pipe_in(pipe, PIPE_DUP),
.ctx = *ctx,
};
return ptr;
}

View file

@ -29,7 +29,29 @@
# include "compat/process.h"
# include "compat/pipe.h"
struct process;
struct test_single_param {
size_t size;
void *ptr;
};
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;
struct test_single_param *param;
};
struct process {
int active;
s_proc_handle *proc;
FILE *in;
struct execution_context ctx;
};
enum status_kind {
EXIT_STATUS,
@ -47,22 +69,22 @@ struct worker_status {
struct process_status status;
};
struct test_single_param {
size_t size;
void *ptr;
struct worker_set {
struct process **workers;
size_t max_workers;
};
extern s_pipe_handle *g_worker_pipe;
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,
struct process *spawn_test_worker(struct execution_context *ctx,
f_worker_func func,
s_pipe_handle *pipe,
struct test_single_param *param);
struct event *worker_read_event(struct process *proc);
s_pipe_handle *pipe);
struct event *worker_read_event(struct worker_set *workers, FILE *pipe);
#endif /* !PROCESS_H_ */

View file

@ -120,6 +120,11 @@ int list_tests(bool unicode) {
return 0;
}
int atou(const char *str) {
int res = atoi(str);
return res < 0 ? 0 : res;
}
int criterion_handle_args(int argc, char *argv[], bool handle_unknown_arg) {
static struct option opts[] = {
{"verbose", optional_argument, 0, 'b'},
@ -128,6 +133,7 @@ int criterion_handle_args(int argc, char *argv[], bool handle_unknown_arg) {
{"help", no_argument, 0, 'h'},
{"list", no_argument, 0, 'l'},
{"ascii", no_argument, 0, 'k'},
{"jobs", required_argument, 0, 'j'},
{"fail-fast", no_argument, 0, 'f'},
{"short-filename", no_argument, 0, 'S'},
#ifdef HAVE_PCRE
@ -151,7 +157,8 @@ int criterion_handle_args(int argc, char *argv[], bool handle_unknown_arg) {
opt->no_early_exit = !strcmp("1", DEF(getenv("CRITERION_NO_EARLY_EXIT") , "0"));
opt->fail_fast = !strcmp("1", DEF(getenv("CRITERION_FAIL_FAST") , "0"));
opt->use_ascii = use_ascii;
opt->logging_threshold = atoi(DEF(getenv("CRITERION_VERBOSITY_LEVEL"), "2"));
opt->jobs = atou(DEF(getenv("CRITERION_JOBS"), "0"));
opt->logging_threshold = atou(DEF(getenv("CRITERION_VERBOSITY_LEVEL"), "2"));
opt->short_filename = !strcmp("1", DEF(getenv("CRITERION_SHORT_FILENAME"), "0"));
#ifdef HAVE_PCRE
opt->pattern = getenv("CRITERION_TEST_PATTERN");
@ -162,12 +169,13 @@ int criterion_handle_args(int argc, char *argv[], bool handle_unknown_arg) {
bool do_list_tests = false;
bool do_print_version = false;
bool do_print_usage = false;
for (int c; (c = getopt_long(argc, argv, "hvlfS", opts, NULL)) != -1;) {
for (int c; (c = getopt_long(argc, argv, "hvlfj:S", opts, NULL)) != -1;) {
switch (c) {
case 'b': criterion_options.logging_threshold = atoi(DEF(optarg, "1")); break;
case 'b': criterion_options.logging_threshold = atou(DEF(optarg, "1")); break;
case 'y': criterion_options.always_succeed = true; break;
case 'z': criterion_options.no_early_exit = true; break;
case 'k': criterion_options.use_ascii = true; break;
case 'j': criterion_options.jobs = atou(optarg); break;
case 'f': criterion_options.fail_fast = true; break;
case 'S': criterion_options.short_filename = true; break;
#ifdef HAVE_PCRE

View file

@ -49,6 +49,10 @@ struct event *read_event(FILE *f) {
if (fread(&kind, sizeof (unsigned), 1, f) == 0)
return NULL;
unsigned long long pid;
if (fread(&pid, sizeof (unsigned long long), 1, f) == 0)
return NULL;
switch (kind) {
case ASSERT: {
const size_t assert_size = sizeof (struct criterion_assert_stats);
@ -73,7 +77,7 @@ struct event *read_event(FILE *f) {
.size = sizeof (struct event),
.dtor = destroy_assert_event
);
*ev = (struct event) { .kind = kind, .data = buf };
*ev = (struct event) { .pid = pid, .kind = kind, .data = buf };
return ev;
fail_assert:
@ -96,7 +100,7 @@ fail_assert:
.size = sizeof (struct event),
.dtor = destroy_event
);
*ev = (struct event) { .kind = kind, .data = buf };
*ev = (struct event) { .pid = pid, .kind = kind, .data = buf };
return ev;
}
case POST_TEST: {
@ -110,7 +114,7 @@ fail_assert:
.size = sizeof (struct event),
.dtor = destroy_event
);
*ev = (struct event) { .kind = kind, .data = elapsed_time };
*ev = (struct event) { .pid = pid, .kind = kind, .data = elapsed_time };
return ev;
}
case WORKER_TERMINATED: {
@ -124,22 +128,26 @@ fail_assert:
.size = sizeof (struct event),
.dtor = destroy_event
);
*ev = (struct event) { .kind = kind, .data = status };
*ev = (struct event) { .pid = pid, .kind = kind, .data = status };
return ev;
}
default: {
struct event *ev = smalloc(sizeof (struct event));
*ev = (struct event) { .kind = kind, .data = NULL };
*ev = (struct event) { .pid = pid, .kind = kind, .data = NULL };
return ev;
}
}
}
void send_event(int kind, void *data, size_t size) {
unsigned char *buf = malloc(sizeof (int) + size);
unsigned long long pid = get_process_id();
unsigned char *buf = malloc(sizeof (int) + sizeof (pid) + size);
memcpy(buf, &kind, sizeof (int));
memcpy(buf + sizeof (int), data, size);
if (fwrite(buf, sizeof (int) + size, 1, g_event_pipe) == 0)
memcpy(buf + sizeof (int), &pid, sizeof (pid));
memcpy(buf + sizeof (int) + sizeof (pid), data, size);
if (fwrite(buf, sizeof (int) + sizeof (pid) + size, 1, g_event_pipe) == 0)
abort();
free(buf);
}

View file

@ -25,13 +25,18 @@
# define EVENT_H_
# include "criterion/event.h"
# include "core/worker.h"
# include <stdio.h>
extern FILE *g_event_pipe;
struct event {
unsigned long long pid;
int kind;
void *data;
struct process *worker;
size_t worker_index;
};
enum other_event_kinds {