Added parallelisation code & cleaned up runner code
This commit is contained in:
parent
3af74ac9dd
commit
a086aa995c
9 changed files with 465 additions and 181 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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_ */
|
||||
|
|
181
src/core/coroutine.h
Normal file
181
src/core/coroutine.h
Normal 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 */
|
|
@ -39,6 +39,7 @@
|
|||
#include "abort.h"
|
||||
#include "config.h"
|
||||
#include "common.h"
|
||||
#include "coroutine.h"
|
||||
|
||||
#ifdef HAVE_PCRE
|
||||
#include "string/extmatch.h"
|
||||
|
@ -130,45 +131,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 +186,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 +252,74 @@ 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 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;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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,
|
||||
.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, ¶m);
|
||||
if (criterion_options.fail_fast && stats->tests_failed > 0)
|
||||
break;
|
||||
if (!is_runner())
|
||||
break;
|
||||
}
|
||||
|
||||
if (params.cleanup)
|
||||
params.cleanup(¶ms);
|
||||
}
|
||||
|
||||
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 +355,134 @@ 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_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_) {
|
||||
|
||||
ctx->params = ctx->test->data->param_();
|
||||
for (ctx->i = 0; ctx->i < ctx->params.length; ++ctx->i) {
|
||||
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,
|
||||
&ctx->suite_set->suite,
|
||||
¶m);
|
||||
|
||||
if (!is_runner()) {
|
||||
sfree(ctx->suite_stats);
|
||||
ccrReturn(NULL);
|
||||
}
|
||||
|
||||
ccrReturn(worker);
|
||||
}
|
||||
|
||||
if (ctx->params.cleanup)
|
||||
ctx->params.cleanup(&ctx->params);
|
||||
} else {
|
||||
struct process *worker = run_test(ctx->stats,
|
||||
ctx->suite_stats,
|
||||
ctx->test,
|
||||
&ctx->suite_set->suite,
|
||||
NULL);
|
||||
|
||||
if (!is_runner()) {
|
||||
sfree(ctx->suite_stats);
|
||||
ccrReturn(NULL);
|
||||
}
|
||||
|
||||
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 = 1;
|
||||
struct worker_set workers = {
|
||||
.max_workers = nb_workers,
|
||||
.workers = malloc(sizeof (struct process*) * nb_workers),
|
||||
};
|
||||
|
||||
size_t active_workers = 0;
|
||||
|
||||
// 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);
|
||||
++active_workers;
|
||||
}
|
||||
|
||||
FILE *event_pipe = pipe_in(g_worker_pipe, PIPE_DUP);
|
||||
|
||||
struct event *ev;
|
||||
while ((ev = worker_read_event(&workers, event_pipe)) != NULL) {
|
||||
handle_event(ev);
|
||||
if (ev->kind == WORKER_TERMINATED) {
|
||||
workers.workers[ev->worker_index] = run_next_test(NULL, NULL, &ctx);
|
||||
if (workers.workers[ev->worker_index] == NULL)
|
||||
--active_workers;
|
||||
}
|
||||
sfree(ev);
|
||||
if (!active_workers)
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static int criterion_run_all_tests_impl(struct criterion_test_set *set) {
|
||||
report(PRE_ALL, set);
|
||||
log(pre_all, set);
|
||||
|
@ -461,7 +494,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;
|
||||
|
||||
|
|
|
@ -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_ */
|
||||
|
|
|
@ -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) {
|
||||
|
@ -57,8 +52,23 @@ static void close_process(void *ptr, UNUSED void *meta) {
|
|||
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 +84,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;
|
||||
|
@ -102,7 +110,11 @@ struct process *spawn_test_worker(struct criterion_test *test,
|
|||
.kind = UNIQUE,
|
||||
.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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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_ */
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue