commit f6ad6195f31f30e9bf1aacef0f9a324d0d938a11 from: Sergey Bronnikov date: Wed Nov 04 11:06:22 2020 UTC runtest: - recursive dir tree traversal using fts(3) - set proper test status using exit code commit - deed5012af2072e6bec7db5318300f911cd96da5 commit + f6ad6195f31f30e9bf1aacef0f9a324d0d938a11 blob - 225c53597915a1bcf224cfa080cb71041f279eef blob + f65d4c7000cafb27ae4f7ee00fc10629f65e4a23 --- tools/runtest/test_list.h +++ tools/runtest/test_list.h @@ -32,11 +32,11 @@ #include enum test_status { - STATUS_OK, /* TestAnythingProtocol */ - STATUS_NOTOK, /* TestAnythingProtocol */ - STATUS_MISSING, /* TestAnythingProtocol */ - STATUS_TODO, /* TestAnythingProtocol */ - STATUS_SKIP, /* TestAnythingProtocol */ + STATUS_OK, /* TestAnythingProtocol */ + STATUS_NOTOK, /* TestAnythingProtocol */ + STATUS_MISSING, /* TestAnythingProtocol */ + STATUS_TODO, /* TestAnythingProtocol */ + STATUS_SKIP, /* TestAnythingProtocol */ }; struct test { blob - 11d5c4b677e9dd0ef5de584bf7a4cb34727baf98 blob + 5ffbc8a3e51ed7ea87a36b24e67b40852f9f91e1 --- tools/runtest/utils.c +++ tools/runtest/utils.c @@ -1,5 +1,4 @@ #define _GNU_SOURCE -#include #include #include #include @@ -14,7 +13,9 @@ #include #include #include - +#include +#include +#include #include #include "test_list.h" @@ -35,15 +36,18 @@ static inline char *format_time(char *strtime, size_t return strtime; } -struct test_list *test_discovery(const char *dir, const char *exec_file) { +/* Compare files by name. */ +int entcmp(const FTSENT **a, const FTSENT **b) +{ + return strcmp((*a)->fts_name, (*b)->fts_name); +} + +/* + * Print all files in the directory tree that match the glob pattern. + * Example: test_discovery("/usr/src/test", "Makefile"); + */ +struct test_list *test_discovery(char *dir, const char *pattern) { struct test_list *tests; - struct stat st_buf; - - int n, i; - struct dirent **namelist; - int fail; - int saved_errno; - tests = calloc(1, sizeof(struct test_list)); if (tests == NULL) { fprintf(stderr, "malloc failed"); @@ -51,80 +55,62 @@ struct test_list *test_discovery(const char *dir, cons } TAILQ_INIT(tests); - do { - if (stat(dir, &st_buf) == -1) { - free_tests(tests); - break; - } + FTS *tree; + FTSENT *f; + char *argv[] = { dir, NULL }; - if (!S_ISDIR(st_buf.st_mode)) { - free_tests(tests); - errno = EINVAL; - break; + /* + * FTS_LOGICAL follows symbolic links, including links to other + * directories. It detects cycles, so we never have an infinite + * loop. FTS_NOSTAT is because we never use f->statp. It uses + * our entcmp() to sort files by name. + */ + tree = fts_open(argv, FTS_LOGICAL | FTS_NOSTAT, entcmp); + if (tree == NULL) + err(1, "fts_open"); + + while ((f = fts_read(tree))) { + switch (f->fts_info) { + case FTS_DNR: /* Cannot read directory */ + case FTS_ERR: /* Miscellaneous error */ + case FTS_NS: /* stat() error */ + /* Show error, then continue to next files. */ + warn("%s", f->fts_path); + continue; + case FTS_DP: + /* Ignore post-order visit to directory. */ + continue; } - n = scandir(dir, &namelist, NULL, alphasort); - if (n == -1) { - free_tests(tests); - break; - } - - fail = 0; - for (i = 0; i < n; i++) { - char *run_test; - char *d_name = strdup(namelist[i]->d_name); - if (d_name == NULL) { - fail = 1; - saved_errno = errno; - break; - } - - if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) { - free(d_name); - continue; - } - - if (asprintf(&run_test, "%s/%s/%s", dir, d_name, exec_file) == -1) { - fail = 1; - saved_errno = errno; - free(d_name); - break; - } - - if (stat(run_test, &st_buf) == -1) { - free(run_test); - free(d_name); - continue; - } - - if (!S_ISREG(st_buf.st_mode)) { - free(run_test); - free(d_name); - continue; - } - + if (fnmatch(pattern, f->fts_name, FNM_PERIOD) == 0) { struct test *t = NULL; t = calloc(1, sizeof(struct test)); if (t == NULL) { fprintf(stderr, "malloc failed"); break; } - t->name = run_test; - t->path = d_name; + t->name = dirname(strdup(f->fts_path)); + t->path = strdup(f->fts_path); TAILQ_INSERT_TAIL(tests, t, entries); } - for (i = 0; i < n; i++) - free(namelist[i]); - free(namelist); - - if (fail) { - free_tests(tests); - errno = saved_errno; - break; + /* + * A cycle happens when a symbolic link (or perhaps a + * hard link) puts a directory inside itself. Tell user + * when this happens. + */ + if (f->fts_info == FTS_DC) { + warnx("%s: cycle in directory tree", f->fts_path); } - } while (0); + } + + /* fts_read() sets errno = 0 unless it has error. */ + if (errno != 0) + err(1, "fts_read"); + if (fts_close(tree) < 0) + err(1, "fts_close"); + return tests; } @@ -149,15 +135,15 @@ static inline void run_child(char *run_test, int fd_st exit(-1); } - char make[] = "/usr/bin/make"; + char make_bin[] = "/usr/bin/make"; char make_opt[] = "-C"; - char *argv[] = {make, make_opt, dirname(strdup(run_test)), NULL}; - execv(argv[0], argv); + char *argv[] = {make_bin, make_opt, run_test, NULL}; + int rc = execv(argv[0], argv); dup2(fd_stdout, STDOUT_FILENO); dup2(fd_stderr, STDERR_FILENO); - exit(1); + exit(rc); } static inline int wait_child(const char *test_dir, const char *run_test, @@ -255,7 +241,6 @@ int run_tests(struct test_list *tests, const struct te rc = -1; break; } - dirname(test_dir); child = fork(); if (child == -1) { @@ -280,6 +265,14 @@ int run_tests(struct test_list *tests, const struct te if (status) rc += 1; + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) { + t->status = STATUS_NOTOK; + } else { + t->status = STATUS_OK; + } + } + fprintf(fp_stdout, "%s END: %s\n", format_time(time, TIME_BUF_SIZE), test_dir); } blob - ad0bf429bc5fc8000b36f01ae1830c10904087d6 blob + 1a605247ceecd97ead5f17d8db2cb789af17a88c --- tools/runtest/utils.h +++ tools/runtest/utils.h @@ -16,7 +16,7 @@ struct test_options { char *exec_file; }; -extern struct test_list *test_discovery(const char *, const char *); +extern struct test_list *test_discovery(char *, const char *); extern int print_tests(struct test_list *, FILE *); extern int run_tests(struct test_list *, const struct test_options, const char *, FILE *, FILE *);