2020-03-24 20:18:16 +01:00
|
|
|
/* radare - LGPL - Copyright 2020 - thestr4ng3r */
|
|
|
|
|
|
|
|
#include "r2r.h"
|
|
|
|
|
|
|
|
#include <r_cons.h>
|
|
|
|
|
|
|
|
typedef struct r2r_state_t {
|
|
|
|
R2RRunConfig run_config;
|
|
|
|
bool verbose;
|
|
|
|
R2RTestDatabase *db;
|
|
|
|
|
|
|
|
RThreadCond *cond; // signaled from workers to main thread to update status
|
|
|
|
RThreadLock *lock; // protects everything below
|
|
|
|
ut64 ok_count;
|
|
|
|
ut64 xx_count;
|
|
|
|
ut64 br_count;
|
|
|
|
ut64 fx_count;
|
|
|
|
RPVector queue;
|
|
|
|
RPVector results;
|
|
|
|
} R2RState;
|
|
|
|
|
|
|
|
static RThreadFunctionRet worker_th(RThread *th);
|
|
|
|
static void print_state(R2RState *state, ut64 prev_completed);
|
|
|
|
|
|
|
|
static int help(bool verbose) {
|
2020-03-29 18:07:44 +02:00
|
|
|
printf ("Usage: r2r [-vh] [-j threads] [test path]\n");
|
2020-03-24 20:18:16 +01:00
|
|
|
if (verbose) {
|
2020-03-29 18:07:44 +02:00
|
|
|
printf (
|
|
|
|
" -h print this help\n"
|
|
|
|
" -v verbose\n"
|
|
|
|
" -j [threads] how many threads to use for running tests concurrently (default is 4)\n");
|
2020-03-24 20:18:16 +01:00
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char **argv) {
|
2020-03-29 18:07:44 +02:00
|
|
|
int workers_count = 4;
|
2020-03-24 20:18:16 +01:00
|
|
|
bool verbose = false;
|
2020-03-29 18:07:44 +02:00
|
|
|
|
2020-03-27 17:06:40 +01:00
|
|
|
RGetopt opt;
|
2020-03-29 18:07:44 +02:00
|
|
|
r_getopt_init (&opt, argc, (const char **)argv, "hvj:");
|
|
|
|
int c;
|
2020-03-27 17:06:40 +01:00
|
|
|
while ((c = r_getopt_next (&opt)) != -1) {
|
2020-03-24 20:18:16 +01:00
|
|
|
switch (c) {
|
|
|
|
case 'h':
|
|
|
|
return help (true);
|
|
|
|
case 'v':
|
|
|
|
verbose = true;
|
|
|
|
break;
|
2020-03-29 18:07:44 +02:00
|
|
|
case 'j': {
|
|
|
|
workers_count = atoi (opt.arg);
|
|
|
|
if (workers_count <= 0) {
|
|
|
|
eprintf ("Invalid thread count\n");
|
|
|
|
return help (false);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2020-03-24 20:18:16 +01:00
|
|
|
default:
|
|
|
|
return help (false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!r2r_subprocess_init ()) {
|
|
|
|
eprintf ("Subprocess init failed\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
atexit (r2r_subprocess_fini);
|
|
|
|
|
2020-03-27 06:15:47 +01:00
|
|
|
R2RState state = {{0}};
|
2020-03-24 20:18:16 +01:00
|
|
|
state.run_config.r2_cmd = "radare2";
|
|
|
|
state.run_config.rasm2_cmd = "rasm2";
|
|
|
|
state.verbose = verbose;
|
|
|
|
state.db = r2r_test_database_new ();
|
|
|
|
if (!state.db) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
r_pvector_init (&state.queue, NULL);
|
|
|
|
r_pvector_init (&state.results, (RPVectorFree)r2r_test_result_info_free);
|
|
|
|
state.lock = r_th_lock_new (false);
|
|
|
|
if (!state.lock) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
state.cond = r_th_cond_new ();
|
|
|
|
if (!state.cond) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2020-03-27 17:06:40 +01:00
|
|
|
if (opt.ind < argc) {
|
2020-03-24 20:18:16 +01:00
|
|
|
// Manually specified path(s)
|
|
|
|
int i;
|
2020-03-27 17:06:40 +01:00
|
|
|
for (i = opt.ind; i < argc; i++) {
|
2020-03-24 20:18:16 +01:00
|
|
|
if (!r2r_test_database_load (state.db, argv[i])) {
|
|
|
|
eprintf ("Failed to load tests from \"%s\"\n", argv[i]);
|
|
|
|
r2r_test_database_free (state.db);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Default db path
|
|
|
|
if (!r2r_test_database_load (state.db, "db")) {
|
|
|
|
eprintf ("Failed to load tests from ./db\n");
|
|
|
|
r2r_test_database_free (state.db);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
r_pvector_insert_range (&state.queue, 0, state.db->tests.v.a, r_pvector_len (&state.db->tests));
|
|
|
|
|
|
|
|
r_th_lock_enter (state.lock);
|
|
|
|
|
|
|
|
RPVector workers;
|
|
|
|
r_pvector_init (&workers, NULL);
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < workers_count; i++) {
|
|
|
|
RThread *th = r_th_new (worker_th, &state, 0);
|
|
|
|
if (!th) {
|
|
|
|
eprintf ("Failed to start thread.\n");
|
|
|
|
exit (-1);
|
|
|
|
}
|
|
|
|
r_pvector_push (&workers, th);
|
|
|
|
}
|
|
|
|
|
|
|
|
ut64 prev_completed = UT64_MAX;
|
|
|
|
while (true) {
|
|
|
|
ut64 completed = (ut64)r_pvector_len (&state.results);
|
|
|
|
if (completed != prev_completed) {
|
|
|
|
print_state (&state, prev_completed);
|
|
|
|
prev_completed = completed;
|
|
|
|
if (completed == r_pvector_len (&state.db->tests)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
r_th_cond_wait (state.cond, state.lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
r_th_lock_leave (state.lock);
|
|
|
|
|
|
|
|
printf ("\n");
|
|
|
|
|
|
|
|
void **it;
|
|
|
|
r_pvector_foreach (&workers, it) {
|
|
|
|
RThread *th = *it;
|
|
|
|
r_th_wait (th);
|
|
|
|
r_th_free (th);
|
|
|
|
}
|
|
|
|
r_pvector_clear (&workers);
|
|
|
|
|
|
|
|
int ret = 0;
|
|
|
|
if (state.xx_count) {
|
|
|
|
ret = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
r_pvector_clear (&state.queue);
|
|
|
|
r_pvector_clear (&state.results);
|
|
|
|
r2r_test_database_free (state.db);
|
|
|
|
r_th_lock_free (state.lock);
|
|
|
|
r_th_cond_free (state.cond);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static RThreadFunctionRet worker_th(RThread *th) {
|
|
|
|
R2RState *state = th->user;
|
|
|
|
r_th_lock_enter (state->lock);
|
|
|
|
while (true) {
|
|
|
|
if (r_pvector_empty (&state->queue)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
R2RTest *test = r_pvector_pop (&state->queue);
|
|
|
|
r_th_lock_leave (state->lock);
|
|
|
|
|
|
|
|
R2RTestResultInfo *result = r2r_run_test (&state->run_config, test);
|
|
|
|
|
|
|
|
r_th_lock_enter (state->lock);
|
|
|
|
r_pvector_push (&state->results, result);
|
|
|
|
switch (result->result) {
|
|
|
|
case R2R_TEST_RESULT_OK:
|
|
|
|
state->ok_count++;
|
|
|
|
break;
|
|
|
|
case R2R_TEST_RESULT_FAILED:
|
|
|
|
state->xx_count++;
|
|
|
|
break;
|
|
|
|
case R2R_TEST_RESULT_BROKEN:
|
|
|
|
state->br_count++;
|
|
|
|
break;
|
|
|
|
case R2R_TEST_RESULT_FIXED:
|
|
|
|
state->fx_count++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
r_th_cond_signal (state->cond);
|
|
|
|
}
|
|
|
|
r_th_lock_leave (state->lock);
|
|
|
|
return R_TH_STOP;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print_diff(const char *actual, const char *expected) {
|
2020-03-27 06:13:41 +01:00
|
|
|
#define DO_DIFF !__WINDOWS__
|
|
|
|
#if DO_DIFF
|
|
|
|
RDiff *d = r_diff_new ();
|
|
|
|
char *uni = r_diff_buffers_to_string (d, (const ut8 *)expected, (int)strlen (expected), (const ut8 *)actual, (int)strlen (actual));
|
|
|
|
r_diff_free (d);
|
|
|
|
|
|
|
|
RList *lines = r_str_split_duplist (uni, "\n");
|
|
|
|
RListIter *it;
|
|
|
|
char *line;
|
|
|
|
r_list_foreach (lines, it, line) {
|
|
|
|
char c = *line;
|
|
|
|
switch (c) {
|
|
|
|
case '+':
|
|
|
|
printf ("%s", Color_GREEN);
|
|
|
|
break;
|
|
|
|
case '-':
|
|
|
|
printf ("%s", Color_RED);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
printf ("%s\n", line);
|
|
|
|
if (c == '+' || c == '-') {
|
|
|
|
printf ("%s", Color_RESET);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
r_list_free (lines);
|
|
|
|
free (uni);
|
|
|
|
printf ("\n");
|
|
|
|
#else
|
2020-03-24 20:18:16 +01:00
|
|
|
RList *lines = r_str_split_duplist (expected, "\n");
|
|
|
|
RListIter *it;
|
|
|
|
char *line;
|
|
|
|
r_list_foreach (lines, it, line) {
|
2020-03-27 06:13:41 +01:00
|
|
|
printf (Color_RED"- %s"Color_RESET"\n", line);
|
2020-03-24 20:18:16 +01:00
|
|
|
}
|
|
|
|
r_list_free (lines);
|
|
|
|
lines = r_str_split_duplist (actual, "\n");
|
|
|
|
r_list_foreach (lines, it, line) {
|
2020-03-27 06:13:41 +01:00
|
|
|
printf (Color_GREEN"+ %s"Color_RESET"\n", line);
|
2020-03-24 20:18:16 +01:00
|
|
|
}
|
|
|
|
r_list_free (lines);
|
2020-03-27 06:13:41 +01:00
|
|
|
#endif
|
2020-03-24 20:18:16 +01:00
|
|
|
}
|
|
|
|
|
2020-03-29 11:32:21 +02:00
|
|
|
static R2RProcessOutput *print_runner(const char *file, const char *args[], size_t args_size,
|
|
|
|
const char *envvars[], const char *envvals[], size_t env_size) {
|
|
|
|
size_t i;
|
|
|
|
for (i = 0; i < env_size; i++) {
|
|
|
|
printf ("%s=%s ", envvars[i], envvals[i]);
|
|
|
|
}
|
|
|
|
printf ("%s", file);
|
|
|
|
for (i = 0; i < args_size; i++) {
|
|
|
|
const char *str = args[i];
|
|
|
|
if (strpbrk (str, "\n \'\"")) {
|
|
|
|
printf (" '%s'", str); // TODO: escape
|
|
|
|
} else {
|
|
|
|
printf (" %s", str);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
printf ("\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print_result_diff(R2RRunConfig *config, R2RTestResultInfo *result) {
|
2020-03-24 20:18:16 +01:00
|
|
|
switch (result->test->type) {
|
|
|
|
case R2R_TEST_TYPE_CMD: {
|
2020-03-29 11:32:21 +02:00
|
|
|
r2r_run_cmd_test (config, result->test->cmd_test, print_runner);
|
2020-03-24 20:18:16 +01:00
|
|
|
const char *expect = result->test->cmd_test->expect.value;
|
2020-03-29 11:32:21 +02:00
|
|
|
if (expect && strcmp (result->proc_out->out, expect)) {
|
2020-03-24 20:18:16 +01:00
|
|
|
printf ("-- stdout\n");
|
|
|
|
print_diff (result->proc_out->out, expect);
|
|
|
|
}
|
|
|
|
expect = result->test->cmd_test->expect_err.value;
|
2020-03-29 18:07:44 +02:00
|
|
|
const char *err = result->proc_out->err;
|
|
|
|
if (expect && strcmp (err, expect)) {
|
2020-03-24 20:18:16 +01:00
|
|
|
printf ("-- stderr\n");
|
2020-03-29 18:07:44 +02:00
|
|
|
print_diff (err, expect);
|
|
|
|
} else if (*err) {
|
|
|
|
printf ("-- stderr\n%s\n", err);
|
2020-03-24 20:18:16 +01:00
|
|
|
}
|
2020-03-29 11:32:21 +02:00
|
|
|
if (result->proc_out->ret != 0) {
|
|
|
|
printf ("-- exit status: "Color_RED"%d"Color_RESET"\n", result->proc_out->ret);
|
|
|
|
}
|
2020-03-24 20:18:16 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case R2R_TEST_TYPE_ASM:
|
|
|
|
// TODO
|
|
|
|
break;
|
|
|
|
case R2R_TEST_TYPE_JSON:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void print_state(R2RState *state, ut64 prev_completed) {
|
|
|
|
printf (R_CONS_CLEAR_LINE);
|
|
|
|
|
|
|
|
// Detailed test result (with diff if necessary)
|
|
|
|
ut64 completed = (ut64)r_pvector_len (&state->results);
|
|
|
|
ut64 i;
|
|
|
|
for (i = prev_completed; i < completed; i++) {
|
|
|
|
R2RTestResultInfo *result = r_pvector_at (&state->results, (size_t)i);
|
2020-03-27 06:13:41 +01:00
|
|
|
if (!state->verbose && (result->result == R2R_TEST_RESULT_OK || result->result == R2R_TEST_RESULT_FIXED || result->result == R2R_TEST_RESULT_BROKEN)) {
|
2020-03-24 20:18:16 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
char *name = r2r_test_name (result->test);
|
|
|
|
if (!name) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
switch (result->result) {
|
|
|
|
case R2R_TEST_RESULT_OK:
|
|
|
|
printf (Color_GREEN"[OK]"Color_RESET);
|
|
|
|
break;
|
|
|
|
case R2R_TEST_RESULT_FAILED:
|
|
|
|
printf (Color_RED"[XX]"Color_RESET);
|
|
|
|
break;
|
|
|
|
case R2R_TEST_RESULT_BROKEN:
|
|
|
|
printf (Color_BLUE"[BR]"Color_RESET);
|
|
|
|
break;
|
|
|
|
case R2R_TEST_RESULT_FIXED:
|
|
|
|
printf (Color_CYAN"[FX]"Color_RESET);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
printf (" %s "Color_YELLOW"%s"Color_RESET"\n", result->test->path, name);
|
|
|
|
if (result->result == R2R_TEST_RESULT_FAILED || (state->verbose && result->result == R2R_TEST_RESULT_BROKEN)) {
|
2020-03-29 11:32:21 +02:00
|
|
|
print_result_diff (&state->run_config, result);
|
2020-03-24 20:18:16 +01:00
|
|
|
}
|
|
|
|
free (name);
|
|
|
|
}
|
|
|
|
|
|
|
|
// [x/x] OK 42 BR 0 ...
|
|
|
|
int w = printf ("[%"PFMT64u"/%"PFMT64u"]", completed, (ut64)r_pvector_len (&state->db->tests));
|
|
|
|
while (w >= 0 && w < 20) {
|
|
|
|
printf (" ");
|
|
|
|
w++;
|
|
|
|
}
|
|
|
|
printf (" ");
|
|
|
|
printf ("OK %8"PFMT64u" BR %8"PFMT64u" XX %8"PFMT64u" FX %8"PFMT64u,
|
|
|
|
state->ok_count, state->br_count, state->xx_count, state->fx_count);
|
|
|
|
fflush (stdout);
|
|
|
|
}
|