| #include <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <linux/limits.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include "job_list.h" |
| #include "igt_core.h" |
| |
| static bool matches_any(const char *str, struct regex_list *list) |
| { |
| size_t i; |
| |
| for (i = 0; i < list->size; i++) { |
| if (g_regex_match(list->regexes[i], str, 0, NULL)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static void add_job_list_entry(struct job_list *job_list, |
| char *binary, |
| char **subtests, |
| size_t subtest_count) |
| { |
| struct job_list_entry *entry; |
| |
| job_list->size++; |
| job_list->entries = realloc(job_list->entries, job_list->size * sizeof(*job_list->entries)); |
| entry = &job_list->entries[job_list->size - 1]; |
| |
| entry->binary = binary; |
| entry->subtests = subtests; |
| entry->subtest_count = subtest_count; |
| } |
| |
| static void add_subtests(struct job_list *job_list, struct settings *settings, |
| char *binary, |
| struct regex_list *include, struct regex_list *exclude) |
| { |
| FILE *p; |
| char cmd[256] = {}; |
| char *subtestname; |
| char **subtests = NULL; |
| size_t num_subtests = 0; |
| int s; |
| |
| s = snprintf(cmd, sizeof(cmd), "%s/%s --list-subtests", |
| settings->test_root, binary); |
| if (s < 0) { |
| fprintf(stderr, "Failure generating command string, this shouldn't happen.\n"); |
| return; |
| } |
| |
| if (s >= sizeof(cmd)) { |
| fprintf(stderr, "Path to binary too long, ignoring: %s/%s\n", |
| settings->test_root, binary); |
| return; |
| } |
| |
| p = popen(cmd, "r"); |
| if (!p) { |
| fprintf(stderr, "popen failed when executing %s: %s\n", |
| cmd, |
| strerror(errno)); |
| return; |
| } |
| |
| while (fscanf(p, "%ms", &subtestname) == 1) { |
| char piglitname[256]; |
| |
| generate_piglit_name(binary, subtestname, piglitname, sizeof(piglitname)); |
| |
| if (exclude && exclude->size && matches_any(piglitname, exclude)) { |
| free(subtestname); |
| continue; |
| } |
| |
| if (include && include->size && !matches_any(piglitname, include)) { |
| free(subtestname); |
| continue; |
| } |
| |
| if (settings->multiple_mode) { |
| num_subtests++; |
| subtests = realloc(subtests, num_subtests * sizeof(*subtests)); |
| subtests[num_subtests - 1] = strdup(subtestname); |
| } else { |
| subtests = malloc(sizeof(*subtests)); |
| *subtests = strdup(subtestname); |
| add_job_list_entry(job_list, strdup(binary), subtests, 1); |
| subtests = NULL; |
| } |
| |
| free(subtestname); |
| } |
| |
| if (num_subtests) |
| add_job_list_entry(job_list, strdup(binary), subtests, num_subtests); |
| |
| s = pclose(p); |
| if (s == 0) { |
| return; |
| } else if (s == -1) { |
| fprintf(stderr, "popen error when executing %s: %s\n", binary, strerror(errno)); |
| } else if (WIFEXITED(s)) { |
| if (WEXITSTATUS(s) == IGT_EXIT_INVALID) { |
| char piglitname[256]; |
| |
| generate_piglit_name(binary, NULL, |
| piglitname, sizeof(piglitname)); |
| /* No subtests on this one */ |
| if (exclude && exclude->size && |
| matches_any(piglitname, exclude)) { |
| return; |
| } |
| if (!include || !include->size || |
| matches_any(piglitname, include)) { |
| add_job_list_entry(job_list, strdup(binary), NULL, 0); |
| return; |
| } |
| } |
| } else { |
| fprintf(stderr, "Test binary %s died unexpectedly\n", binary); |
| } |
| } |
| |
| static bool filtered_job_list(struct job_list *job_list, |
| struct settings *settings, |
| int fd) |
| { |
| FILE *f; |
| char buf[128]; |
| bool ok; |
| |
| if (job_list->entries != NULL) { |
| fprintf(stderr, "Caller didn't clear the job list, this shouldn't happen\n"); |
| exit(1); |
| } |
| |
| f = fdopen(fd, "r"); |
| |
| while (fscanf(f, "%127s", buf) == 1) { |
| if (!strcmp(buf, "TESTLIST") || !(strcmp(buf, "END"))) |
| continue; |
| |
| /* |
| * If the binary name matches exclude filters, no |
| * subtests are added. |
| */ |
| if (settings->exclude_regexes.size && matches_any(buf, &settings->exclude_regexes)) |
| continue; |
| |
| /* |
| * If the binary name matches include filters (or include filters not present), |
| * all subtests except those matching exclude filters are added. |
| */ |
| if (!settings->include_regexes.size || matches_any(buf, &settings->include_regexes)) { |
| if (settings->multiple_mode && !settings->exclude_regexes.size) |
| /* |
| * Optimization; we know that all |
| * subtests will be included, so we |
| * get to omit executing |
| * --list-subtests. |
| */ |
| add_job_list_entry(job_list, strdup(buf), NULL, 0); |
| else |
| add_subtests(job_list, settings, buf, |
| NULL, &settings->exclude_regexes); |
| continue; |
| } |
| |
| /* |
| * Binary name doesn't match exclude or include filters. |
| */ |
| add_subtests(job_list, settings, buf, |
| &settings->include_regexes, |
| &settings->exclude_regexes); |
| } |
| |
| ok = job_list->size != 0; |
| if (!ok) |
| fprintf(stderr, "Filter didn't match any job name\n"); |
| |
| return ok; |
| } |
| |
| static bool job_list_from_test_list(struct job_list *job_list, |
| struct settings *settings) |
| { |
| FILE *f; |
| char *line = NULL; |
| size_t line_len = 0; |
| struct job_list_entry entry = {}; |
| bool any = false; |
| |
| if ((f = fopen(settings->test_list, "r")) == NULL) { |
| fprintf(stderr, "Cannot open test list file %s\n", settings->test_list); |
| return false; |
| } |
| |
| while (1) { |
| char *binary; |
| char *delim; |
| |
| if (getline(&line, &line_len, f) == -1) { |
| if (errno == EINTR) |
| continue; |
| else |
| break; |
| } |
| |
| /* # starts a comment */ |
| if ((delim = strchr(line, '#')) != NULL) |
| *delim = '\0'; |
| |
| if (settings->exclude_regexes.size && matches_any(line, &settings->exclude_regexes)) |
| continue; |
| |
| if (settings->include_regexes.size && !matches_any(line, &settings->include_regexes)) |
| continue; |
| |
| if (sscanf(line, "igt@%ms", &binary) == 1) { |
| if ((delim = strchr(binary, '@')) != NULL) |
| *delim++ = '\0'; |
| |
| if (!settings->multiple_mode) { |
| char **subtests = NULL; |
| if (delim) { |
| subtests = malloc(sizeof(char*)); |
| subtests[0] = strdup(delim); |
| } |
| add_job_list_entry(job_list, strdup(binary), |
| subtests, (size_t)(subtests != NULL)); |
| any = true; |
| free(binary); |
| binary = NULL; |
| continue; |
| } |
| |
| /* |
| * If the currently built entry has the same |
| * binary, add a subtest. Otherwise submit |
| * what's already built and start a new one. |
| */ |
| if (entry.binary && !strcmp(entry.binary, binary)) { |
| if (!delim) { |
| /* ... except we didn't get a subtest */ |
| fprintf(stderr, |
| "Error: Unexpected test without subtests " |
| "after same test had subtests\n"); |
| free(binary); |
| fclose(f); |
| return false; |
| } |
| entry.subtest_count++; |
| entry.subtests = realloc(entry.subtests, |
| entry.subtest_count * |
| sizeof(*entry.subtests)); |
| entry.subtests[entry.subtest_count - 1] = strdup(delim); |
| free(binary); |
| binary = NULL; |
| continue; |
| } |
| |
| if (entry.binary) { |
| add_job_list_entry(job_list, entry.binary, entry.subtests, entry.subtest_count); |
| any = true; |
| } |
| |
| memset(&entry, 0, sizeof(entry)); |
| entry.binary = strdup(binary); |
| if (delim) { |
| entry.subtests = malloc(sizeof(*entry.subtests)); |
| entry.subtests[0] = strdup(delim); |
| entry.subtest_count = 1; |
| } |
| |
| free(binary); |
| binary = NULL; |
| } |
| } |
| |
| if (entry.binary) { |
| add_job_list_entry(job_list, entry.binary, entry.subtests, entry.subtest_count); |
| any = true; |
| } |
| |
| free(line); |
| fclose(f); |
| return any; |
| } |
| |
| void list_all_tests(struct job_list *lst) |
| { |
| char piglit_name[256]; |
| |
| for (size_t test_idx = 0; test_idx < lst->size; ++test_idx) { |
| struct job_list_entry *current_entry = lst->entries + test_idx; |
| char *binary = current_entry->binary; |
| |
| if (current_entry->subtest_count == 0) { |
| generate_piglit_name(binary, NULL, |
| piglit_name, sizeof(piglit_name)); |
| printf("%s\n", piglit_name); |
| continue; |
| } |
| for (size_t subtest_idx = 0; |
| subtest_idx < current_entry->subtest_count; |
| ++subtest_idx) { |
| generate_piglit_name(binary, current_entry->subtests[subtest_idx], |
| piglit_name, sizeof(piglit_name)); |
| printf("%s\n", piglit_name); |
| } |
| } |
| } |
| |
| static char *lowercase(const char *str) |
| { |
| char *ret = malloc(strlen(str) + 1); |
| char *q = ret; |
| |
| while (*str) { |
| if (isspace(*str)) |
| break; |
| |
| *q++ = tolower(*str++); |
| } |
| *q = '\0'; |
| |
| return ret; |
| } |
| |
| void generate_piglit_name(const char *binary, const char *subtest, |
| char *namebuf, size_t namebuf_size) |
| { |
| char *lc_binary = lowercase(binary); |
| char *lc_subtest = NULL; |
| |
| if (!subtest) { |
| snprintf(namebuf, namebuf_size, "igt@%s", lc_binary); |
| free(lc_binary); |
| return; |
| } |
| |
| lc_subtest = lowercase(subtest); |
| |
| snprintf(namebuf, namebuf_size, "igt@%s@%s", lc_binary, lc_subtest); |
| |
| free(lc_binary); |
| free(lc_subtest); |
| } |
| |
| void init_job_list(struct job_list *job_list) |
| { |
| memset(job_list, 0, sizeof(*job_list)); |
| } |
| |
| void free_job_list(struct job_list *job_list) |
| { |
| int i, k; |
| |
| for (i = 0; i < job_list->size; i++) { |
| struct job_list_entry *entry = &job_list->entries[i]; |
| |
| free(entry->binary); |
| for (k = 0; k < entry->subtest_count; k++) { |
| free(entry->subtests[k]); |
| } |
| free(entry->subtests); |
| } |
| free(job_list->entries); |
| init_job_list(job_list); |
| } |
| |
| bool create_job_list(struct job_list *job_list, |
| struct settings *settings) |
| { |
| int dirfd, fd; |
| bool result; |
| |
| if (!settings->test_root) { |
| fprintf(stderr, "No test root set; this shouldn't happen\n"); |
| return false; |
| } |
| |
| free_job_list(job_list); |
| |
| 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; |
| } |
| |
| /* |
| * If a test_list is given (not to be confused with |
| * test-list.txt), we use it directly without making tests |
| * list their subtests. If include/exclude filters are given |
| * we filter them directly from the test_list. |
| */ |
| if (settings->test_list) |
| result = job_list_from_test_list(job_list, settings); |
| else |
| result = filtered_job_list(job_list, settings, fd); |
| |
| close(fd); |
| close(dirfd); |
| |
| return result; |
| } |
| |
| static char joblist_filename[] = "joblist.txt"; |
| bool serialize_job_list(struct job_list *job_list, struct settings *settings) |
| { |
| int dirfd, fd; |
| size_t i, k; |
| FILE *f; |
| |
| if (!settings->results_path) { |
| fprintf(stderr, "No results-path set; this shouldn't happen\n"); |
| return false; |
| } |
| |
| if ((dirfd = open(settings->results_path, O_DIRECTORY | O_RDONLY)) < 0) { |
| mkdir(settings->results_path, 0777); |
| if ((dirfd = open(settings->results_path, O_DIRECTORY | O_RDONLY)) < 0) { |
| fprintf(stderr, "Creating results-path failed\n"); |
| return false; |
| } |
| } |
| |
| if (!settings->overwrite && |
| faccessat(dirfd, joblist_filename, F_OK, 0) == 0) { |
| fprintf(stderr, "Job list file already exists and not overwriting\n"); |
| close(dirfd); |
| return false; |
| } |
| |
| if (settings->overwrite && |
| unlinkat(dirfd, joblist_filename, 0) != 0 && |
| errno != ENOENT) { |
| fprintf(stderr, "Error removing old job list\n"); |
| close(dirfd); |
| return false; |
| } |
| |
| if ((fd = openat(dirfd, joblist_filename, O_CREAT | O_EXCL | O_WRONLY, 0666)) < 0) { |
| fprintf(stderr, "Creating job list serialization file failed: %s\n", strerror(errno)); |
| close(dirfd); |
| return false; |
| } |
| |
| f = fdopen(fd, "w"); |
| if (!f) { |
| close(fd); |
| close(dirfd); |
| return false; |
| } |
| |
| for (i = 0; i < job_list->size; i++) { |
| struct job_list_entry *entry = &job_list->entries[i]; |
| fputs(entry->binary, f); |
| |
| if (entry->subtest_count) { |
| const char *delim = ""; |
| |
| fprintf(f, " "); |
| |
| for (k = 0; k < entry->subtest_count; k++) { |
| fprintf(f, "%s%s", delim, entry->subtests[k]); |
| delim = ","; |
| } |
| } |
| |
| fprintf(f, "\n"); |
| } |
| |
| if (settings->sync) { |
| fsync(fd); |
| fsync(dirfd); |
| } |
| |
| fclose(f); |
| close(dirfd); |
| return true; |
| } |
| |
| bool read_job_list(struct job_list *job_list, int dirfd) |
| { |
| int fd; |
| FILE *f; |
| ssize_t read; |
| char *line = NULL; |
| size_t line_len = 0; |
| |
| free_job_list(job_list); |
| |
| if ((fd = openat(dirfd, joblist_filename, O_RDONLY)) < 0) |
| return false; |
| |
| f = fdopen(fd, "r"); |
| if (!f) { |
| close(fd); |
| return false; |
| } |
| |
| while ((read = getline(&line, &line_len, f))) { |
| char *binary, *sublist, *comma; |
| char **subtests = NULL; |
| size_t num_subtests = 0, len; |
| |
| if (read < 0) { |
| if (errno == EINTR) |
| continue; |
| else |
| break; |
| } |
| |
| len = strlen(line); |
| if (len > 0 && line[len - 1] == '\n') |
| line[len - 1] = '\0'; |
| |
| sublist = strchr(line, ' '); |
| if (!sublist) { |
| add_job_list_entry(job_list, strdup(line), NULL, 0); |
| continue; |
| } |
| |
| *sublist++ = '\0'; |
| binary = strdup(line); |
| |
| do { |
| comma = strchr(sublist, ','); |
| if (comma) { |
| *comma++ = '\0'; |
| } |
| |
| ++num_subtests; |
| subtests = realloc(subtests, num_subtests * sizeof(*subtests)); |
| subtests[num_subtests - 1] = strdup(sublist); |
| sublist = comma; |
| } while (comma != NULL); |
| |
| add_job_list_entry(job_list, binary, subtests, num_subtests); |
| } |
| |
| free(line); |
| fclose(f); |
| |
| return true; |
| } |