From f4e444a8d3d35405d6252759d1a9ed93611c0a49 Mon Sep 17 00:00:00 2001 From: Snaipe Date: Mon, 11 Jan 2016 12:21:58 +0100 Subject: [PATCH] Integrated new protocol into the core --- include/criterion/internal/assert.h | 34 +-- src/compat/process.c | 32 ++- src/core/abort.c | 14 +- src/core/client.c | 355 +++++++++++++++++++--------- src/core/client.h | 23 +- src/core/runner.c | 24 +- src/core/runner_coroutine.c | 10 +- src/core/stats.c | 2 + src/core/test.c | 19 +- src/core/theories.c | 55 ++++- src/core/worker.c | 35 +-- src/core/worker.h | 10 +- src/io/asprintf.h | 30 +++ src/io/event.c | 9 +- src/io/event.h | 5 +- src/protocol/connect.c | 16 +- src/protocol/connect.h | 1 + src/protocol/messages.c | 84 ++++++- src/protocol/messages.h | 4 + src/protocol/protocol.h | 7 +- 20 files changed, 539 insertions(+), 230 deletions(-) create mode 100644 src/io/asprintf.h diff --git a/include/criterion/internal/assert.h b/include/criterion/internal/assert.h index fd79d79..c66e52c 100644 --- a/include/criterion/internal/assert.h +++ b/include/criterion/internal/assert.h @@ -95,32 +95,18 @@ CR_END_C_API "" CR_TRANSLATE_DEF_MSG__(CR_VA_HEAD(CR_VA_TAIL(__VA_ARGS__))) \ )) -# define CR_INIT_STATS_(BufSize, MsgVar, ...) CR_EXPAND( \ +# define CR_INIT_STATS_(MsgVar, ...) CR_EXPAND( \ do { \ char *def_msg = CR_EXPAND(CR_TRANSLATE_DEF_MSG_(__VA_ARGS__)); \ char *formatted_msg = NULL; \ - int msglen = cr_asprintf(&formatted_msg, \ - "" CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__))); \ + cr_asprintf(&formatted_msg, "" CR_VA_TAIL(CR_VA_TAIL(__VA_ARGS__))); \ if (formatted_msg && *formatted_msg) { \ MsgVar = formatted_msg; \ CR_STDN free(def_msg); \ } else { \ MsgVar = def_msg; \ - msglen = strlen(def_msg); \ CR_STDN free(formatted_msg); \ } \ - \ - BufSize = sizeof(struct criterion_assert_stats) \ - + sizeof (size_t) + msglen + 1; \ - \ - char *buf = (char*) CR_STDN malloc(BufSize); \ - stat = (struct criterion_assert_stats*) buf; \ - CR_STDN memset(buf, 0, sizeof (struct criterion_assert_stats)); \ - buf += sizeof (struct criterion_assert_stats); \ - *((size_t*) buf) = msglen + 1; \ - buf += sizeof (size_t); \ - CR_STDN strcpy(buf, MsgVar); \ - CR_STDN free(MsgVar); \ } while (0)) # define CR_FAIL_ABORT_ criterion_abort_test @@ -137,16 +123,16 @@ CR_END_C_API bool passed = !!(Condition); \ \ char *msg = NULL; \ - size_t bufsize; \ \ - struct criterion_assert_stats *stat; \ - CR_EXPAND(CR_INIT_STATS_(bufsize, msg, CR_VA_TAIL(__VA_ARGS__))); \ - stat->passed = passed; \ - stat->file = __FILE__; \ - stat->line = __LINE__; \ + CR_EXPAND(CR_INIT_STATS_(msg, CR_VA_TAIL(__VA_ARGS__))); \ + struct criterion_assert_stats stat; \ + stat.passed = passed; \ + stat.file = __FILE__; \ + stat.line = __LINE__; \ + stat.message = msg; \ + criterion_send_assert(&stat); \ \ - criterion_send_event(ASSERT, stat, bufsize); \ - CR_STDN free(stat); \ + CR_STDN free(msg); \ \ if (!passed) \ Fail(); \ diff --git a/src/compat/process.c b/src/compat/process.c index 6bb6872..94155e2 100644 --- a/src/compat/process.c +++ b/src/compat/process.c @@ -27,6 +27,8 @@ #include #include "core/worker.h" #include "core/runner.h" +#include "protocol/protocol.h" +#include "protocol/messages.h" #include "io/event.h" #include "process.h" #include "internal.h" @@ -118,28 +120,24 @@ static struct full_context local_ctx; # endif static void handle_sigchld_pump(void) { - int fd = g_worker_pipe->fds[1]; pid_t pid; int status; while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { - int kind = WORKER_TERMINATED; - struct worker_status ws = { - (s_proc_handle) { pid }, get_status(status) - }; + int result = WIFEXITED(status) + ? criterion_protocol_death_result_type_NORMAL + : criterion_protocol_death_result_type_CRASH; + int code = WIFEXITED(status) + ? WEXITSTATUS(status) + : WTERMSIG(status); - unsigned long long pid_ull = (unsigned long long) pid; + criterion_protocol_msg msg = criterion_message(death, + .result = result, + .has_status = true, + .status = code, + ); - char buf[sizeof (int) + sizeof (pid_ull) + sizeof (struct worker_status)]; - memcpy(buf, &kind, sizeof (kind)); - 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)) { - criterion_perror("Could not write the WORKER_TERMINATED event " - "down the event pipe: %s.\n", - strerror(errno)); - abort(); - } + msg.id.pid = pid; + cr_send_to_runner(&msg); } } diff --git a/src/core/abort.c b/src/core/abort.c index 3fc3518..00a31a3 100644 --- a/src/core/abort.c +++ b/src/core/abort.c @@ -23,7 +23,10 @@ */ #include #include "abort.h" +#include "protocol/protocol.h" +#include "protocol/messages.h" #include "criterion/internal/asprintf-compat.h" +#include "criterion/criterion.h" #include "io/event.h" jmp_buf g_pre_test; @@ -42,12 +45,13 @@ void criterion_test_die(const char *msg, ...) { if (res < 0) abort(); - size_t *buf = malloc(sizeof (size_t) + res + 1); - *buf = res + 1; - memcpy(buf + 1, formatted_msg, res + 1); + criterion_protocol_msg abort_msg = criterion_message(phase, + .phase = criterion_protocol_phase_kind_ABORT, + .name = (char *) criterion_current_test->name, + .message = formatted_msg, + ); + cr_send_to_runner(&abort_msg); - criterion_send_event(TEST_ABORT, buf, sizeof(size_t) + res + 1); - free(buf); free(formatted_msg); exit(0); diff --git a/src/core/client.c b/src/core/client.c index 395a94c..afd7de8 100644 --- a/src/core/client.c +++ b/src/core/client.c @@ -23,7 +23,10 @@ */ #include #include +#include +#include "compat/strtok.h" #include "protocol/protocol.h" +#include "protocol/messages.h" #include "criterion/logging.h" #include "criterion/options.h" #include "io/event.h" @@ -31,46 +34,43 @@ #include "stats.h" #include "client.h" -void nothing(); - -enum protocol_version { - PROTOCOL_V1 = 1, -}; +static void nothing(void) {}; KHASH_MAP_INIT_INT(ht_client, struct client_ctx) -static enum client_state msg_to_state[] = { - [criterion_protocol_submessage_birth_tag] = CS_BIRTH, - [criterion_protocol_submessage_pre_init_tag] = CS_PRE_INIT, - [criterion_protocol_submessage_pre_test_tag] = CS_PRE_TEST, - [criterion_protocol_submessage_post_test_tag] = CS_POST_TEST, - [criterion_protocol_submessage_post_fini_tag] = CS_POST_FINI, - [criterion_protocol_submessage_death_tag] = CS_DEATH, +static enum client_state phase_to_state[] = { + [criterion_protocol_phase_kind_SETUP] = CS_SETUP, + [criterion_protocol_phase_kind_MAIN] = CS_MAIN, + [criterion_protocol_phase_kind_TEARDOWN] = CS_TEARDOWN, + [criterion_protocol_phase_kind_END] = CS_END, + [criterion_protocol_phase_kind_ABORT] = CS_ABORT, + [criterion_protocol_phase_kind_TIMEOUT] = CS_TIMEOUT, }; static const char *state_to_string[] = { - [CS_BIRTH] = "BIRTH", - [CS_PRE_INIT] = "PRE_INIT", - [CS_PRE_TEST] = "PRE_TEST", - [CS_POST_TEST] = "POST_TEST", - [CS_POST_FINI] = "POST_FINI", - [CS_DEATH] = "DEATH", + [CS_SETUP] = "setup", + [CS_MAIN] = "main", + [CS_TEARDOWN] = "teardown", + [CS_END] = "end", + [CS_ABORT] = "abort", + [CS_TIMEOUT] = "timeout", }; -typedef void message_handler(struct server_ctx *, struct client_ctx *, const criterion_protocol_msg *); +typedef bool message_handler(struct server_ctx *, struct client_ctx *, const criterion_protocol_msg *); +typedef bool phase_handler(struct server_ctx *, struct client_ctx *, const criterion_protocol_phase *); -void handle_birth(struct server_ctx *, struct client_ctx *, const criterion_protocol_msg *); -void handle_pre_init(struct server_ctx *, struct client_ctx *, const criterion_protocol_msg *); -void handle_pre_test(struct server_ctx *, struct client_ctx *, const criterion_protocol_msg *); -void handle_post_test(struct server_ctx *, struct client_ctx *, const criterion_protocol_msg *); -void handle_death(struct server_ctx *, struct client_ctx *, const criterion_protocol_msg *); +bool handle_birth(struct server_ctx *, struct client_ctx *, const criterion_protocol_msg *); +bool handle_phase(struct server_ctx *, struct client_ctx *, const criterion_protocol_msg *); +bool handle_death(struct server_ctx *, struct client_ctx *, const criterion_protocol_msg *); +bool handle_assert(struct server_ctx *, struct client_ctx *, const criterion_protocol_msg *); +bool handle_message(struct server_ctx *, struct client_ctx *, const criterion_protocol_msg *); static message_handler *message_handlers[] = { - [criterion_protocol_submessage_birth_tag] = handle_birth, - [criterion_protocol_submessage_pre_init_tag] = handle_pre_init, - [criterion_protocol_submessage_pre_test_tag] = handle_pre_test, - [criterion_protocol_submessage_post_test_tag] = handle_post_test, - [criterion_protocol_submessage_death_tag] = handle_death, + [criterion_protocol_submessage_birth_tag] = handle_birth, + [criterion_protocol_submessage_phase_tag] = handle_phase, + [criterion_protocol_submessage_death_tag] = handle_death, + [criterion_protocol_submessage_assert_tag] = handle_assert, + [criterion_protocol_submessage_message_tag] = handle_message, }; static void get_message_id(char *out, size_t n, const criterion_protocol_msg *msg) { @@ -85,7 +85,19 @@ void init_server_context(struct server_ctx *sctx) { sctx->subprocesses = kh_init(ht_client); } +void destroy_client_context(struct client_ctx *ctx) { + sfree(ctx->worker); +} + void destroy_server_context(struct server_ctx *sctx) { + khint_t k; + (void) k; + struct client_ctx v; + (void) v; + + kh_foreach(sctx->subprocesses, k, v, { + destroy_client_context(&v); + }); kh_destroy(ht_client, sctx->subprocesses); } @@ -101,45 +113,31 @@ struct client_ctx *add_client_from_worker(struct server_ctx *sctx, struct client void remove_client_by_pid(struct server_ctx *sctx, int pid) { khint_t k = kh_get(ht_client, sctx->subprocesses, pid); - if (k != kh_end(sctx->subprocesses)) - kh_del(ht_client, sctx->subprocesses, pid); + if (k != kh_end(sctx->subprocesses)) { + destroy_client_context(&kh_value(sctx->subprocesses, k)); + kh_del(ht_client, sctx->subprocesses, k); + } } static void process_client_message_impl(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_msg *msg) { - if (msg->data.which_value >= CS_MAX_CLIENT_STATES - || msg_to_state[msg->data.which_value] == 0) { - char id[32]; - get_message_id(id, sizeof (id), msg); - - criterion_perror("%s: Received message with malformed data.value tag '%d'.", - id, - msg->data.which_value); - return; - } - - enum client_state new_state = msg_to_state[msg->data.which_value]; - if (new_state < CS_DEATH && new_state != ctx->state + 1) { - char id[32]; - get_message_id(id, sizeof (id), msg); - - criterion_perror("%s: Expected message to change to state '%s', got '%s' instead.", - id, - state_to_string[ctx->state + 1], - state_to_string[new_state]); - return; - } - message_handler *handler = message_handlers[msg->data.which_value]; + bool ack; if (handler) - handler(sctx, ctx, msg); + ack = handler(sctx, ctx, msg); - if (new_state <= CS_DEATH) - ctx->state = new_state; + if (!ack) + send_ack(sctx->socket, true, NULL); } +# define handler_error(Ctx, IdFmt, Id, Fmt, ...) \ + do { \ + criterion_perror(IdFmt Fmt "\n", Id, __VA_ARGS__); \ + send_ack((Ctx)->socket, false, Fmt, __VA_ARGS__); \ + } while (0) + struct client_ctx *process_client_message(struct server_ctx *ctx, const criterion_protocol_msg *msg) { if (msg->version != PROTOCOL_V1) { - criterion_perror("Received message using invalid protocol version number '%d'.", msg->version); + handler_error(ctx, "%s", "", "Received message using invalid protocol version number '%d'.", msg->version); return NULL; } @@ -150,16 +148,12 @@ struct client_ctx *process_client_message(struct server_ctx *ctx, const criterio process_client_message_impl(ctx, &kh_value(ctx->subprocesses, k), msg); return &kh_value(ctx->subprocesses, k); } else { - char id[32]; - get_message_id(id, sizeof (id), msg); - - criterion_perror("%s: Received message identified by a PID " - "that is not a child process.", - id); + handler_error(ctx, "%s", "", "Received message identified by a PID '%ld'" + "that is not a child process.", msg->id.pid); } } break; default: { - criterion_perror("Received message with malformed id tag '%d'.", + handler_error(ctx, "%s", "", "Received message with malformed id tag '%d'.\n", criterion_protocol_msg_pid_tag); } break; } @@ -183,48 +177,183 @@ struct client_ctx *process_client_message(struct server_ctx *ctx, const criterio }); \ } while (0) -void handle_birth(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_msg *msg) { +bool handle_birth(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_msg *msg) { + (void) sctx; + (void) msg; + + ctx->alive = true; + return false; +} + +bool handle_pre_init(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_phase *msg) { + (void) sctx; + (void) msg; + + if (ctx->state == 0) { // only pre_init if there are no nested states + push_event_noreport(PRE_INIT); + report(PRE_INIT, ctx->test); + log(pre_init, ctx->test); + } + return false; +} + +bool handle_pre_test(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_phase *msg) { + (void) sctx; + (void) msg; + + if (ctx->state < CS_MAX_CLIENT_STATES) { + push_event_noreport(PRE_TEST); + report(PRE_TEST, ctx->test); + log(pre_test, ctx->test); + } + return false; +} + +bool handle_post_test(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_phase *msg) { + (void) sctx; + (void) msg; + + if (ctx->state < CS_MAX_CLIENT_STATES) { + double elapsed_time = 0; // TODO: restore elapsed time handling + push_event_noreport(POST_TEST, .data = &elapsed_time); + report(POST_TEST, ctx->tstats); + log(post_test, ctx->tstats); + } + return false; +} + +bool handle_post_fini(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_phase *msg) { (void) sctx; (void) ctx; (void) msg; + if (ctx->state < CS_MAX_CLIENT_STATES) { + push_event(POST_FINI); + log(post_fini, ctx->tstats); + } + return false; } -void handle_pre_init(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_msg *msg) { +bool handle_abort(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_phase *msg) { + (void) sctx; + (void) ctx; + (void) msg; + + enum client_state curstate = ctx->state & (CS_MAX_CLIENT_STATES - 1); + + if (ctx->state < CS_MAX_CLIENT_STATES) { + if (curstate < CS_TEARDOWN) { + double elapsed_time = 0; + push_event(POST_TEST, .data = &elapsed_time); + log(post_test, ctx->tstats); + } + if (curstate < CS_END) { + push_event(POST_FINI); + log(post_fini, ctx->tstats); + } + } else { + struct criterion_theory_stats ths = { + .formatted_args = strdup(msg->message), + .stats = ctx->tstats, + }; + report(THEORY_FAIL, &ths); + log(theory_fail, &ths); + } + return false; +} + +bool handle_timeout(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_phase *msg) { (void) sctx; (void) msg; - push_event_noreport(PRE_INIT); - report(PRE_INIT, ctx->test); - log(pre_init, ctx->test); + if (ctx->state < CS_MAX_CLIENT_STATES) { + ctx->tstats->timed_out = true; + double elapsed_time = ctx->test->data->timeout; + if (elapsed_time == 0 && ctx->suite->data) + elapsed_time = ctx->suite->data->timeout; + push_event(POST_TEST, .data = &elapsed_time); + push_event(POST_FINI); + log(test_timeout, ctx->tstats); + } + return false; } -void handle_pre_test(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_msg *msg) { - (void) sctx; - (void) msg; +# define MAX_TEST_DEPTH 15 - report(PRE_TEST, ctx->test); - log(pre_test, ctx->test); +bool handle_phase(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_msg *msg) { + const criterion_protocol_phase *phase_msg = &msg->data.value.phase; + + enum client_state new_state = phase_to_state[phase_msg->phase]; + enum client_state curstate = ctx->state & (CS_MAX_CLIENT_STATES - 1); + + if (new_state == CS_SETUP) { + if (ctx->state != 0 && ctx->state != CS_MAIN) { + char id[32]; + get_message_id(id, sizeof (id), msg); + + handler_error(sctx, "%s: ", id, "Cannot spawn a subtest outside of the '%s' test phase.", state_to_string[CS_MAIN]); + return true; + } + if (ctx->state & (0xff << MAX_TEST_DEPTH * 2)) { + char id[32]; + get_message_id(id, sizeof (id), msg); + + handler_error(sctx, "%s: ", id, "Cannot nest more than %d tests at a time.", MAX_TEST_DEPTH); + return true; + } + } else if (curstate == CS_END) { + char id[32]; + get_message_id(id, sizeof (id), msg); + + handler_error(sctx, "%s: ", id, "The test has already ended, invalid state '%s'.", state_to_string[new_state]); + return true; + } else if (curstate < CS_END && new_state <= CS_END && new_state != curstate + 1) { + char id[32]; + get_message_id(id, sizeof (id), msg); + + handler_error(sctx, "%s: ", id, "Expected message to change to state '%s', got '%s' instead.", + state_to_string[ctx->state + 1], + state_to_string[new_state]); + return true; + } + + static phase_handler *handlers[] = { + [CS_SETUP] = handle_pre_init, + [CS_MAIN] = handle_pre_test, + [CS_TEARDOWN] = handle_post_test, + [CS_END] = handle_post_fini, + [CS_ABORT] = handle_abort, + [CS_TIMEOUT] = handle_timeout, + }; + + bool ack = handlers[new_state](sctx, ctx, phase_msg); + + if (new_state >= CS_END) { + if ((ctx->state >> 2) != 0) + ctx->state >>= 2; // pop the current state + else + ctx->state = CS_END; + } else if (new_state == CS_SETUP) { + ctx->state <<= 2; // shift the state to make space for a new state + } else { + ++ctx->state; + } + + return ack; } -void handle_post_test(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_msg *msg) { +bool handle_death(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_msg *msg) { (void) sctx; - (void) msg; - double elapsed_time = 0; // TODO: restore elapsed time handling - push_event_noreport(POST_TEST, .data = &elapsed_time); - report(POST_TEST, ctx->tstats); - log(post_test, ctx->tstats); -} + ctx->alive = false; -void handle_death(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_msg *msg) { - (void) sctx; const criterion_protocol_death *death = &msg->data.value.death; + enum client_state curstate = ctx->state & (CS_MAX_CLIENT_STATES - 1); switch (death->result) { case criterion_protocol_death_result_type_CRASH: { - if (ctx->state >= CS_POST_TEST || ctx->state < CS_PRE_TEST) { + if (curstate != CS_MAIN) { log(other_crash, ctx->tstats); - if (ctx->state < CS_PRE_TEST) { + if (ctx->state < CS_MAIN) { stat_push_event(ctx->gstats, ctx->sstats, ctx->tstats, @@ -244,39 +373,19 @@ void handle_death(struct server_ctx *sctx, struct client_ctx *ctx, const criteri } } } break; - case criterion_protocol_death_result_type_TIMEOUT: { - ctx->tstats->timed_out = true; - double elapsed_time = ctx->test->data->timeout; - if (elapsed_time == 0 && ctx->suite->data) - elapsed_time = ctx->suite->data->timeout; - push_event(POST_TEST, .data = &elapsed_time); - push_event(POST_FINI); - log(test_timeout, ctx->tstats); - } break; - case criterion_protocol_death_result_type_ABORT: { - if (ctx->state < CS_POST_TEST) { - double elapsed_time = 0; - push_event(POST_TEST, .data = &elapsed_time); - log(post_test, ctx->tstats); - } - if (ctx->state < CS_POST_FINI) { - push_event(POST_FINI); - log(post_fini, ctx->tstats); - } - } break; case criterion_protocol_death_result_type_NORMAL: { - if ((ctx->state >= CS_POST_TEST && ctx->state < CS_POST_FINI) || ctx->state < CS_PRE_TEST) { + if (curstate == CS_TEARDOWN || ctx->state == CS_SETUP) { log(abnormal_exit, ctx->tstats); - if (ctx->state < CS_PRE_TEST) { + if (ctx->state == CS_SETUP) { stat_push_event(ctx->gstats, ctx->sstats, ctx->tstats, &(struct event) { .kind = TEST_CRASH }); } - return; + break; } ctx->tstats->exit_code = death->status; - if (ctx->state < CS_POST_TEST) { + if (ctx->state == CS_MAIN) { if (ctx->test->data->exit_code == 0) { push_event(TEST_CRASH); log(abnormal_exit, ctx->tstats); @@ -291,4 +400,30 @@ void handle_death(struct server_ctx *sctx, struct client_ctx *ctx, const criteri } break; default: break; } + return false; +} + +bool handle_assert(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_msg *msg) { + (void) sctx; + (void) ctx; + (void) msg; + const criterion_protocol_assert *asrt = &msg->data.value.assert; + struct criterion_assert_stats asrt_stats = { + .message = asrt->message, + .passed = asrt->passed, + .line = asrt->has_line ? asrt->line : 0, + .file = asrt->file ? asrt->file : "unknown", + }; + + push_event_noreport(ASSERT, .data = &asrt_stats); + report(ASSERT, &asrt_stats); + log(assert, &asrt_stats); + return false; +} + +bool handle_message(struct server_ctx *sctx, struct client_ctx *ctx, const criterion_protocol_msg *msg) { + (void) sctx; + (void) ctx; + (void) msg; + return false; } diff --git a/src/core/client.h b/src/core/client.h index 409d220..e2290ed 100644 --- a/src/core/client.h +++ b/src/core/client.h @@ -28,19 +28,20 @@ // order matters here enum client_state { - CS_BIRTH = 1, - CS_PRE_INIT, - CS_PRE_TEST, - CS_POST_TEST, - CS_POST_FINI, - CS_DEATH, + CS_SETUP, + CS_MAIN, + CS_TEARDOWN, + CS_END, - CS_PRE_SUBTEST, - CS_POST_SUBTEST, - - CS_MAX_CLIENT_STATES // always leave at the end + // The states belows are non-states that should not be + // added in the state count + CS_ABORT, + CS_TIMEOUT, }; +// always make it a power of 2 +# define CS_MAX_CLIENT_STATES 4 + enum client_kind { WORKER, EXTERN, @@ -51,6 +52,7 @@ struct client_ctx { struct worker *worker; enum client_state state; + bool alive; struct criterion_global_stats *gstats; struct criterion_suite_stats *sstats; struct criterion_test_stats *tstats; @@ -61,6 +63,7 @@ struct client_ctx { typedef struct kh_ht_client_s khash_t(ht_client); struct server_ctx { + int socket; khash_t(ht_client) *subprocesses; }; diff --git a/src/core/runner.c b/src/core/runner.c index 3fbe043..9490b71 100644 --- a/src/core/runner.c +++ b/src/core/runner.c @@ -267,6 +267,8 @@ static struct client_ctx *spawn_next_client(struct server_ctx *sctx, ccrContext return add_client_from_worker(sctx, &new_ctx, w); } +#include + static void run_tests_async(struct criterion_test_set *set, struct criterion_global_stats *stats, int socket) { @@ -275,10 +277,13 @@ static void run_tests_async(struct criterion_test_set *set, size_t nb_workers = DEF(criterion_options.jobs, get_processor_count()); size_t active_workers = 0; + int has_msg = 0; struct server_ctx sctx; init_server_context(&sctx); + sctx.socket = socket; + // initialization of coroutine run_next_test(set, stats, &ctx); @@ -296,11 +301,10 @@ static void run_tests_async(struct criterion_test_set *set, goto cleanup; criterion_protocol_msg msg = criterion_protocol_msg_init_zero; - while (read_message(socket, &msg) == 1) { + while ((has_msg = read_message(socket, &msg)) == 1) { struct client_ctx *cctx = process_client_message(&sctx, &msg); - if (cctx->state == CS_DEATH && cctx->kind == WORKER) { + if (!cctx->alive && cctx->kind == WORKER) { remove_client_by_pid(&sctx, get_process_id_of(cctx->worker->proc)); - sfree(cctx->worker); cctx = spawn_next_client(&sctx, &ctx); if (!is_runner()) @@ -312,11 +316,14 @@ static void run_tests_async(struct criterion_test_set *set, if (!active_workers) break; + + pb_release(criterion_protocol_msg_fields, &msg); } - destroy_server_context(&sctx); - cleanup: + if (has_msg) + pb_release(criterion_protocol_msg_fields, &msg); + destroy_server_context(&sctx); ccrAbort(ctx); } @@ -342,6 +349,13 @@ static int criterion_run_all_tests_impl(struct criterion_test_set *set) { abort(); } + g_client_socket = connect_client(); + if (g_client_socket < 0) { + criterion_perror("Could not initialize the message client: %s.\n", + strerror(errno)); + abort(); + } + init_proc_compat(); struct criterion_global_stats *stats = stats_init(); diff --git a/src/core/runner_coroutine.c b/src/core/runner_coroutine.c index 57a091a..c451882 100644 --- a/src/core/runner_coroutine.c +++ b/src/core/runner_coroutine.c @@ -63,7 +63,7 @@ static struct worker *run_test(struct criterion_global_stats *stats, .suite_stats = sref(suite_stats), .param = param, }; - return spawn_test_worker(&ctx, run_test_child, g_worker_pipe); + return spawn_test_worker(&ctx, run_test_child); } static INLINE bool is_disabled(struct criterion_test *t, @@ -109,9 +109,11 @@ struct worker *run_next_test(struct criterion_test_set *p_set, ccrBegin(ctx); - ctx->set = p_set; - ctx->stats = p_stats; - ccrReturn(NULL); + do { + ctx->set = p_set; + ctx->stats = p_stats; + ccrReturn(NULL); + } while (ctx->set == NULL && ctx->stats == NULL); for (ctx->ns = ctx->set->suites->first; ctx->ns; ctx->ns = ctx->ns->next) { ctx->suite_set = (void*) (ctx->ns + 1); diff --git a/src/core/stats.c b/src/core/stats.c index f8d46cd..a6f7009 100644 --- a/src/core/stats.c +++ b/src/core/stats.c @@ -108,6 +108,7 @@ static void destroy_test_stats(void *ptr, CR_UNUSED void *meta) { static void destroy_assert_stats(void *ptr, CR_UNUSED void *meta) { s_assert_stats *stats = ptr; free((void *) stats->message); + free((void *) stats->file); } s_test_stats *test_stats_init(struct criterion_test *t) { @@ -192,6 +193,7 @@ static void push_assert(s_glob_stats *stats, .dtor = destroy_assert_stats); memcpy(dup, data, sizeof (s_assert_stats)); dup->message = strdup(data->message); + dup->file = strdup(data->file); dup->next = test->asserts; test->asserts = dup; diff --git a/src/core/test.c b/src/core/test.c index 4295360..2a88a95 100644 --- a/src/core/test.c +++ b/src/core/test.c @@ -27,18 +27,28 @@ #include "core/worker.h" #include "core/report.h" #include "compat/time.h" +#include "protocol/protocol.h" +#include "protocol/messages.h" #include "io/event.h" extern const struct criterion_test *criterion_current_test; extern const struct criterion_suite *criterion_current_suite; +static void send_event(int phase) { + criterion_protocol_msg msg = criterion_message(phase, + .phase = phase, + .name = (char *) criterion_current_test->name, + ); + cr_send_to_runner(&msg); +} + static INLINE void nothing(void) {} void criterion_internal_test_setup(void) { const struct criterion_suite *suite = criterion_current_suite; const struct criterion_test *test = criterion_current_test; - criterion_send_event(PRE_INIT, NULL, 0); + send_event(criterion_protocol_phase_kind_SETUP); if (suite->data) (suite->data->init ? suite->data->init : nothing)(); (test->data->init ? test->data->init : nothing)(); @@ -47,7 +57,7 @@ void criterion_internal_test_setup(void) { void criterion_internal_test_main(void (*fn)(void)) { const struct criterion_test *test = criterion_current_test; - criterion_send_event(PRE_TEST, NULL, 0); + send_event(criterion_protocol_phase_kind_MAIN); struct timespec_compat ts; if (!setjmp(g_pre_test)) { @@ -64,7 +74,7 @@ void criterion_internal_test_main(void (*fn)(void)) { if (!timer_end(&elapsed_time, &ts)) elapsed_time = -1; - criterion_send_event(POST_TEST, &elapsed_time, sizeof(double)); + send_event(criterion_protocol_phase_kind_TEARDOWN); } void criterion_internal_test_teardown(void) { @@ -74,6 +84,7 @@ void criterion_internal_test_teardown(void) { (test->data->fini ? test->data->fini : nothing)(); if (suite->data) (suite->data->fini ? suite->data->fini : nothing)(); - criterion_send_event(POST_FINI, NULL, 0); + + send_event(criterion_protocol_phase_kind_END); } diff --git a/src/core/theories.c b/src/core/theories.c index 28d0d12..b8da613 100644 --- a/src/core/theories.c +++ b/src/core/theories.c @@ -29,6 +29,10 @@ #include #include #include "criterion/theories.h" +#include "protocol/protocol.h" +#include "protocol/messages.h" +#include "io/asprintf.h" +#include "io/event.h" #include "abort.h" struct criterion_theory_context { @@ -172,12 +176,14 @@ static void format_arg(char (*arg)[1024], struct criterion_datapoints *dp, void } } -static void concat_arg(char (*msg)[4096], struct criterion_datapoints *dps, size_t *indices, size_t i) { +# define BUFSIZE 4096 + +static void concat_arg(char (*msg)[BUFSIZE], struct criterion_datapoints *dps, size_t *indices, size_t i) { void *data = ((char*) dps[i].arr) + dps[i].size * indices[i]; char arg[1024]; format_arg(&arg, dps + i, data); - strncat(*msg, arg, sizeof (*msg) - 1); + strncat(*msg, arg, BUFSIZE - 1); } int try_call_theory(struct criterion_theory_context *ctx, void (*fnptr)(void)) { @@ -194,8 +200,25 @@ void cr_theory_main(struct criterion_datapoints *dps, size_t datapoints, void (* size_t *indices = malloc(sizeof (size_t) * datapoints); memset(indices, 0, datapoints * sizeof (size_t)); + int round = 1; volatile bool has_next = true; while (has_next) { + char *name = NULL; + cr_asprintf(&name, "%s::%d", criterion_current_test->name, round); + + criterion_protocol_msg setup_msg = criterion_message(phase, + .phase = criterion_protocol_phase_kind_SETUP, + .name = name, + ); + cr_send_to_runner(&setup_msg); + + criterion_protocol_msg main_msg = criterion_message(phase, + .phase = criterion_protocol_phase_kind_MAIN, + .name = name, + ); + cr_send_to_runner(&main_msg); + + int theory_aborted = 0; if (!setjmp(theory_jmp)) { cr_theory_reset(ctx); for (size_t i = 0; i < datapoints; ++i) { @@ -210,9 +233,11 @@ void cr_theory_main(struct criterion_datapoints *dps, size_t datapoints, void (* } if (!try_call_theory(ctx, fnptr)) { + theory_aborted = 1; + struct { size_t len; - char msg[4096]; + char msg[BUFSIZE]; } result = { .len = 0 }; for (size_t i = 0; i < datapoints - 1; ++i) { @@ -221,11 +246,32 @@ void cr_theory_main(struct criterion_datapoints *dps, size_t datapoints, void (* } concat_arg(&result.msg, dps, indices, datapoints - 1); result.len = strlen(result.msg) + 1; + result.msg[result.len] = '\0'; - criterion_send_event(THEORY_FAIL, &result, result.len + sizeof(size_t)); + criterion_protocol_msg msg = criterion_message(phase, + .phase = criterion_protocol_phase_kind_ABORT, + .name = name, + .message = result.msg + ); + cr_send_to_runner(&msg); } } + if (!theory_aborted) { + criterion_protocol_msg teardown_msg = criterion_message(phase, + .phase = criterion_protocol_phase_kind_TEARDOWN, + .name = name, + ); + cr_send_to_runner(&teardown_msg); + + criterion_protocol_msg end_msg = criterion_message(phase, + .phase = criterion_protocol_phase_kind_END, + .name = name, + ); + cr_send_to_runner(&end_msg); + } + free(name); + for (size_t i = 0; i < datapoints; ++i) { if (indices[i] == dps[i].len - 1) { indices[i] = 0; @@ -235,6 +281,7 @@ void cr_theory_main(struct criterion_datapoints *dps, size_t datapoints, void (* break; } } + ++round; } free(indices); diff --git a/src/core/worker.c b/src/core/worker.c index 363d478..8bc7ed6 100644 --- a/src/core/worker.c +++ b/src/core/worker.c @@ -30,6 +30,8 @@ #include "criterion/types.h" #include "criterion/options.h" #include "criterion/redirect.h" +#include "protocol/protocol.h" +#include "protocol/messages.h" #include "io/event.h" #include "compat/posix.h" #include "worker.h" @@ -51,7 +53,6 @@ bool is_runner(void) { static void close_process(void *ptr, CR_UNUSED void *meta) { struct worker *proc = ptr; - sfree(proc->in); sfree(proc->ctx.suite_stats); sfree(proc->ctx.test_stats); sfree(proc->ctx.stats); @@ -61,9 +62,18 @@ static void close_process(void *ptr, CR_UNUSED void *meta) { void run_worker(struct worker_context *ctx) { cr_redirect_stdin(); g_client_socket = connect_client(); + if (g_client_socket < 0) { + criterion_perror("Could not initialize the message client: %s.\n", + strerror(errno)); + abort(); + } + + // Notify the runner that the test was born + criterion_protocol_msg msg = criterion_message(birth); + cr_send_to_runner(&msg); ctx->func(ctx->test, ctx->suite); - nn_close(g_client_socket); + close_socket(g_client_socket); fflush(NULL); // flush all opened streams if (criterion_options.no_early_exit) @@ -72,13 +82,11 @@ void run_worker(struct worker_context *ctx) { } struct worker *spawn_test_worker(struct execution_context *ctx, - cr_worker_func func, - s_pipe_handle *pipe) { + cr_worker_func func) { g_worker_context = (struct worker_context) { .test = ctx->test, .suite = ctx->suite, .func = func, - .pipe = pipe, .param = ctx->param, }; @@ -103,24 +111,7 @@ struct worker *spawn_test_worker(struct execution_context *ctx, *ptr = (struct worker) { .proc = proc, - .in = pipe_in_handle(pipe, PIPE_DUP), .ctx = *ctx, }; return ptr; } - -struct process_status get_status(int status) { - if (WIFEXITED(status)) - return (struct process_status) { - .kind = EXIT_STATUS, - .status = WEXITSTATUS(status) - }; - - if (WIFSIGNALED(status)) - return (struct process_status) { - .kind = SIGNAL, - .status = WTERMSIG(status) - }; - - return (struct process_status) { .kind = STOPPED }; -} diff --git a/src/core/worker.h b/src/core/worker.h index 414f374..5fcb00c 100644 --- a/src/core/worker.h +++ b/src/core/worker.h @@ -27,7 +27,6 @@ # include # include "criterion/types.h" # include "compat/process.h" -# include "compat/pipe.h" struct test_single_param { size_t size; @@ -50,7 +49,6 @@ struct execution_context { struct worker { int active; s_proc_handle *proc; - s_pipe_file_handle *in; struct execution_context ctx; }; @@ -75,17 +73,11 @@ struct worker_set { 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 worker *proc); -struct process_status get_status(int status); -struct worker *spawn_test_worker(struct execution_context *ctx, - cr_worker_func func, - s_pipe_handle *pipe); -struct event *worker_read_event(struct worker_set *workers, s_pipe_file_handle *pipe); +struct worker *spawn_test_worker(struct execution_context *ctx, cr_worker_func func); #endif /* !PROCESS_H_ */ diff --git a/src/io/asprintf.h b/src/io/asprintf.h new file mode 100644 index 0000000..6270503 --- /dev/null +++ b/src/io/asprintf.h @@ -0,0 +1,30 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2015 Franklin "Snaipe" Mathieu + * + * 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 ASPRINTF_H_ +# define ASPRINTF_H_ + +int cr_vasprintf(char **strp, const char *fmt, va_list ap); +int cr_asprintf(char **strp, const char *fmt, ...); + +#endif /* !ASPRINTF_H_ */ diff --git a/src/io/event.c b/src/io/event.c index aa7d9b7..2ea9ce9 100644 --- a/src/io/event.c +++ b/src/io/event.c @@ -25,15 +25,18 @@ #include "protocol/protocol.h" #include "protocol/messages.h" #include "event.h" +#include "assert.h" int g_client_socket = -1; void criterion_send_assert(struct criterion_assert_stats *stats) { + assert(stats->message); criterion_protocol_msg msg = criterion_message(assert, - .message = stats->message, + .message = (char *) stats->message, .passed = stats->passed, - .file = stats->file, + .file = (char *) stats->file, + .has_line = true, .line = stats->line, ); - write_message(g_client_socket, &msg); + cr_send_to_runner(&msg); } diff --git a/src/io/event.h b/src/io/event.h index a42153e..c492494 100644 --- a/src/io/event.h +++ b/src/io/event.h @@ -40,9 +40,6 @@ struct event { size_t worker_index; }; -enum other_event_kinds { - WORKER_TERMINATED = 1 << 30, - TEST_ABORT, -}; +void criterion_send_assert(struct criterion_assert_stats *stats); #endif /* !EVENT_H_ */ diff --git a/src/protocol/connect.c b/src/protocol/connect.c index 4615217..45e1d2e 100644 --- a/src/protocol/connect.c +++ b/src/protocol/connect.c @@ -23,20 +23,20 @@ */ #include #include -#include +#include #define URL "ipc://criterion.sock" #define errno_ignore(Stmt) do { int err = errno; Stmt; errno = err; } while (0) int bind_server(void) { - int sock = nn_socket(AF_SP, NN_SUB); + int fstrat = NN_FORK_RESET; + nn_setopt(NN_FORK_STRATEGY, &fstrat, sizeof (fstrat)); + + int sock = nn_socket(AF_SP, NN_REP); if (sock < 0) return -1; - if (nn_setsockopt(sock, NN_SUB, NN_SUB_SUBSCRIBE, "", 0) < 0) - goto error; - if (nn_bind(sock, URL) < 0) goto error; @@ -48,7 +48,7 @@ error: {} } int connect_client(void) { - int sock = nn_socket(AF_SP, NN_PUB); + int sock = nn_socket(AF_SP, NN_REQ); if (sock < 0) return -1; @@ -61,3 +61,7 @@ error: {} errno_ignore(nn_close(sock)); return -1; } + +void close_socket(int sock) { + nn_close(sock); +} diff --git a/src/protocol/connect.h b/src/protocol/connect.h index cc84df9..b4f8327 100644 --- a/src/protocol/connect.h +++ b/src/protocol/connect.h @@ -26,5 +26,6 @@ int connect_client(void); int bind_server(void); +void close_socket(int sock); #endif /* !CONNECT_H_ */ diff --git a/src/protocol/messages.c b/src/protocol/messages.c index 2f0e7ba..c836291 100644 --- a/src/protocol/messages.c +++ b/src/protocol/messages.c @@ -24,10 +24,13 @@ #include #include #include "protocol/protocol.h" +#include "criterion/logging.h" +#include "io/event.h" +#include "io/asprintf.h" int read_message(int sock, criterion_protocol_msg *message) { int res; - unsigned char *buf; + unsigned char *buf = NULL; int read = res = nn_recv(sock, &buf, NN_MSG, 0); if (read <= 0) @@ -41,7 +44,8 @@ int read_message(int sock, criterion_protocol_msg *message) { res = 1; cleanup: - nn_freemsg(buf); + if (buf) + nn_freemsg(buf); return res; } @@ -66,3 +70,79 @@ cleanup: free(buf); return res; } + +const char *message_names[] = { + [criterion_protocol_submessage_birth_tag] = "birth", + [criterion_protocol_submessage_phase_tag] = "phase", + [criterion_protocol_submessage_death_tag] = "death", + [criterion_protocol_submessage_message_tag] = "message", + [criterion_protocol_submessage_assert_tag] = "assert", +}; + +void cr_send_to_runner(const criterion_protocol_msg *message) { + if (write_message(g_client_socket, message) != 1) { + criterion_perror("Could not write the \"%s\" message down the event pipe: %s.\n", + message_names[message->data.which_value], + strerror(errno)); + abort(); + } + + unsigned char *buf = NULL; + int read = nn_recv(g_client_socket, &buf, NN_MSG, 0); + + if (read <= 0) { + criterion_perror("Could not read ack: %s.\n", strerror(errno)); + abort(); + } + + criterion_protocol_ack ack; + pb_istream_t stream = pb_istream_from_buffer(buf, read); + if (!pb_decode(&stream, criterion_protocol_ack_fields, &ack)) { + criterion_perror("Could not decode ack: %s.\n", PB_GET_ERROR(&stream)); + abort(); + } + + if (ack.status_code != criterion_protocol_ack_status_OK) { + criterion_perror("Runner returned an error: %s.\n", ack.message ? ack.message : "Unknown error"); + abort(); + } + + if (buf) + nn_freemsg(buf); +} + +void send_ack(int sock, bool ok, const char *msg, ...) { + criterion_protocol_ack ack; + ack.status_code = ok ? criterion_protocol_ack_status_OK : criterion_protocol_ack_status_ERROR; + ack.message = NULL; + + if (!ok) { + va_list ap; + va_start(ap, msg); + if (cr_vasprintf(&ack.message, msg, ap) < 0) + ack.message = NULL; + va_end(ap); + } + + size_t size; + unsigned char *buf = NULL; + if (!pb_get_encoded_size(&size, criterion_protocol_ack_fields, &ack)) { + criterion_perror("Could not calculate the size of an ack.\n"); + abort(); + } + + buf = malloc(size); + pb_ostream_t stream = pb_ostream_from_buffer(buf, size); + if (!pb_encode(&stream, criterion_protocol_ack_fields, &ack)) { + criterion_perror("Could not encode ack: %s.\n", PB_GET_ERROR(&stream)); + abort(); + } + + int written = nn_send(sock, buf, size, 0); + if (written <= 0 || written != (int) size) { + criterion_perror("Could not send ack: %s.\n", strerror(errno)); + abort(); + } + + free(buf); +} diff --git a/src/protocol/messages.h b/src/protocol/messages.h index e44b4c1..f69ff39 100644 --- a/src/protocol/messages.h +++ b/src/protocol/messages.h @@ -24,7 +24,11 @@ #ifndef MESSAGES_H_ # define MESSAGES_H_ +# include "criterion.pb.h" + int write_message(int sock, const criterion_protocol_msg *message); int read_message(int sock, criterion_protocol_msg *message); +void cr_send_to_runner(const criterion_protocol_msg *message); +void send_ack(int sock, bool ok, const char *msg, ...); #endif /* !MESSAGES_H_ */ diff --git a/src/protocol/protocol.h b/src/protocol/protocol.h index b7b9b82..833932d 100644 --- a/src/protocol/protocol.h +++ b/src/protocol/protocol.h @@ -24,11 +24,16 @@ #ifndef PROTOCOL_H_ # define PROTOCOL_H_ +# include # include # include # include "criterion.pb.h" # include "criterion/internal/preprocess.h" +enum protocol_version { + PROTOCOL_V1 = 1, +}; + bool pb_write_string(pb_ostream_t *stream, const pb_field_t *field, void * const *arg); bool pb_read_string(pb_istream_t *stream, const pb_field_t *field, void **arg); @@ -45,7 +50,7 @@ pb_istream_t pb_istream_from_fd(int fd); .data = { \ .which_value = criterion_protocol_submessage_ ## Kind ## _tag, \ .value = { \ - .Kind = { CR_EXPAND(__VA_ARGS__) }, \ + .Kind = { __VA_ARGS__ }, \ } \ } \ }