From 87571b898f097fe0d26406fd8d2a49252fdeaedf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krystian=20Bac=C5=82awski?= Date: Mon, 31 Jul 2023 13:03:46 +0200 Subject: [PATCH] Parallel test runner for userspace tests (#1409) --- bin/utest/main.c | 143 ++++++++++++++++++++++++++++++++++++++-------- bin/utest/utest.c | 3 +- bin/utest/utest.h | 1 + 3 files changed, 122 insertions(+), 25 deletions(-) diff --git a/bin/utest/main.c b/bin/utest/main.c index a6a12b730..4f5ead8b7 100644 --- a/bin/utest/main.c +++ b/bin/utest/main.c @@ -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) { @@ -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; @@ -35,8 +113,12 @@ 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; @@ -44,22 +126,17 @@ static void run_test(test_entry_t *te) { 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) { @@ -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; @@ -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]); @@ -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); @@ -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; } diff --git a/bin/utest/utest.c b/bin/utest/utest.c index cd16f7a09..f842e02ae 100644 --- a/bin/utest/utest.c +++ b/bin/utest/utest.c @@ -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; diff --git a/bin/utest/utest.h b/bin/utest/utest.h index c11e13970..b8a3e3aa9 100644 --- a/bin/utest/utest.h +++ b/bin/utest/utest.h @@ -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, ...);