Skip to content

Commit

Permalink
Parallel test runner for userspace tests (#1409)
Browse files Browse the repository at this point in the history
  • Loading branch information
cahirwpz authored Jul 31, 2023
1 parent ecde9bb commit 87571b8
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 25 deletions.
143 changes: 119 additions & 24 deletions bin/utest/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,48 @@

SET_DECLARE(tests, test_entry_t);

static int utest_repeat = 1;
static int utest_seed = 0;

typedef struct job {
test_entry_t *te;
pid_t pid;
int status;
} job_t;

static job_t *jobs = NULL;
static int njobmax = 1;
static sigset_t sigchld_mask;

static void sigchld_handler(__unused int sig) {
int old_errno = errno;

pid_t pid;
int status;

while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
int found = 0;

for (int j = 0; j < njobmax; j++) {
job_t *job = &jobs[j];

if (job->pid == pid) {
job->status = status;
found = 1;
break;
}
}

if (!found) {
fprintf(stderr, "utest[%d]: reaped somebody's else child (pid=%d)!\n",
getpid(), pid);
exit(EXIT_FAILURE);
}
}

errno = old_errno;
}

static test_entry_t *find_test(const char *name) {
test_entry_t **ptr;
SET_FOREACH (ptr, tests) {
Expand All @@ -26,7 +68,43 @@ static timeval_t timestamp(void) {
return (timeval_t){.tv_sec = ts.tv_sec, .tv_usec = ts.tv_nsec / 1000};
}

static void run_test(test_entry_t *te) {
static int running(void) {
int pending = 0;

for (int i = 0; i < njobmax; i++) {
job_t *job = &jobs[i];
if (job->te == NULL)
continue;
if (job->status < 0) {
pending++;
continue;
}
if (WIFEXITED(job->status)) {
int code = WEXITSTATUS(job->status);
if (code) {
fprintf(stderr, "Test '%s' exited with %d code!\n", job->te->name,
code);
exit(EXIT_FAILURE);
}
} else if (WIFSIGNALED(job->status)) {
fprintf(stderr, "Test '%s' was terminated by %s!\n", job->te->name,
sys_signame[WTERMSIG(job->status)]);
} else {
fprintf(stderr, "Test '%s' exited with invalid status %d!\n",
job->te->name, job->status);
exit(EXIT_FAILURE);
}
job->te = NULL;
job->status = 0;
job->pid = 0;
}

return pending;
}

const char *testname = NULL;

static void run_test(sigset_t *mask, test_entry_t *te) {
timeval_t tv = timestamp();
const char *name = te->name;

Expand All @@ -35,31 +113,30 @@ static void run_test(test_entry_t *te) {

pid_t pid = xfork();
if (pid == 0) {
xsignal(SIGCHLD, SIG_DFL);
setsid();
setpgid(0, 0);
xsigprocmask(SIG_SETMASK, mask, NULL);

testname = te->name;

if (te->flags & TF_DEBUG)
__verbose = 1;

exit(te->func());
}

int status;
waitpid(pid, &status, 0);

if (WIFEXITED(status)) {
int code = WEXITSTATUS(status);
if (code) {
fprintf(stderr, "Test '%s' exited with %d code!\n", name, code);
exit(EXIT_FAILURE);
for (int i = 0; i < njobmax; i++) {
job_t *job = &jobs[i];
if (job->te == NULL) {
job->te = te;
job->pid = pid;
job->status = -1;
return;
}
} else if (WIFSIGNALED(status)) {
fprintf(stderr, "Test '%s' was terminated by %s!\n", name,
sys_signame[WTERMSIG(status)]);
} else {
fprintf(stderr, "Test '%s' exited with invalid status %d!\n", name, status);
exit(EXIT_FAILURE);
}

abort();
}

static inline int test_disabled(test_entry_t *te) {
Expand All @@ -72,10 +149,6 @@ static int test_compare(const void *a_, const void *b_) {
return strcmp(a->name, b->name);
}

static int utest_repeat = 1;
static int utest_seed = 0;
static unsigned seed = 0;

static void select_tests(char *test_str) {
if (!strcmp(test_str, "all"))
return;
Expand Down Expand Up @@ -145,8 +218,12 @@ int main(int argc, char **argv) {
if (repeat_str)
utest_repeat = strtoul(repeat_str, NULL, 10);

fprintf(stderr, "utest: seed=%u repeat=%u test=%s\n", utest_seed,
utest_repeat, argv[1]);
const char *parallel_str = getenv("parallel");
if (parallel_str)
njobmax = strtoul(parallel_str, NULL, 10);

fprintf(stderr, "utest[%d]: seed=%u repeat=%u parallel=%d test=%s\n",
getpid(), utest_seed, utest_repeat, njobmax, argv[1]);

select_tests(argv[1]);

Expand All @@ -155,7 +232,7 @@ int main(int argc, char **argv) {

if (utest_seed != 0) {
/* Initialize LCG with seed.*/
seed = utest_seed;
unsigned seed = utest_seed;
/* Yates-Fisher shuffle. */
for (int i = 0; i <= ntests - 2; i++) {
int j = i + rand_r(&seed) % (ntests - i);
Expand All @@ -165,8 +242,26 @@ int main(int argc, char **argv) {
}
}

for (int i = 0; i < ntests; i++)
run_test(tests[i]);
jobs = calloc(sizeof(job_t), njobmax);

sigset_t mask;

xsignal(SIGCHLD, sigchld_handler);
sigemptyset(&sigchld_mask);
sigaddset(&sigchld_mask, SIGCHLD);
xsigprocmask(SIG_BLOCK, &sigchld_mask, &mask);

for (int i = 0; i < ntests; i++) {
run_test(&mask, tests[i]);

while (running() == njobmax)
sigsuspend(&mask);
}

while (running() > 0)
sigsuspend(&mask);

xsigprocmask(SIG_SETMASK, &mask, NULL);

return 0;
}
3 changes: 2 additions & 1 deletion bin/utest/utest.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ static void print_msg(const char *file, int line, const char *fmt, va_list ap) {

char *basename = strrchr(file, '/');
file = basename ? basename + 1 : file;
res = snprintf_ss(buf, sizeof(buf), "[%s:%d] ", file, line);
res =
snprintf_ss(buf, sizeof(buf), "[%s|%s:%d] ", testname ?: "?", file, line);
if (res < 0)
exit(EXIT_FAILURE);
len += res;
Expand Down
1 change: 1 addition & 0 deletions bin/utest/utest.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ typedef struct test_entry {
*/

extern int __verbose;
extern const char *testname;

noreturn void __die(const char *file, int line, const char *fmt, ...);
void __msg(const char *file, int line, const char *fmt, ...);
Expand Down

0 comments on commit 87571b8

Please sign in to comment.