diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:03:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:03:56 +0000 |
commit | 18da3ffcd7f3c8a0c5f790c801b5813503c2273d (patch) | |
tree | 84caf98dc5cef3d123c56ba12e35fd67026e0693 /testsuite/testsuite.c | |
parent | Initial commit. (diff) | |
download | kmod-18da3ffcd7f3c8a0c5f790c801b5813503c2273d.tar.xz kmod-18da3ffcd7f3c8a0c5f790c801b5813503c2273d.zip |
Adding upstream version 31+20240202.upstream/31+20240202
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | testsuite/testsuite.c | 1119 |
1 files changed, 1119 insertions, 0 deletions
diff --git a/testsuite/testsuite.c b/testsuite/testsuite.c new file mode 100644 index 0000000..318343a --- /dev/null +++ b/testsuite/testsuite.c @@ -0,0 +1,1119 @@ +/* + * Copyright (C) 2012-2013 ProFUSION embedded systems + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <limits.h> +#include <regex.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <sys/epoll.h> +#include <sys/prctl.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <shared/util.h> + +#include "testsuite.h" + +static const char *ANSI_HIGHLIGHT_GREEN_ON = "\x1B[1;32m"; +static const char *ANSI_HIGHLIGHT_YELLOW_ON = "\x1B[1;33m"; +static const char *ANSI_HIGHLIGHT_RED_ON = "\x1B[1;31m"; +static const char *ANSI_HIGHLIGHT_OFF = "\x1B[0m"; + +static const char *progname; +static int oneshot = 0; +static const char options_short[] = "lhn"; +static const struct option options[] = { + { "list", no_argument, 0, 'l' }, + { "help", no_argument, 0, 'h' }, + { NULL, 0, 0, 0 } +}; + +#define OVERRIDE_LIBDIR ABS_TOP_BUILDDIR "/testsuite/.libs/" +#define TEST_TIMEOUT_USEC 2 * USEC_PER_SEC + +static const struct { + const char *key; + const char *ldpreload; +} env_config[_TC_LAST] = { + [TC_UNAME_R] = { S_TC_UNAME_R, OVERRIDE_LIBDIR "uname.so" }, + [TC_ROOTFS] = { S_TC_ROOTFS, OVERRIDE_LIBDIR "path.so" }, + [TC_INIT_MODULE_RETCODES] = { S_TC_INIT_MODULE_RETCODES, OVERRIDE_LIBDIR "init_module.so" }, + [TC_DELETE_MODULE_RETCODES] = { S_TC_DELETE_MODULE_RETCODES, OVERRIDE_LIBDIR "delete_module.so" }, +}; + +static void help(void) +{ + const struct option *itr; + const char *itr_short; + + printf("Usage:\n" + "\t%s [options] <test>\n" + "Options:\n", basename(progname)); + + for (itr = options, itr_short = options_short; + itr->name != NULL; itr++, itr_short++) + printf("\t-%c, --%s\n", *itr_short, itr->name); +} + +static void test_list(const struct test *start, const struct test *stop) +{ + const struct test *t; + + printf("Available tests:\n"); + for (t = start; t < stop; t++) + printf("\t%s, %s\n", t->name, t->description); +} + +int test_init(const struct test *start, const struct test *stop, + int argc, char *const argv[]) +{ + progname = argv[0]; + + for (;;) { + int c, idx = 0; + c = getopt_long(argc, argv, options_short, options, &idx); + if (c == -1) + break; + switch (c) { + case 'l': + test_list(start, stop); + return 0; + case 'h': + help(); + return 0; + case 'n': + oneshot = 1; + break; + case '?': + return -1; + default: + ERR("unexpected getopt_long() value %c\n", c); + return -1; + } + } + + if (isatty(STDOUT_FILENO) == 0) { + ANSI_HIGHLIGHT_OFF = ""; + ANSI_HIGHLIGHT_RED_ON = ""; + ANSI_HIGHLIGHT_GREEN_ON = ""; + } + + return optind; +} + +const struct test *test_find(const struct test *start, + const struct test *stop, const char *name) +{ + const struct test *t; + + for (t = start; t < stop; t++) { + if (streq(t->name, name)) + return t; + } + + return NULL; +} + +static int test_spawn_test(const struct test *t) +{ + const char *const args[] = { progname, "-n", t->name, NULL }; + + execv(progname, (char *const *) args); + + ERR("failed to spawn %s for %s: %m\n", progname, t->name); + return EXIT_FAILURE; +} + +static int test_run_spawned(const struct test *t) +{ + int err = t->func(t); + exit(err); + + return EXIT_FAILURE; +} + +int test_spawn_prog(const char *prog, const char *const args[]) +{ + execv(prog, (char *const *) args); + + ERR("failed to spawn %s\n", prog); + ERR("did you forget to build tools?\n"); + return EXIT_FAILURE; +} + +static void test_export_environ(const struct test *t) +{ + char *preload = NULL; + size_t preloadlen = 0; + size_t i; + const struct keyval *env; + + unsetenv("LD_PRELOAD"); + + for (i = 0; i < _TC_LAST; i++) { + const char *ldpreload; + size_t ldpreloadlen; + char *tmp; + + if (t->config[i] == NULL) + continue; + + setenv(env_config[i].key, t->config[i], 1); + + ldpreload = env_config[i].ldpreload; + ldpreloadlen = strlen(ldpreload); + tmp = realloc(preload, preloadlen + 2 + ldpreloadlen); + if (tmp == NULL) { + ERR("oom: test_export_environ()\n"); + return; + } + preload = tmp; + + if (preloadlen > 0) + preload[preloadlen++] = ' '; + memcpy(preload + preloadlen, ldpreload, ldpreloadlen); + preloadlen += ldpreloadlen; + preload[preloadlen] = '\0'; + } + + if (preload != NULL) + setenv("LD_PRELOAD", preload, 1); + + free(preload); + + for (env = t->env_vars; env && env->key; env++) + setenv(env->key, env->val, 1); +} + +static inline int test_run_child(const struct test *t, int fdout[2], + int fderr[2], int fdmonitor[2]) +{ + /* kill child if parent dies */ + prctl(PR_SET_PDEATHSIG, SIGTERM); + + test_export_environ(t); + + /* Close read-fds and redirect std{out,err} to the write-fds */ + if (t->output.out != NULL) { + close(fdout[0]); + if (dup2(fdout[1], STDOUT_FILENO) < 0) { + ERR("could not redirect stdout to pipe: %m\n"); + exit(EXIT_FAILURE); + } + } + + if (t->output.err != NULL) { + close(fderr[0]); + if (dup2(fderr[1], STDERR_FILENO) < 0) { + ERR("could not redirect stderr to pipe: %m\n"); + exit(EXIT_FAILURE); + } + } + + close(fdmonitor[0]); + + if (t->config[TC_ROOTFS] != NULL) { + const char *stamp = TESTSUITE_ROOTFS "../stamp-rootfs"; + const char *rootfs = t->config[TC_ROOTFS]; + struct stat rootfsst, stampst; + + if (stat(stamp, &stampst) != 0) { + ERR("could not stat %s\n - %m", stamp); + exit(EXIT_FAILURE); + } + + if (stat(rootfs, &rootfsst) != 0) { + ERR("could not stat %s\n - %m", rootfs); + exit(EXIT_FAILURE); + } + + if (stat_mstamp(&rootfsst) > stat_mstamp(&stampst)) { + ERR("rootfs %s is dirty, please run 'make rootfs' before runnning this test\n", + rootfs); + exit(EXIT_FAILURE); + } + } + + if (t->need_spawn) + return test_spawn_test(t); + else + return test_run_spawned(t); +} + +#define BUFSZ 4096 + +enum fd_cmp_type { + FD_CMP_MONITOR, + FD_CMP_OUT, + FD_CMP_ERR, + FD_CMP_MAX = FD_CMP_ERR, +}; + +struct fd_cmp { + enum fd_cmp_type type; + int fd; + int fd_match; + bool activity; + const char *path; + const char *name; + char buf[BUFSZ]; + char buf_match[BUFSZ]; + unsigned int head; + unsigned int head_match; +}; + +static int fd_cmp_check_activity(struct fd_cmp *fd_cmp) +{ + struct stat st; + + /* not monitoring or monitoring and it has activity */ + if (fd_cmp == NULL || fd_cmp->fd < 0 || fd_cmp->activity) + return 0; + + /* monitoring, there was no activity and size matches */ + if (stat(fd_cmp->path, &st) == 0 && st.st_size == 0) + return 0; + + ERR("Expecting output on %s, but test didn't produce any\n", + fd_cmp->name); + + return -1; +} + +static bool fd_cmp_is_active(struct fd_cmp *fd_cmp) +{ + return fd_cmp->fd != -1; +} + +static int fd_cmp_open_monitor(struct fd_cmp *fd_cmp, int fd, int fd_ep) +{ + struct epoll_event ep = {}; + + ep.events = EPOLLHUP; + ep.data.ptr = fd_cmp; + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd, &ep) < 0) { + ERR("could not add monitor fd to epoll: %m\n"); + return -errno; + } + + return 0; +} + +static int fd_cmp_open_std(struct fd_cmp *fd_cmp, + const char *fn, int fd, int fd_ep) +{ + struct epoll_event ep = {}; + int fd_match; + + fd_match = open(fn, O_RDONLY); + if (fd_match < 0) { + ERR("could not open %s for read: %m\n", fn); + return -errno; + } + ep.events = EPOLLIN; + ep.data.ptr = fd_cmp; + if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd, &ep) < 0) { + ERR("could not add fd to epoll: %m\n"); + close(fd_match); + return -errno; + } + + return fd_match; +} + +/* opens output file AND adds descriptor to epoll */ +static int fd_cmp_open(struct fd_cmp **fd_cmp_out, + enum fd_cmp_type type, const char *fn, int fd, + int fd_ep) +{ + int err = 0; + struct fd_cmp *fd_cmp; + + fd_cmp = calloc(1, sizeof(*fd_cmp)); + if (fd_cmp == NULL) { + ERR("could not allocate fd_cmp\n"); + return -ENOMEM; + } + + switch (type) { + case FD_CMP_MONITOR: + err = fd_cmp_open_monitor(fd_cmp, fd, fd_ep); + break; + case FD_CMP_OUT: + fd_cmp->name = "STDOUT"; + err = fd_cmp_open_std(fd_cmp, fn, fd, fd_ep); + break; + case FD_CMP_ERR: + fd_cmp->name = "STDERR"; + err = fd_cmp_open_std(fd_cmp, fn, fd, fd_ep); + break; + default: + ERR("unknown fd type %d\n", type); + err = -1; + } + + if (err < 0) { + free(fd_cmp); + return err; + } + + fd_cmp->fd_match = err; + fd_cmp->fd = fd; + fd_cmp->type = type; + fd_cmp->path = fn; + + *fd_cmp_out = fd_cmp; + return 0; +} + +static int fd_cmp_check_ev_in(struct fd_cmp *fd_cmp) +{ + if (fd_cmp->type == FD_CMP_MONITOR) { + ERR("Unexpected activity on monitor pipe\n"); + return -EINVAL; + } + fd_cmp->activity = true; + + return 0; +} + +static void fd_cmp_delete_ep(struct fd_cmp *fd_cmp, int fd_ep) +{ + if (epoll_ctl(fd_ep, EPOLL_CTL_DEL, fd_cmp->fd, NULL) < 0) { + ERR("could not remove fd %d from epoll: %m\n", fd_cmp->fd); + } + fd_cmp->fd = -1; +} + +static void fd_cmp_close(struct fd_cmp *fd_cmp) +{ + if (fd_cmp == NULL) + return; + + if (fd_cmp->fd >= 0) + close(fd_cmp->fd); + free(fd_cmp); +} + +static bool fd_cmp_regex_one(const char *pattern, const char *s) +{ + _cleanup_(regfree) regex_t re = { }; + + return !regcomp(&re, pattern, REG_EXTENDED|REG_NOSUB) && + !regexec(&re, s, 0, NULL, 0); +} + +/* + * read fd and fd_match, checking the first matches the regex of the second, + * line by line + */ +static bool fd_cmp_regex(struct fd_cmp *fd_cmp, const struct test *t) +{ + char *p, *p_match; + int done = 0, done_match = 0, r; + + if (fd_cmp->head >= sizeof(fd_cmp->buf)) { + ERR("Read %zu bytes without a newline\n", sizeof(fd_cmp->buf)); + ERR("output: %.*s", (int)sizeof(fd_cmp->buf), fd_cmp->buf); + return false; + } + + r = read(fd_cmp->fd, fd_cmp->buf + fd_cmp->head, + sizeof(fd_cmp->buf) - fd_cmp->head); + if (r <= 0) + return true; + + fd_cmp->head += r; + + /* + * Process as many lines as read from fd and that fits in the buffer - + * it's assumed that if we get N lines from fd, we should be able to + * get the same amount from fd_match + */ + for (;;) { + p = memchr(fd_cmp->buf + done, '\n', fd_cmp->head - done); + if (!p) + break; + *p = '\0'; + + p_match = memchr(fd_cmp->buf_match + done_match, '\n', + fd_cmp->head_match - done_match); + if (!p_match) { + if (fd_cmp->head_match >= sizeof(fd_cmp->buf_match)) { + ERR("Read %zu bytes without a match\n", sizeof(fd_cmp->buf_match)); + ERR("output: %.*s", (int)sizeof(fd_cmp->buf_match), fd_cmp->buf_match); + return false; + } + + /* pump more data from file */ + r = read(fd_cmp->fd_match, fd_cmp->buf_match + fd_cmp->head_match, + sizeof(fd_cmp->buf_match) - fd_cmp->head_match); + if (r <= 0) { + ERR("could not read match fd %d\n", fd_cmp->fd_match); + return false; + } + fd_cmp->head_match += r; + p_match = memchr(fd_cmp->buf_match + done_match, '\n', + fd_cmp->head_match - done_match); + if (!p_match) { + ERR("could not find match line from fd %d\n", fd_cmp->fd_match); + return false; + } + } + *p_match = '\0'; + + if (!fd_cmp_regex_one(fd_cmp->buf_match + done_match, fd_cmp->buf + done)) { + ERR("Output does not match pattern on %s:\n", fd_cmp->name); + ERR("pattern: %s\n", fd_cmp->buf_match + done_match); + ERR("output : %s\n", fd_cmp->buf + done); + return false; + } + + done = p - fd_cmp->buf + 1; + done_match = p_match - fd_cmp->buf_match + 1; + } + + /* + * Prepare for the next call: anything we processed we remove from the + * buffer by memmoving the remaining bytes up to the beginning + */ + if (done) { + if (fd_cmp->head - done) + memmove(fd_cmp->buf, fd_cmp->buf + done, fd_cmp->head - done); + fd_cmp->head -= done; + } + + if (done_match) { + if (fd_cmp->head_match - done_match) + memmove(fd_cmp->buf_match, fd_cmp->buf_match + done_match, + fd_cmp->head_match - done_match); + fd_cmp->head_match -= done_match; + } + + return true; +} + +/* read fd and fd_match, checking they match exactly */ +static bool fd_cmp_exact(struct fd_cmp *fd_cmp, const struct test *t) +{ + int r, rmatch, done = 0; + + r = read(fd_cmp->fd, fd_cmp->buf, sizeof(fd_cmp->buf) - 1); + if (r <= 0) + /* try again later */ + return true; + + /* read as much data from fd_match as we read from fd */ + for (;;) { + rmatch = read(fd_cmp->fd_match, fd_cmp->buf_match + done, r - done); + if (rmatch == 0) + break; + + if (rmatch < 0) { + if (errno == EINTR) + continue; + ERR("could not read match fd %d\n", fd_cmp->fd_match); + return false; + } + + done += rmatch; + } + + fd_cmp->buf[r] = '\0'; + fd_cmp->buf_match[r] = '\0'; + + if (t->print_outputs) + printf("%s: %s\n", fd_cmp->name, fd_cmp->buf); + + if (!streq(fd_cmp->buf, fd_cmp->buf_match)) { + ERR("Outputs do not match on %s:\n", fd_cmp->name); + ERR("correct:\n%s\n", fd_cmp->buf_match); + ERR("wrong:\n%s\n", fd_cmp->buf); + return false; + } + + return true; +} + +static bool test_run_parent_check_outputs(const struct test *t, + int fdout, int fderr, int fdmonitor, + pid_t child) +{ + int err, fd_ep; + unsigned long long end_usec, start_usec; + struct fd_cmp *fd_cmp_out = NULL; + struct fd_cmp *fd_cmp_err = NULL; + struct fd_cmp *fd_cmp_monitor = NULL; + int n_fd = 0; + + fd_ep = epoll_create1(EPOLL_CLOEXEC); + if (fd_ep < 0) { + ERR("could not create epoll fd: %m\n"); + return false; + } + + if (t->output.out != NULL) { + err = fd_cmp_open(&fd_cmp_out, + FD_CMP_OUT, t->output.out, fdout, fd_ep); + if (err < 0) + goto out; + n_fd++; + } + + if (t->output.err != NULL) { + err = fd_cmp_open(&fd_cmp_err, + FD_CMP_ERR, t->output.err, fderr, fd_ep); + if (err < 0) + goto out; + n_fd++; + } + + err = fd_cmp_open(&fd_cmp_monitor, FD_CMP_MONITOR, NULL, fdmonitor, fd_ep); + if (err < 0) + goto out; + n_fd++; + + start_usec = now_usec(); + end_usec = start_usec + TEST_TIMEOUT_USEC; + + for (err = 0; n_fd > 0;) { + int fdcount, i, timeout; + struct epoll_event ev[4]; + unsigned long long curr_usec = now_usec(); + + if (curr_usec > end_usec) + break; + + timeout = (end_usec - curr_usec) / USEC_PER_MSEC; + fdcount = epoll_wait(fd_ep, ev, 4, timeout); + if (fdcount < 0) { + if (errno == EINTR) + continue; + err = -errno; + ERR("could not poll: %m\n"); + goto out; + } + + for (i = 0; i < fdcount; i++) { + struct fd_cmp *fd_cmp = ev[i].data.ptr; + bool ret; + + if (ev[i].events & EPOLLIN) { + err = fd_cmp_check_ev_in(fd_cmp); + if (err < 0) + goto out; + + if (t->output.regex) + ret = fd_cmp_regex(fd_cmp, t); + else + ret = fd_cmp_exact(fd_cmp, t); + + if (!ret) { + err = -1; + goto out; + } + } else if (ev[i].events & EPOLLHUP) { + fd_cmp_delete_ep(fd_cmp, fd_ep); + n_fd--; + } + } + } + + err = fd_cmp_check_activity(fd_cmp_out); + err |= fd_cmp_check_activity(fd_cmp_err); + + if (err == 0 && fd_cmp_is_active(fd_cmp_monitor)) { + err = -EINVAL; + ERR("Test '%s' timed out, killing %d\n", t->name, child); + kill(child, SIGKILL); + } + +out: + fd_cmp_close(fd_cmp_out); + fd_cmp_close(fd_cmp_err); + fd_cmp_close(fd_cmp_monitor); + close(fd_ep); + + return err == 0; +} + +static inline int safe_read(int fd, void *buf, size_t count) +{ + int r; + + while (1) { + r = read(fd, buf, count); + if (r == -1 && errno == EINTR) + continue; + break; + } + + return r; +} + +static bool check_generated_files(const struct test *t) +{ + const struct keyval *k; + + /* This is not meant to be a diff replacement, just stupidly check if + * the files match. Bear in mind they can be binary files */ + for (k = t->output.files; k && k->key; k++) { + struct stat sta, stb; + int fda = -1, fdb = -1; + char bufa[4096]; + char bufb[4096]; + + fda = open(k->key, O_RDONLY); + if (fda < 0) { + ERR("could not open %s\n - %m\n", k->key); + goto fail; + } + + fdb = open(k->val, O_RDONLY); + if (fdb < 0) { + ERR("could not open %s\n - %m\n", k->val); + goto fail; + } + + if (fstat(fda, &sta) != 0) { + ERR("could not fstat %d %s\n - %m\n", fda, k->key); + goto fail; + } + + if (fstat(fdb, &stb) != 0) { + ERR("could not fstat %d %s\n - %m\n", fdb, k->key); + goto fail; + } + + if (sta.st_size != stb.st_size) { + ERR("sizes do not match %s %s\n", k->key, k->val); + goto fail; + } + + for (;;) { + int r, done; + + r = safe_read(fda, bufa, sizeof(bufa)); + if (r < 0) + goto fail; + + if (r == 0) + /* size is already checked, go to next file */ + goto next; + + for (done = 0; done < r;) { + int r2 = safe_read(fdb, bufb + done, r - done); + + if (r2 <= 0) + goto fail; + + done += r2; + } + + if (memcmp(bufa, bufb, r) != 0) + goto fail; + } + +next: + close(fda); + close(fdb); + continue; + +fail: + if (fda >= 0) + close(fda); + if (fdb >= 0) + close(fdb); + + return false; + } + + return true; +} + +static int cmp_modnames(const void *m1, const void *m2) +{ + const char *s1 = *(char *const *)m1; + const char *s2 = *(char *const *)m2; + int i; + + for (i = 0; s1[i] || s2[i]; i++) { + char c1 = s1[i], c2 = s2[i]; + if (c1 == '-') + c1 = '_'; + if (c2 == '-') + c2 = '_'; + if (c1 != c2) + return c1 - c2; + } + return 0; +} + +/* + * Store the expected module names in buf and return a list of pointers to + * them. + */ +static const char **read_expected_modules(const struct test *t, + char **buf, int *count) +{ + const char **res; + int len; + int i; + char *p; + + if (t->modules_loaded[0] == '\0') { + *count = 0; + *buf = NULL; + return NULL; + } + *buf = strdup(t->modules_loaded); + if (!*buf) { + *count = -1; + return NULL; + } + len = 1; + for (p = *buf; *p; p++) + if (*p == ',') + len++; + res = malloc(sizeof(char *) * len); + if (!res) { + perror("malloc"); + *count = -1; + free(*buf); + *buf = NULL; + return NULL; + } + i = 0; + res[i++] = *buf; + for (p = *buf; i < len; p++) + if (*p == ',') { + *p = '\0'; + res[i++] = p + 1; + } + *count = len; + return res; +} + +static char **read_loaded_modules(const struct test *t, char **buf, int *count) +{ + char dirname[PATH_MAX]; + DIR *dir; + struct dirent *dirent; + int i; + int len = 0, bufsz; + char **res = NULL; + char *p; + const char *rootfs = t->config[TC_ROOTFS] ? t->config[TC_ROOTFS] : ""; + + /* Store the entries in /sys/module to res */ + if (snprintf(dirname, sizeof(dirname), "%s/sys/module", rootfs) + >= (int)sizeof(dirname)) { + ERR("rootfs path too long: %s\n", rootfs); + *buf = NULL; + len = -1; + goto out; + } + dir = opendir(dirname); + /* not an error, simply return empty list */ + if (!dir) { + *buf = NULL; + goto out; + } + bufsz = 0; + while ((dirent = readdir(dir))) { + if (dirent->d_name[0] == '.') + continue; + len++; + bufsz += strlen(dirent->d_name) + 1; + } + res = malloc(sizeof(char *) * len); + if (!res) { + perror("malloc"); + len = -1; + goto out_dir; + } + *buf = malloc(bufsz); + if (!*buf) { + perror("malloc"); + free(res); + res = NULL; + len = -1; + goto out_dir; + } + rewinddir(dir); + i = 0; + p = *buf; + while ((dirent = readdir(dir))) { + int size; + + if (dirent->d_name[0] == '.') + continue; + size = strlen(dirent->d_name) + 1; + memcpy(p, dirent->d_name, size); + res[i++] = p; + p += size; + } +out_dir: + closedir(dir); +out: + *count = len; + return res; +} + +static int check_loaded_modules(const struct test *t) +{ + int l1, l2, i1, i2; + const char **a1; + char **a2; + char *buf1, *buf2; + int err = false; + + a1 = read_expected_modules(t, &buf1, &l1); + if (l1 < 0) + return err; + a2 = read_loaded_modules(t, &buf2, &l2); + if (l2 < 0) + goto out_a1; + qsort(a1, l1, sizeof(char *), cmp_modnames); + qsort(a2, l2, sizeof(char *), cmp_modnames); + i1 = i2 = 0; + err = true; + while (i1 < l1 || i2 < l2) { + int cmp; + + if (i1 >= l1) + cmp = 1; + else if (i2 >= l2) + cmp = -1; + else + cmp = cmp_modnames(&a1[i1], &a2[i2]); + if (cmp == 0) { + i1++; + i2++; + } else if (cmp < 0) { + err = false; + ERR("module %s not loaded\n", a1[i1]); + i1++; + } else { + err = false; + ERR("module %s is loaded but should not be \n", a2[i2]); + i2++; + } + } + free(a2); + free(buf2); +out_a1: + free(a1); + free(buf1); + return err; +} + +static inline int test_run_parent(const struct test *t, int fdout[2], + int fderr[2], int fdmonitor[2], pid_t child) +{ + pid_t pid; + int err; + bool matchout, match_modules; + + if (t->skip) { + LOG("%sSKIPPED%s: %s\n", + ANSI_HIGHLIGHT_YELLOW_ON, ANSI_HIGHLIGHT_OFF, + t->name); + err = EXIT_SUCCESS; + goto exit; + } + + /* Close write-fds */ + if (t->output.out != NULL) + close(fdout[1]); + if (t->output.err != NULL) + close(fderr[1]); + close(fdmonitor[1]); + + matchout = test_run_parent_check_outputs(t, fdout[0], fderr[0], + fdmonitor[0], child); + + /* + * break pipe on the other end: either child already closed or we want + * to stop it + */ + if (t->output.out != NULL) + close(fdout[0]); + if (t->output.err != NULL) + close(fderr[0]); + close(fdmonitor[0]); + + do { + pid = wait(&err); + if (pid == -1) { + ERR("error waitpid(): %m\n"); + err = EXIT_FAILURE; + goto exit; + } + } while (!WIFEXITED(err) && !WIFSIGNALED(err)); + + if (WIFEXITED(err)) { + if (WEXITSTATUS(err) != 0) + ERR("'%s' [%u] exited with return code %d\n", + t->name, pid, WEXITSTATUS(err)); + else + LOG("'%s' [%u] exited with return code %d\n", + t->name, pid, WEXITSTATUS(err)); + } else if (WIFSIGNALED(err)) { + ERR("'%s' [%u] terminated by signal %d (%s)\n", t->name, pid, + WTERMSIG(err), strsignal(WTERMSIG(err))); + err = t->expected_fail ? EXIT_SUCCESS : EXIT_FAILURE; + goto exit; + } + + if (matchout) + matchout = check_generated_files(t); + if (t->modules_loaded) + match_modules = check_loaded_modules(t); + else + match_modules = true; + + if (t->expected_fail == false) { + if (err == 0) { + if (matchout && match_modules) + LOG("%sPASSED%s: %s\n", + ANSI_HIGHLIGHT_GREEN_ON, ANSI_HIGHLIGHT_OFF, + t->name); + else { + ERR("%sFAILED%s: exit ok but %s do not match: %s\n", + ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF, + matchout ? "loaded modules" : "outputs", + t->name); + err = EXIT_FAILURE; + } + } else { + ERR("%sFAILED%s: %s\n", + ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF, + t->name); + } + } else { + if (err == 0) { + if (matchout) { + ERR("%sUNEXPECTED PASS%s: exit with 0: %s\n", + ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF, + t->name); + err = EXIT_FAILURE; + } else { + ERR("%sUNEXPECTED PASS%s: exit with 0 and outputs do not match: %s\n", + ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF, + t->name); + err = EXIT_FAILURE; + } + } else { + if (matchout) { + LOG("%sEXPECTED FAIL%s: %s\n", + ANSI_HIGHLIGHT_GREEN_ON, ANSI_HIGHLIGHT_OFF, + t->name); + err = EXIT_SUCCESS; + } else { + LOG("%sEXPECTED FAIL%s: exit with %d but outputs do not match: %s\n", + ANSI_HIGHLIGHT_GREEN_ON, ANSI_HIGHLIGHT_OFF, + WEXITSTATUS(err), t->name); + err = EXIT_FAILURE; + } + } + } + +exit: + LOG("------\n"); + return err; +} + +static int prepend_path(const char *extra) +{ + char *oldpath, *newpath; + int r; + + if (extra == NULL) + return 0; + + oldpath = getenv("PATH"); + if (oldpath == NULL) + return setenv("PATH", extra, 1); + + if (asprintf(&newpath, "%s:%s", extra, oldpath) < 0) { + ERR("failed to allocate memory to new PATH\n"); + return -1; + } + + r = setenv("PATH", newpath, 1); + free(newpath); + + return r; +} + +int test_run(const struct test *t) +{ + pid_t pid; + int fdout[2]; + int fderr[2]; + int fdmonitor[2]; + + if (t->need_spawn && oneshot) + test_run_spawned(t); + + if (t->output.out != NULL) { + if (pipe(fdout) != 0) { + ERR("could not create out pipe for %s\n", t->name); + return EXIT_FAILURE; + } + } + + if (t->output.err != NULL) { + if (pipe(fderr) != 0) { + ERR("could not create err pipe for %s\n", t->name); + return EXIT_FAILURE; + } + } + + if (pipe(fdmonitor) != 0) { + ERR("could not create monitor pipe for %s\n", t->name); + return EXIT_FAILURE; + } + + if (prepend_path(t->path) < 0) { + ERR("failed to prepend '%s' to PATH\n", t->path); + return EXIT_FAILURE; + } + + LOG("running %s, in forked context\n", t->name); + + pid = fork(); + if (pid < 0) { + ERR("could not fork(): %m\n"); + LOG("FAILED: %s\n", t->name); + return EXIT_FAILURE; + } + + if (pid > 0) + return test_run_parent(t, fdout, fderr, fdmonitor, pid); + + return test_run_child(t, fdout, fderr, fdmonitor); +} |