407 lines
12 KiB
C
407 lines
12 KiB
C
/*
|
|
* 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.
|
|
*/
|
|
#define CRITERION_LOGGING_COLORS
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <csptr/smalloc.h>
|
|
#include <valgrind/valgrind.h>
|
|
#include <nanomsg/nn.h>
|
|
#include "criterion/internal/test.h"
|
|
#include "criterion/options.h"
|
|
#include "criterion/internal/ordered-set.h"
|
|
#include "criterion/logging.h"
|
|
#include "criterion/internal/preprocess.h"
|
|
#include "protocol/protocol.h"
|
|
#include "protocol/connect.h"
|
|
#include "compat/time.h"
|
|
#include "compat/posix.h"
|
|
#include "compat/processor.h"
|
|
#include "string/i18n.h"
|
|
#include "io/event.h"
|
|
#include "io/output.h"
|
|
#include "runner_coroutine.h"
|
|
#include "stats.h"
|
|
#include "runner.h"
|
|
#include "report.h"
|
|
#include "worker.h"
|
|
#include "abort.h"
|
|
#include "config.h"
|
|
#include "common.h"
|
|
#include "client.h"
|
|
|
|
#ifdef HAVE_PCRE
|
|
#include "string/extmatch.h"
|
|
#endif
|
|
|
|
typedef const char *const msg_t;
|
|
|
|
#ifdef ENABLE_NLS
|
|
static msg_t msg_valgrind_early_exit = N_("%1$sWarning! Criterion has detected "
|
|
"that it is running under valgrind, but the no_early_exit option is "
|
|
"explicitely disabled. Reports will not be accurate!%2$s\n");
|
|
|
|
static msg_t msg_valgrind_jobs = N_("%1$sWarning! Criterion has detected "
|
|
"that it is running under valgrind, but the number of jobs have been "
|
|
"explicitely set. Reports might appear confusing!%2$s\n");
|
|
#else
|
|
static msg_t msg_valgrind_early_exit = "%sWarning! Criterion has detected "
|
|
"that it is running under valgrind, but the no_early_exit option is "
|
|
"explicitely disabled. Reports will not be accurate!%s\n";
|
|
|
|
static msg_t msg_valgrind_jobs = "%sWarning! Criterion has detected "
|
|
"that it is running under valgrind, but the number of jobs have been "
|
|
"explicitely set. Reports might appear confusing!%s\n";
|
|
#endif
|
|
|
|
|
|
#ifdef _MSC_VER
|
|
struct criterion_test *CR_SECTION_START_(cr_tst);
|
|
struct criterion_suite *CR_SECTION_START_(cr_sts);
|
|
struct criterion_test *CR_SECTION_END_(cr_tst);
|
|
struct criterion_suite *CR_SECTION_END_(cr_sts);
|
|
#endif
|
|
|
|
CR_IMPL_SECTION_LIMITS(struct criterion_test*, cr_tst);
|
|
CR_IMPL_SECTION_LIMITS(struct criterion_suite*, cr_sts);
|
|
|
|
// This is here to make the test suite & test sections non-empty
|
|
CR_SECTION_("cr_sts") struct criterion_suite *dummy_suite = NULL;
|
|
CR_SECTION_("cr_tst") struct criterion_test *dummy_test = NULL;
|
|
|
|
static INLINE void nothing(void) {}
|
|
|
|
int cmp_suite(void *a, void *b) {
|
|
struct criterion_suite *s1 = a, *s2 = b;
|
|
return strcmp(s1->name, s2->name);
|
|
}
|
|
|
|
int cmp_test(void *a, void *b) {
|
|
struct criterion_test *s1 = a, *s2 = b;
|
|
return strcmp(s1->name, s2->name);
|
|
}
|
|
|
|
static void dtor_suite_set(void *ptr, CR_UNUSED void *meta) {
|
|
struct criterion_suite_set *s = ptr;
|
|
sfree(s->tests);
|
|
}
|
|
|
|
static void dtor_test_set(void *ptr, CR_UNUSED void *meta) {
|
|
struct criterion_test_set *t = ptr;
|
|
sfree(t->suites);
|
|
}
|
|
|
|
void criterion_register_test(struct criterion_test_set *set,
|
|
struct criterion_test *test) {
|
|
|
|
struct criterion_suite_set css = {
|
|
.suite = { .name = test->category },
|
|
};
|
|
struct criterion_suite_set *s = insert_ordered_set(set->suites, &css, sizeof (css));
|
|
if (!s->tests)
|
|
s->tests = new_ordered_set(cmp_test, NULL);
|
|
|
|
insert_ordered_set(s->tests, test, sizeof(*test));
|
|
++set->tests;
|
|
}
|
|
|
|
struct criterion_test_set *criterion_init(void) {
|
|
struct criterion_ordered_set *suites = new_ordered_set(cmp_suite, dtor_suite_set);
|
|
|
|
FOREACH_SUITE_SEC(s) {
|
|
if (!*s || !*(*s)->name)
|
|
continue;
|
|
|
|
struct criterion_suite_set css = {
|
|
.suite = **s,
|
|
};
|
|
insert_ordered_set(suites, &css, sizeof (css));
|
|
}
|
|
|
|
struct criterion_test_set *set = smalloc(
|
|
.size = sizeof (struct criterion_test_set),
|
|
.dtor = dtor_test_set
|
|
);
|
|
|
|
*set = (struct criterion_test_set) {
|
|
suites,
|
|
0,
|
|
};
|
|
|
|
FOREACH_TEST_SEC(test) {
|
|
if (!*test)
|
|
continue;
|
|
|
|
if (!*(*test)->category || !*(*test)->name)
|
|
continue;
|
|
|
|
criterion_register_test(set, *test);
|
|
}
|
|
|
|
return set;
|
|
}
|
|
|
|
const struct criterion_test *criterion_current_test;
|
|
const struct criterion_suite *criterion_current_suite;
|
|
|
|
void run_test_child(struct criterion_test *test,
|
|
struct criterion_suite *suite) {
|
|
|
|
#ifndef ENABLE_VALGRIND_ERRORS
|
|
VALGRIND_ENABLE_ERROR_REPORTING;
|
|
#endif
|
|
|
|
criterion_current_test = test;
|
|
criterion_current_suite = suite;
|
|
|
|
if (suite->data && suite->data->timeout != 0 && test->data->timeout == 0)
|
|
setup_timeout((uint64_t) (suite->data->timeout * 1e9));
|
|
else if (test->data->timeout != 0)
|
|
setup_timeout((uint64_t) (test->data->timeout * 1e9));
|
|
|
|
if (test->test)
|
|
test->test();
|
|
}
|
|
|
|
#define push_event(...) \
|
|
do { \
|
|
stat_push_event(ctx->stats, \
|
|
ctx->suite_stats, \
|
|
ctx->test_stats, \
|
|
&(struct event) { \
|
|
.kind = CR_VA_HEAD(__VA_ARGS__), \
|
|
CR_VA_TAIL(__VA_ARGS__) \
|
|
}); \
|
|
report(CR_VA_HEAD(__VA_ARGS__), ctx->test_stats); \
|
|
} while (0)
|
|
|
|
s_pipe_handle *g_worker_pipe;
|
|
|
|
#ifdef HAVE_PCRE
|
|
void disable_unmatching(struct criterion_test_set *set) {
|
|
FOREACH_SET(struct criterion_suite_set *s, set->suites) {
|
|
if ((s->suite.data && s->suite.data->disabled) || !s->tests)
|
|
continue;
|
|
|
|
FOREACH_SET(struct criterion_test *test, s->tests) {
|
|
const char *errmsg;
|
|
int ret = extmatch(criterion_options.pattern, test->data->identifier_, &errmsg);
|
|
if (ret == -10) {
|
|
printf("pattern error: %s\n", errmsg);
|
|
exit(1);
|
|
} else if (ret < 0) {
|
|
test->data->disabled = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
struct criterion_test_set *criterion_initialize(void) {
|
|
init_i18n();
|
|
|
|
#ifndef ENABLE_VALGRIND_ERRORS
|
|
VALGRIND_DISABLE_ERROR_REPORTING;
|
|
#endif
|
|
if (RUNNING_ON_VALGRIND) {
|
|
criterion_options.no_early_exit = 1;
|
|
criterion_options.jobs = 1;
|
|
}
|
|
|
|
if (resume_child()) // (windows only) resume from the fork
|
|
exit(0);
|
|
|
|
criterion_register_output_provider("tap", tap_report);
|
|
criterion_register_output_provider("xml", xml_report);
|
|
criterion_register_output_provider("json", json_report);
|
|
|
|
return criterion_init();
|
|
}
|
|
|
|
void criterion_finalize(struct criterion_test_set *set) {
|
|
sfree(set);
|
|
|
|
#ifndef ENABLE_VALGRIND_ERRORS
|
|
VALGRIND_ENABLE_ERROR_REPORTING;
|
|
#endif
|
|
|
|
criterion_free_output();
|
|
}
|
|
|
|
static struct client_ctx *spawn_next_client(struct server_ctx *sctx, ccrContext *ctx) {
|
|
struct worker *w = ctx ? run_next_test(NULL, NULL, ctx) : NULL;
|
|
|
|
if (!is_runner() || w == NULL)
|
|
return NULL;
|
|
|
|
struct client_ctx new_ctx = (struct client_ctx) {
|
|
.test = w->ctx.test,
|
|
.tstats = w->ctx.test_stats,
|
|
.suite = w->ctx.suite,
|
|
.sstats = w->ctx.suite_stats,
|
|
.gstats = w->ctx.stats,
|
|
};
|
|
|
|
return add_client_from_worker(sctx, &new_ctx, w);
|
|
}
|
|
|
|
static void run_tests_async(struct criterion_test_set *set,
|
|
struct criterion_global_stats *stats,
|
|
int socket) {
|
|
|
|
ccrContext ctx = 0;
|
|
|
|
size_t nb_workers = DEF(criterion_options.jobs, get_processor_count());
|
|
size_t active_workers = 0;
|
|
|
|
struct server_ctx sctx;
|
|
init_server_context(&sctx);
|
|
|
|
// initialization of coroutine
|
|
run_next_test(set, stats, &ctx);
|
|
|
|
for (size_t i = 0; i < nb_workers; ++i) {
|
|
struct client_ctx *cctx = spawn_next_client(&sctx, &ctx);
|
|
if (!is_runner())
|
|
goto cleanup;
|
|
|
|
if (!cctx)
|
|
break;
|
|
++active_workers;
|
|
}
|
|
|
|
if (!active_workers)
|
|
goto cleanup;
|
|
|
|
criterion_protocol_msg msg = criterion_protocol_msg_init_zero;
|
|
unsigned char *buf = NULL;
|
|
int length;
|
|
while ((length = nn_recv(socket, &buf, NN_MSG, 0)) > 0) {
|
|
pb_istream_t in = pb_istream_from_buffer(buf, length);
|
|
if (pb_decode(&in, criterion_protocol_msg_fields, &msg)) {
|
|
struct client_ctx *cctx = process_client_message(&sctx, &msg);
|
|
if (cctx->state == CS_DEATH && 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())
|
|
goto cleanup;
|
|
|
|
if (cctx == NULL)
|
|
--active_workers;
|
|
}
|
|
}
|
|
nn_freemsg(buf);
|
|
if (!active_workers)
|
|
break;
|
|
}
|
|
|
|
destroy_server_context(&sctx);
|
|
|
|
cleanup:
|
|
ccrAbort(ctx);
|
|
}
|
|
|
|
static int criterion_run_all_tests_impl(struct criterion_test_set *set) {
|
|
report(PRE_ALL, set);
|
|
log(pre_all, set);
|
|
|
|
if (RUNNING_ON_VALGRIND) {
|
|
if (!criterion_options.no_early_exit)
|
|
criterion_pimportant(CRITERION_PREFIX_DASHES,
|
|
_(msg_valgrind_early_exit), CR_FG_BOLD, CR_RESET);
|
|
if (criterion_options.jobs != 1)
|
|
criterion_pimportant(CRITERION_PREFIX_DASHES,
|
|
_(msg_valgrind_jobs), CR_FG_BOLD, CR_RESET);
|
|
}
|
|
|
|
fflush(NULL); // flush everything before forking
|
|
|
|
int sock = bind_server();
|
|
if (sock < 0) {
|
|
criterion_perror("Could not initialize the message server: %s.\n",
|
|
strerror(errno));
|
|
abort();
|
|
}
|
|
|
|
init_proc_compat();
|
|
|
|
struct criterion_global_stats *stats = stats_init();
|
|
run_tests_async(set, stats, sock);
|
|
|
|
int result = is_runner() ? stats->tests_failed == 0 : -1;
|
|
|
|
if (!is_runner())
|
|
goto cleanup;
|
|
|
|
report(POST_ALL, stats);
|
|
process_all_output(stats);
|
|
log(post_all, stats);
|
|
|
|
cleanup:
|
|
free_proc_compat();
|
|
nn_close(sock);
|
|
sfree(stats);
|
|
return result;
|
|
}
|
|
|
|
int criterion_run_all_tests(struct criterion_test_set *set) {
|
|
#ifdef HAVE_PCRE
|
|
if (criterion_options.pattern)
|
|
disable_unmatching(set);
|
|
#endif
|
|
|
|
set_runner_process();
|
|
int res = criterion_run_all_tests_impl(set);
|
|
unset_runner_process();
|
|
|
|
if (res == -1) {
|
|
criterion_finalize(set);
|
|
exit(0);
|
|
}
|
|
|
|
return criterion_options.always_succeed || res;
|
|
}
|
|
|
|
void run_single_test_by_name(const char *testname) {
|
|
struct criterion_test_set *set = criterion_init();
|
|
|
|
g_event_pipe = pipe_file_open(NULL);
|
|
|
|
FOREACH_SET(struct criterion_suite_set *s, set->suites) {
|
|
size_t tests = s->tests ? s->tests->size : 0;
|
|
if (!tests)
|
|
continue;
|
|
|
|
FOREACH_SET(struct criterion_test *t, s->tests) {
|
|
char name[1024];
|
|
snprintf(name, sizeof (name), "%s::%s", s->suite.name, t->name);
|
|
if (!strncmp(name, testname, 1024))
|
|
run_test_child(t, &s->suite);
|
|
}
|
|
}
|
|
|
|
sfree(set);
|
|
}
|