diff options
Diffstat (limited to 'tests/main.c')
-rw-r--r-- | tests/main.c | 200 |
1 files changed, 173 insertions, 27 deletions
diff --git a/tests/main.c b/tests/main.c index 429772b..9240e1c 100644 --- a/tests/main.c +++ b/tests/main.c @@ -5,19 +5,45 @@ * Entry point for unit tests. */ -#include "prelude.h" #include "tests.h" + +#include "alloc.h" #include "bfstd.h" #include "color.h" -#include <errno.h> +#include "list.h" + #include <locale.h> #include <stdio.h> #include <stdlib.h> +#include <stdint.h> #include <string.h> +#include <sys/wait.h> #include <time.h> +#include <unistd.h> + +/** Result of the current test. */ +static bool pass; + +bool bfs_check_impl(bool result) { + pass &= result; + return result; +} + +/** + * A running test. + */ +struct test_proc { + /** Linked list links. */ + struct test_proc *prev, *next; + + /** The PID of this test. */ + pid_t pid; + /** The name of this test. */ + const char *name; +}; /** - * Test context. + * Global test context. */ struct test_ctx { /** Number of command line arguments. */ @@ -25,6 +51,17 @@ struct test_ctx { /** The arguments themselves. */ char **argv; + /** Maximum jobs (-j). */ + int jobs; + /** Current jobs. */ + int running; + /** Completed jobs. */ + int done; + /** List of running tests. */ + struct { + struct test_proc *head, *tail; + } procs; + /** Parsed colors. */ struct colors *colors; /** Colorized output stream. */ @@ -35,10 +72,15 @@ struct test_ctx { }; /** Initialize the test context. */ -static int test_init(struct test_ctx *ctx, int argc, char **argv) { +static int test_init(struct test_ctx *ctx, int jobs, int argc, char **argv) { ctx->argc = argc; ctx->argv = argv; + ctx->jobs = jobs; + ctx->running = 0; + ctx->done = 0; + LIST_INIT(&ctx->procs); + ctx->colors = parse_colors(); ctx->cout = cfwrap(stdout, ctx->colors, false); if (!ctx->cout) { @@ -50,26 +92,15 @@ static int test_init(struct test_ctx *ctx, int argc, char **argv) { return 0; } -/** Finalize the test context. */ -static int test_fini(struct test_ctx *ctx) { - if (ctx->cout) { - cfclose(ctx->cout); - } - - free_colors(ctx->colors); - - return ctx->ret; -} - /** Check if a test case is enabled for this run. */ static bool should_run(const struct test_ctx *ctx, const char *test) { // Run all tests by default - if (ctx->argc < 2) { + if (ctx->argc == 0) { return true; } // With args, run only specified tests - for (int i = 1; i < ctx->argc; ++i) { + for (int i = 0; i < ctx->argc; ++i) { if (strcmp(test, ctx->argv[i]) == 0) { return true; } @@ -78,20 +109,104 @@ static bool should_run(const struct test_ctx *ctx, const char *test) { return false; } -/** Run a test if it's enabled. */ -static void run_test(struct test_ctx *ctx, const char *test, test_fn *fn) { - if (should_run(ctx, test)) { - if (fn()) { - cfprintf(ctx->cout, "${grn}[PASS]${rs} ${bld}%s${rs}\n", test); +/** Wait for a test to finish. */ +static void wait_test(struct test_ctx *ctx) { + int wstatus; + pid_t pid = xwaitpid(0, &wstatus, 0); + bfs_everify(pid > 0, "xwaitpid()"); + + struct test_proc *proc = NULL; + for_list (struct test_proc, i, &ctx->procs) { + if (i->pid == pid) { + proc = i; + break; + } + } + + bfs_verify(proc, "No test_proc for PID %ju", (intmax_t)pid); + + bool passed = false; + + if (WIFEXITED(wstatus)) { + int status = WEXITSTATUS(wstatus); + if (status == EXIT_SUCCESS) { + cfprintf(ctx->cout, "${grn}[PASS]${rs} ${bld}%s${rs}\n", proc->name); + passed = true; + } else if (status == EXIT_FAILURE) { + cfprintf(ctx->cout, "${red}[FAIL]${rs} ${bld}%s${rs}\n", proc->name); } else { - cfprintf(ctx->cout, "${red}[FAIL]${rs} ${bld}%s${rs}\n", test); - ctx->ret = EXIT_FAILURE; + cfprintf(ctx->cout, "${red}[FAIL]${rs} ${bld}%s${rs} (Exit %d)\n", proc->name, status); } + } else { + const char *str = NULL; + if (WIFSIGNALED(wstatus)) { + str = strsignal(WTERMSIG(wstatus)); + } + if (!str) { + str = "Unknown"; + } + cfprintf(ctx->cout, "${red}[FAIL]${rs} ${bld}%s${rs} (%s)\n", proc->name, str); + } + + if (!passed) { + ctx->ret = EXIT_FAILURE; } + + --ctx->running; + ++ctx->done; + LIST_REMOVE(&ctx->procs, proc); + free(proc); } -const char *bfs_errstr(void) { - return xstrerror(errno); +/** Unit test function type. */ +typedef void test_fn(void); + +/** Run a test if it's enabled. */ +static void run_test(struct test_ctx *ctx, const char *test, test_fn *fn) { + if (!should_run(ctx, test)) { + return; + } + + while (ctx->running >= ctx->jobs) { + wait_test(ctx); + } + + struct test_proc *proc = ALLOC(struct test_proc); + bfs_everify(proc, "alloc()"); + + LIST_ITEM_INIT(proc); + proc->name = test; + + fflush(NULL); + proc->pid = fork(); + bfs_everify(proc->pid >= 0, "fork()"); + + if (proc->pid > 0) { + // Parent + ++ctx->running; + LIST_APPEND(&ctx->procs, proc); + return; + } + + // Child + pass = true; + fn(); + exit(pass ? EXIT_SUCCESS : EXIT_FAILURE); +} + +/** Finalize the test context. */ +static int test_fini(struct test_ctx *ctx) { + while (ctx->running > 0) { + wait_test(ctx); + } + + if (ctx->cout) { + cfclose(ctx->cout); + } + + free_colors(ctx->colors); + + return ctx->ret; } int main(int argc, char *argv[]) { @@ -107,8 +222,37 @@ int main(int argc, char *argv[]) { } tzset(); + unsigned int jobs = 0; + + const char *cmd = argc > 0 ? argv[0] : "units"; + int c; + while (c = getopt(argc, argv, ":j:"), c != -1) { + switch (c) { + case 'j': + if (xstrtoui(optarg, NULL, 10, &jobs) != 0) { + fprintf(stderr, "%s: Bad job count '%s': %s\n", cmd, optarg, errstr()); + return EXIT_FAILURE; + } + break; + case ':': + fprintf(stderr, "%s: Missing argument to -%c\n", cmd, optopt); + return EXIT_FAILURE; + case '?': + fprintf(stderr, "%s: Unrecognized option -%c\n", cmd, optopt); + return EXIT_FAILURE; + } + } + + if (!jobs) { + jobs = nproc(); + } + + if (optind > argc) { + optind = argc; + } + struct test_ctx ctx; - if (test_init(&ctx, argc, argv) != 0) { + if (test_init(&ctx, jobs, argc - optind, argv + optind) != 0) { goto done; } @@ -116,6 +260,8 @@ int main(int argc, char *argv[]) { run_test(&ctx, "bfstd", check_bfstd); run_test(&ctx, "bit", check_bit); run_test(&ctx, "ioq", check_ioq); + run_test(&ctx, "list", check_list); + run_test(&ctx, "sighook", check_sighook); run_test(&ctx, "trie", check_trie); run_test(&ctx, "xspawn", check_xspawn); run_test(&ctx, "xtime", check_xtime); |