Integrated new protocol into the core

This commit is contained in:
Snaipe 2016-01-11 12:21:58 +01:00
parent 131a6a646b
commit f4e444a8d3
20 changed files with 539 additions and 230 deletions

View file

@ -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(); \

View file

@ -27,6 +27,8 @@
#include <csptr/smalloc.h>
#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);
}
}

View file

@ -23,7 +23,10 @@
*/
#include <string.h>
#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);

View file

@ -23,7 +23,10 @@
*/
#include <stdio.h>
#include <inttypes.h>
#include <csptr/smalloc.h>
#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;
}

View file

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

View file

@ -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 <stdio.h>
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();

View file

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

View file

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

View file

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

View file

@ -29,6 +29,10 @@
#include <assert.h>
#include <limits.h>
#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);

View file

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

View file

@ -27,7 +27,6 @@
# include <stdbool.h>
# 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_ */

30
src/io/asprintf.h Normal file
View file

@ -0,0 +1,30 @@
/*
* 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 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_ */

View file

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

View file

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

View file

@ -23,20 +23,20 @@
*/
#include <errno.h>
#include <nanomsg/nn.h>
#include <nanomsg/pubsub.h>
#include <nanomsg/reqrep.h>
#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);
}

View file

@ -26,5 +26,6 @@
int connect_client(void);
int bind_server(void);
void close_socket(int sock);
#endif /* !CONNECT_H_ */

View file

@ -24,10 +24,13 @@
#include <nanomsg/nn.h>
#include <stdlib.h>
#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);
}

View file

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

View file

@ -24,11 +24,16 @@
#ifndef PROTOCOL_H_
# define PROTOCOL_H_
# include <pb.h>
# include <pb_encode.h>
# include <pb_decode.h>
# 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__ }, \
} \
} \
}