#include "settings.h" #include #include #include #include #include #include #include #include #include #include #include #include enum { OPT_ABORT_ON_ERROR, OPT_TEST_LIST, OPT_IGNORE_MISSING, OPT_PIGLIT_DMESG, OPT_DMESG_WARN_LEVEL, OPT_OVERALL_TIMEOUT, 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', OPT_BLACKLIST = 'b', OPT_LIST_ALL = 'L', }; static struct { int level; const char *name; } log_levels[] = { { LOG_LEVEL_NORMAL, "normal" }, { LOG_LEVEL_QUIET, "quiet" }, { LOG_LEVEL_VERBOSE, "verbose" }, { 0, 0 }, }; static struct { int value; const char *name; } abort_conditions[] = { { ABORT_TAINT, "taint" }, { ABORT_LOCKDEP, "lockdep" }, { ABORT_ALL, "all" }, { 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 bool set_abort_condition(struct settings* settings, const char *cond) { typeof(*abort_conditions) *it; if (!cond) { settings->abort_mask = ABORT_ALL; return true; } if (strlen(cond) == 0) { settings->abort_mask = 0; return true; } for (it = abort_conditions; it->name; it++) { if (!strcmp(cond, it->name)) { settings->abort_mask |= it->value; return true; } } return false; } static bool parse_abort_conditions(struct settings *settings, const char *optarg) { char *dup, *origdup, *p; if (!optarg) return set_abort_condition(settings, NULL); origdup = dup = strdup(optarg); while (dup) { if ((p = strchr(dup, ',')) != NULL) { *p = '\0'; p++; } if (!set_abort_condition(settings, dup)) { free(origdup); return false; } dup = p; } free(origdup); return true; } static const char *usage_str = "usage: runner [options] [test_root] results-path\n" " or: runner --list-all [options] [test_root]\n\n" "Options:\n" " Piglit compatible:\n" " -h, --help Show this help message and exit\n" " -n , --name \n" " Name of this test run\n" " -d, --dry-run Do not execute the tests\n" " -t , --include-tests \n" " Run only matching tests (can be used more than once)\n" " -x , --exclude-tests \n" " Exclude matching tests (can be used more than once)\n" " --abort-on-monitored-error[=list]\n" " Abort execution when a fatal condition is detected.\n" " A comma-separated list of conditions to check can be\n" " given. If not given, all conditions are checked. An\n" " empty string as a condition disables aborting\n" " Possible conditions:\n" " lockdep - abort when kernel lockdep has been angered.\n" " taint - abort when kernel becomes fatally tainted.\n" " all - abort for all of the above.\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 \n" " Kill the running test after of inactivity in\n" " the test's stdout, stderr, or dmesg\n" " --overall-timeout \n" " Don't execute more tests after has elapsed\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" " --dmesg-warn-level \n" " Messages with log level equal or lower (more serious)\n" " to the given one will override the test result to\n" " dmesg-warn/dmesg-fail, assuming they go through filtering.\n" " Defaults to 4 (KERN_WARNING).\n" " --piglit-style-dmesg Filter dmesg like piglit does. Piglit considers matches\n" " against a short filter list to mean the test result\n" " should be changed to dmesg-warn/dmesg-fail. Without\n" " this option everything except matches against a\n" " (longer) filter list means the test result should\n" " change. KERN_NOTICE dmesg level is treated as warn,\n" " unless overridden with --dmesg-warn-level.\n" " -b, --blacklist FILENAME\n" " Exclude all test matching to regexes from FILENAME\n" " (can be used more than once)\n" " -L, --list-all List all matching subtests instead of running\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) { GRegex *regex; GError *error = NULL; regex = g_regex_new(new, G_REGEX_OPTIMIZE, 0, &error); if (error) { char *buf = malloc(snprintf(NULL, 0, "Invalid regex '%s': %s", new, error->message) + 1); sprintf(buf, "Invalid regex '%s': %s", new, error->message); usage(buf, stderr); free(buf); g_error_free(error); 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 bool parse_blacklist(struct regex_list *exclude_regexes, char *blacklist_filename) { FILE *f; char *line = NULL; size_t line_len = 0; bool status = false; if ((f = fopen(blacklist_filename, "r")) == NULL) { fprintf(stderr, "Cannot open blacklist file %s\n", blacklist_filename); return false; } while (1) { size_t str_size = 0, idx = 0; if (getline(&line, &line_len, f) == -1) { if (errno == EINTR) continue; else break; } while (true) { if (line[idx] == '\n' || line[idx] == '\0' || line[idx] == '#') /* # starts a comment */ break; if (!isspace(line[idx])) str_size = idx + 1; idx++; } if (str_size > 0) { char *test_regex = strndup(line, str_size); status = add_regex(exclude_regexes, test_regex); if (!status) break; } } free(line); fclose(f); return status; } static void free_regexes(struct regex_list *regexes) { size_t i; for (i = 0; i < regexes->size; i++) { free(regexes->regex_strings[i]); g_regex_unref(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", optional_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}, {"overall-timeout", required_argument, NULL, OPT_OVERALL_TIMEOUT}, {"use-watchdog", no_argument, NULL, OPT_WATCHDOG}, {"piglit-style-dmesg", no_argument, NULL, OPT_PIGLIT_DMESG}, {"dmesg-warn-level", required_argument, NULL, OPT_DMESG_WARN_LEVEL}, {"blacklist", required_argument, NULL, OPT_BLACKLIST}, {"list-all", no_argument, NULL, OPT_LIST_ALL}, { 0, 0, 0, 0}, }; free_settings(settings); optind = 1; settings->dmesg_warn_level = -1; while ((c = getopt_long(argc, argv, "hn:dt:x:sl:omb:L", 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: if (!parse_abort_conditions(settings, optarg)) goto error; 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_OVERALL_TIMEOUT: settings->overall_timeout = atoi(optarg); break; case OPT_WATCHDOG: settings->use_watchdog = true; break; case OPT_PIGLIT_DMESG: settings->piglit_style_dmesg = true; if (settings->dmesg_warn_level < 0) settings->dmesg_warn_level = 5; /* KERN_NOTICE */ break; case OPT_DMESG_WARN_LEVEL: settings->dmesg_warn_level = atoi(optarg); break; case OPT_BLACKLIST: if (!parse_blacklist(&settings->exclude_regexes, absolute_path(optarg))) goto error; break; case OPT_LIST_ALL: settings->list_all = true; break; case '?': usage(NULL, stderr); goto error; default: usage("Cannot parse options", stderr); goto error; } } if (settings->dmesg_warn_level < 0) settings->dmesg_warn_level = 4; /* KERN_WARN */ if (settings->list_all) { /* --list-all doesn't require results path */ switch (argc - optind) { case 1: settings->test_root = absolute_path(argv[optind]); ++optind; /* fallthrough */ case 0: break; default: usage("Too many arguments for --list-all", stderr); goto error; } } else { 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 (!settings->name) { char *name = strdup(settings->results_path); settings->name = strdup(basename(name)); free(name); } } 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; } 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; } static char *_dirname(const char *path) { char *tmppath = strdup(path); char *tmpname = dirname(tmppath); tmpname = strdup(tmpname); free(tmppath); return tmpname; } static char *_basename(const char *path) { char *tmppath = strdup(path); char *tmpname = basename(tmppath); tmpname = strdup(tmpname); free(tmppath); return tmpname; } char *absolute_path(char *path) { char *result = NULL; char *base, *dir; char *ret; result = realpath(path, NULL); if (result != NULL) return result; dir = _dirname(path); ret = absolute_path(dir); free(dir); base = _basename(path); asprintf(&result, "%s/%s", ret, base); free(base); free(ret); return result; } 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_mask, "%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, overall_timeout, "%d"); SERIALIZE_LINE(f, settings, use_watchdog, "%d"); SERIALIZE_LINE(f, settings, piglit_style_dmesg, "%d"); SERIALIZE_LINE(f, settings, dmesg_warn_level, "%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_from_file(struct settings *settings, FILE *f) { #define PARSE_LINE(s, name, val, field, write) \ if (!strcmp(name, #field)) { \ s->field = write; \ free(name); \ free(val); \ name = val = NULL; \ continue; \ } char *name = NULL, *val = NULL; settings->dmesg_warn_level = -1; while (fscanf(f, "%ms : %ms", &name, &val) == 2) { int numval = atoi(val); PARSE_LINE(settings, name, val, abort_mask, 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, overall_timeout, numval); PARSE_LINE(settings, name, val, use_watchdog, numval); PARSE_LINE(settings, name, val, piglit_style_dmesg, numval); PARSE_LINE(settings, name, val, dmesg_warn_level, 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; } if (settings->dmesg_warn_level < 0) { if (settings->piglit_style_dmesg) settings->dmesg_warn_level = 5; else settings->dmesg_warn_level = 4; } free(name); free(val); return true; #undef PARSE_LINE } bool read_settings_from_dir(struct settings *settings, int dirfd) { int fd; FILE *f; free_settings(settings); if ((fd = openat(dirfd, settings_filename, O_RDONLY)) < 0) return false; f = fdopen(fd, "r"); if (!f) { close(fd); return false; } if (!read_settings_from_file(settings, f)) { fclose(f); return false; } fclose(f); return true; }