diff options
Diffstat (limited to 'runner/settings.c')
-rw-r--r-- | runner/settings.c | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/runner/settings.c b/runner/settings.c new file mode 100644 index 00000000..31754a12 --- /dev/null +++ b/runner/settings.c @@ -0,0 +1,502 @@ +#include "settings.h" + +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <libgen.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +enum { + OPT_ABORT_ON_ERROR, + OPT_TEST_LIST, + OPT_IGNORE_MISSING, + OPT_HELP = 'h', + OPT_NAME = 'n', + OPT_DRY_RUN = 'd', + OPT_INCLUDE = 't', + OPT_EXCLUDE = 'x', + OPT_SYNC = 's', + OPT_LOG_LEVEL = 'l', + OPT_OVERWRITE = 'o', + OPT_MULTIPLE = 'm', + OPT_TIMEOUT = 'c', + OPT_WATCHDOG = 'g', +}; + +static struct { + int level; + const char *name; +} log_levels[] = { + { LOG_LEVEL_NORMAL, "normal" }, + { LOG_LEVEL_QUIET, "quiet" }, + { LOG_LEVEL_VERBOSE, "verbose" }, + { 0, 0 }, +}; + +static bool set_log_level(struct settings* settings, const char *level) +{ + typeof(*log_levels) *it; + + for (it = log_levels; it->name; it++) { + if (!strcmp(level, it->name)) { + settings->log_level = it->level; + return true; + } + } + + return false; +} + +static const char *usage_str = + "usage: runner [options] [test_root] results-path\n\n" + "Options:\n" + " Piglit compatible:\n" + " -h, --help Show this help message and exit\n" + " -n <test name>, --name <test name>\n" + " Name of this test run\n" + " -d, --dry-run Do not execute the tests\n" + " -t <regex>, --include-tests <regex>\n" + " Run only matching tests (can be used more than once)\n" + " -x <regex>, --exclude-tests <regex>\n" + " Exclude matching tests (can be used more than once)\n" + " --abort-on-monitored-error\n" + " Abort execution when a fatal condition is detected.\n" + " <TODO>\n" + " -s, --sync Sync results to disk after every test\n" + " -l {quiet,verbose,dummy}, --log-level {quiet,verbose,dummy}\n" + " Set the logger verbosity level\n" + " --test-list TEST_LIST\n" + " A file containing a list of tests to run\n" + " -o, --overwrite If the results-path already exists, delete it\n" + " --ignore-missing Ignored but accepted, for piglit compatibility\n" + "\n" + " Incompatible options:\n" + " -m, --multiple-mode Run multiple subtests in the same binary execution.\n" + " If a testlist file is given, consecutive subtests are\n" + " run in the same execution if they are from the same\n" + " binary. Note that in that case relative ordering of the\n" + " subtest execution is dictated by the test binary, not\n" + " the testlist\n" + " --inactivity-timeout <seconds>\n" + " Kill the running test after <seconds> of inactivity in\n" + " the test's stdout, stderr, or dmesg\n" + " --use-watchdog Use hardware watchdog for lethal enforcement of the\n" + " above timeout. Killing the test process is still\n" + " attempted at timeout trigger.\n" + " [test_root] Directory that contains the IGT tests. The environment\n" + " variable IGT_TEST_ROOT will be used if set, overriding\n" + " this option if given.\n" + ; + +static void usage(const char *extra_message, FILE *f) +{ + if (extra_message) + fprintf(f, "%s\n\n", extra_message); + + fputs(usage_str, f); +} + +static bool add_regex(struct regex_list *list, char *new) +{ + regex_t *regex; + size_t buflen; + char *buf; + int s; + + regex = malloc(sizeof(*regex)); + + if ((s = regcomp(regex, new, + REG_EXTENDED | REG_NOSUB)) != 0) { + buflen = regerror(s, regex, NULL, 0); + buf = malloc(buflen); + regerror(s, regex, buf, buflen); + usage(buf, stderr); + + free(buf); + regfree(regex); + free(regex); + return false; + } + + list->regexes = realloc(list->regexes, + (list->size + 1) * sizeof(*list->regexes)); + list->regex_strings = realloc(list->regex_strings, + (list->size + 1) * sizeof(*list->regex_strings)); + list->regexes[list->size] = regex; + list->regex_strings[list->size] = new; + list->size++; + + return true; +} + +static void free_regexes(struct regex_list *regexes) +{ + size_t i; + + for (i = 0; i < regexes->size; i++) { + free(regexes->regex_strings[i]); + regfree(regexes->regexes[i]); + free(regexes->regexes[i]); + } + free(regexes->regex_strings); + free(regexes->regexes); +} + +static bool readable_file(char *filename) +{ + return !access(filename, R_OK); +} + +void init_settings(struct settings *settings) +{ + memset(settings, 0, sizeof(*settings)); +} + +void free_settings(struct settings *settings) +{ + free(settings->test_list); + free(settings->name); + free(settings->test_root); + free(settings->results_path); + + free_regexes(&settings->include_regexes); + free_regexes(&settings->exclude_regexes); + + init_settings(settings); +} + +bool parse_options(int argc, char **argv, + struct settings *settings) +{ + int c; + char *env_test_root; + + static struct option long_options[] = { + {"help", no_argument, NULL, OPT_HELP}, + {"name", required_argument, NULL, OPT_NAME}, + {"dry-run", no_argument, NULL, OPT_DRY_RUN}, + {"include-tests", required_argument, NULL, OPT_INCLUDE}, + {"exclude-tests", required_argument, NULL, OPT_EXCLUDE}, + {"abort-on-monitored-error", no_argument, NULL, OPT_ABORT_ON_ERROR}, + {"sync", no_argument, NULL, OPT_SYNC}, + {"log-level", required_argument, NULL, OPT_LOG_LEVEL}, + {"test-list", required_argument, NULL, OPT_TEST_LIST}, + {"overwrite", no_argument, NULL, OPT_OVERWRITE}, + {"ignore-missing", no_argument, NULL, OPT_IGNORE_MISSING}, + {"multiple-mode", no_argument, NULL, OPT_MULTIPLE}, + {"inactivity-timeout", required_argument, NULL, OPT_TIMEOUT}, + {"use-watchdog", no_argument, NULL, OPT_WATCHDOG}, + { 0, 0, 0, 0}, + }; + + free_settings(settings); + + optind = 1; + + while ((c = getopt_long(argc, argv, "hn:dt:x:sl:om", long_options, NULL)) != -1) { + switch (c) { + case OPT_HELP: + usage(NULL, stdout); + goto error; + case OPT_NAME: + settings->name = strdup(optarg); + break; + case OPT_DRY_RUN: + settings->dry_run = true; + break; + case OPT_INCLUDE: + if (!add_regex(&settings->include_regexes, strdup(optarg))) + goto error; + break; + case OPT_EXCLUDE: + if (!add_regex(&settings->exclude_regexes, strdup(optarg))) + goto error; + break; + case OPT_ABORT_ON_ERROR: + settings->abort_on_error = true; + break; + case OPT_SYNC: + settings->sync = true; + break; + case OPT_LOG_LEVEL: + if (!set_log_level(settings, optarg)) { + usage("Cannot parse log level", stderr); + goto error; + } + break; + case OPT_TEST_LIST: + settings->test_list = absolute_path(optarg); + break; + case OPT_OVERWRITE: + settings->overwrite = true; + break; + case OPT_IGNORE_MISSING: + /* Ignored, piglit compatibility */ + break; + case OPT_MULTIPLE: + settings->multiple_mode = true; + break; + case OPT_TIMEOUT: + settings->inactivity_timeout = atoi(optarg); + break; + case OPT_WATCHDOG: + settings->use_watchdog = true; + break; + case '?': + usage(NULL, stderr); + goto error; + default: + usage("Cannot parse options", stderr); + goto error; + } + } + + switch (argc - optind) { + case 2: + settings->test_root = absolute_path(argv[optind]); + ++optind; + /* fallthrough */ + case 1: + settings->results_path = absolute_path(argv[optind]); + break; + case 0: + usage("Results-path missing", stderr); + goto error; + default: + usage("Extra arguments after results-path", stderr); + goto error; + } + + if ((env_test_root = getenv("IGT_TEST_ROOT")) != NULL) { + free(settings->test_root); + settings->test_root = absolute_path(env_test_root); + } + + if (!settings->test_root) { + usage("Test root not set", stderr); + goto error; + } + + if (!settings->name) { + char *name = strdup(settings->results_path); + settings->name = strdup(basename(name)); + free(name); + } + + return true; + + error: + free_settings(settings); + return false; +} + +bool validate_settings(struct settings *settings) +{ + int dirfd, fd; + + if (settings->test_list && !readable_file(settings->test_list)) { + usage("Cannot open test-list file", stderr); + return false; + } + + if (!settings->results_path) { + usage("No results-path set; this shouldn't happen", stderr); + return false; + } + + if (!settings->test_root) { + usage("No test root set; this shouldn't happen", stderr); + return false; + } + + dirfd = open(settings->test_root, O_DIRECTORY | O_RDONLY); + if (dirfd < 0) { + fprintf(stderr, "Test directory %s cannot be opened\n", settings->test_root); + return false; + } + + fd = openat(dirfd, "test-list.txt", O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Cannot open %s/test-list.txt\n", settings->test_root); + close(dirfd); + return false; + } + + close(fd); + close(dirfd); + + return true; +} + +char *absolute_path(char *path) +{ + char *result = NULL; + char *tmppath, *tmpname; + + result = realpath(path, NULL); + if (result != NULL) + return result; + + tmppath = strdup(path); + tmpname = dirname(tmppath); + free(result); + result = realpath(tmpname, NULL); + free(tmppath); + + if (result != NULL) { + char *ret; + + tmppath = strdup(path); + tmpname = basename(tmppath); + + asprintf(&ret, "%s/%s", result, tmpname); + free(result); + free(tmppath); + return ret; + } + + free(result); + return NULL; +} + +static char settings_filename[] = "metadata.txt"; +bool serialize_settings(struct settings *settings) +{ +#define SERIALIZE_LINE(f, s, name, format) fprintf(f, "%s : " format "\n", #name, s->name) + + int dirfd, fd; + FILE *f; + + if (!settings->results_path) { + usage("No results-path set; this shouldn't happen", stderr); + return false; + } + + if ((dirfd = open(settings->results_path, O_DIRECTORY | O_RDONLY)) < 0) { + mkdir(settings->results_path, 0755); + if ((dirfd = open(settings->results_path, O_DIRECTORY | O_RDONLY)) < 0) { + usage("Creating results-path failed", stderr); + return false; + } + } + + if (!settings->overwrite && + faccessat(dirfd, settings_filename, F_OK, 0) == 0) { + usage("Settings metadata already exists and not overwriting", stderr); + return false; + } + + if (settings->overwrite && + unlinkat(dirfd, settings_filename, 0) != 0 && + errno != ENOENT) { + usage("Error removing old settings metadata", stderr); + return false; + } + + if ((fd = openat(dirfd, settings_filename, O_CREAT | O_EXCL | O_WRONLY, 0666)) < 0) { + char *msg; + + asprintf(&msg, "Creating settings serialization file failed: %s", strerror(errno)); + usage(msg, stderr); + + free(msg); + close(dirfd); + return false; + } + + f = fdopen(fd, "w"); + if (!f) { + close(fd); + close(dirfd); + return false; + } + + SERIALIZE_LINE(f, settings, abort_on_error, "%d"); + if (settings->test_list) + SERIALIZE_LINE(f, settings, test_list, "%s"); + if (settings->name) + SERIALIZE_LINE(f, settings, name, "%s"); + SERIALIZE_LINE(f, settings, dry_run, "%d"); + SERIALIZE_LINE(f, settings, sync, "%d"); + SERIALIZE_LINE(f, settings, log_level, "%d"); + SERIALIZE_LINE(f, settings, overwrite, "%d"); + SERIALIZE_LINE(f, settings, multiple_mode, "%d"); + SERIALIZE_LINE(f, settings, inactivity_timeout, "%d"); + SERIALIZE_LINE(f, settings, use_watchdog, "%d"); + SERIALIZE_LINE(f, settings, test_root, "%s"); + SERIALIZE_LINE(f, settings, results_path, "%s"); + + if (settings->sync) { + fsync(fd); + fsync(dirfd); + } + + fclose(f); + close(dirfd); + return true; + +#undef SERIALIZE_LINE +} + +bool read_settings(struct settings *settings, int dirfd) +{ +#define PARSE_LINE(s, name, val, field, write) \ + if (!strcmp(name, #field)) { \ + s->field = write; \ + free(name); \ + free(val); \ + name = val = NULL; \ + continue; \ + } + + int fd; + FILE *f; + char *name = NULL, *val = NULL; + + free_settings(settings); + + if ((fd = openat(dirfd, settings_filename, O_RDONLY)) < 0) + return false; + + f = fdopen(fd, "r"); + if (!f) { + close(fd); + return false; + } + + while (fscanf(f, "%ms : %ms", &name, &val) == 2) { + int numval = atoi(val); + PARSE_LINE(settings, name, val, abort_on_error, numval); + PARSE_LINE(settings, name, val, test_list, val ? strdup(val) : NULL); + PARSE_LINE(settings, name, val, name, val ? strdup(val) : NULL); + PARSE_LINE(settings, name, val, dry_run, numval); + PARSE_LINE(settings, name, val, sync, numval); + PARSE_LINE(settings, name, val, log_level, numval); + PARSE_LINE(settings, name, val, overwrite, numval); + PARSE_LINE(settings, name, val, multiple_mode, numval); + PARSE_LINE(settings, name, val, inactivity_timeout, numval); + PARSE_LINE(settings, name, val, use_watchdog, numval); + PARSE_LINE(settings, name, val, test_root, val ? strdup(val) : NULL); + PARSE_LINE(settings, name, val, results_path, val ? strdup(val) : NULL); + + printf("Warning: Unknown field in settings file: %s = %s\n", + name, val); + free(name); + free(val); + name = val = NULL; + } + + free(name); + free(val); + fclose(f); + + return true; + +#undef PARSE_LINE +} |