1 #include <ctype.h>
2 #include <errno.h>
3 #include <fcntl.h>
4 #include <linux/limits.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <sys/stat.h>
9 #include <sys/types.h>
10 #include <unistd.h>
11
12 #include "job_list.h"
13 #include "igt_core.h"
14
matches_any(const char * str,struct regex_list * list)15 static bool matches_any(const char *str, struct regex_list *list)
16 {
17 size_t i;
18
19 for (i = 0; i < list->size; i++) {
20 if (g_regex_match(list->regexes[i], str, 0, NULL))
21 return true;
22 }
23
24 return false;
25 }
26
add_job_list_entry(struct job_list * job_list,char * binary,char ** subtests,size_t subtest_count)27 static void add_job_list_entry(struct job_list *job_list,
28 char *binary,
29 char **subtests,
30 size_t subtest_count)
31 {
32 struct job_list_entry *entry;
33
34 job_list->size++;
35 job_list->entries = realloc(job_list->entries, job_list->size * sizeof(*job_list->entries));
36 entry = &job_list->entries[job_list->size - 1];
37
38 entry->binary = binary;
39 entry->subtests = subtests;
40 entry->subtest_count = subtest_count;
41 }
42
add_subtests(struct job_list * job_list,struct settings * settings,char * binary,struct regex_list * include,struct regex_list * exclude)43 static void add_subtests(struct job_list *job_list, struct settings *settings,
44 char *binary,
45 struct regex_list *include, struct regex_list *exclude)
46 {
47 FILE *p;
48 char cmd[256] = {};
49 char *subtestname;
50 char **subtests = NULL;
51 size_t num_subtests = 0;
52 int s;
53
54 s = snprintf(cmd, sizeof(cmd), "%s/%s --list-subtests",
55 settings->test_root, binary);
56 if (s < 0) {
57 fprintf(stderr, "Failure generating command string, this shouldn't happen.\n");
58 return;
59 }
60
61 if (s >= sizeof(cmd)) {
62 fprintf(stderr, "Path to binary too long, ignoring: %s/%s\n",
63 settings->test_root, binary);
64 return;
65 }
66
67 p = popen(cmd, "r");
68 if (!p) {
69 fprintf(stderr, "popen failed when executing %s: %s\n",
70 cmd,
71 strerror(errno));
72 return;
73 }
74
75 while (fscanf(p, "%ms", &subtestname) == 1) {
76 char piglitname[256];
77
78 generate_piglit_name(binary, subtestname, piglitname, sizeof(piglitname));
79
80 if (exclude && exclude->size && matches_any(piglitname, exclude)) {
81 free(subtestname);
82 continue;
83 }
84
85 if (include && include->size && !matches_any(piglitname, include)) {
86 free(subtestname);
87 continue;
88 }
89
90 if (settings->multiple_mode) {
91 num_subtests++;
92 subtests = realloc(subtests, num_subtests * sizeof(*subtests));
93 subtests[num_subtests - 1] = strdup(subtestname);
94 } else {
95 subtests = malloc(sizeof(*subtests));
96 *subtests = strdup(subtestname);
97 add_job_list_entry(job_list, strdup(binary), subtests, 1);
98 subtests = NULL;
99 }
100
101 free(subtestname);
102 }
103
104 if (num_subtests)
105 add_job_list_entry(job_list, strdup(binary), subtests, num_subtests);
106
107 s = pclose(p);
108 if (s == 0) {
109 return;
110 } else if (s == -1) {
111 fprintf(stderr, "popen error when executing %s: %s\n", binary, strerror(errno));
112 } else if (WIFEXITED(s)) {
113 if (WEXITSTATUS(s) == IGT_EXIT_INVALID) {
114 char piglitname[256];
115
116 generate_piglit_name(binary, NULL,
117 piglitname, sizeof(piglitname));
118 /* No subtests on this one */
119 if (exclude && exclude->size &&
120 matches_any(piglitname, exclude)) {
121 return;
122 }
123 if (!include || !include->size ||
124 matches_any(piglitname, include)) {
125 add_job_list_entry(job_list, strdup(binary), NULL, 0);
126 return;
127 }
128 }
129 } else {
130 fprintf(stderr, "Test binary %s died unexpectedly\n", binary);
131 }
132 }
133
filtered_job_list(struct job_list * job_list,struct settings * settings,int fd)134 static bool filtered_job_list(struct job_list *job_list,
135 struct settings *settings,
136 int fd)
137 {
138 FILE *f;
139 char buf[128];
140 bool ok;
141
142 if (job_list->entries != NULL) {
143 fprintf(stderr, "Caller didn't clear the job list, this shouldn't happen\n");
144 exit(1);
145 }
146
147 f = fdopen(fd, "r");
148
149 while (fscanf(f, "%127s", buf) == 1) {
150 if (!strcmp(buf, "TESTLIST") || !(strcmp(buf, "END")))
151 continue;
152
153 /*
154 * If the binary name matches exclude filters, no
155 * subtests are added.
156 */
157 if (settings->exclude_regexes.size && matches_any(buf, &settings->exclude_regexes))
158 continue;
159
160 /*
161 * If the binary name matches include filters (or include filters not present),
162 * all subtests except those matching exclude filters are added.
163 */
164 if (!settings->include_regexes.size || matches_any(buf, &settings->include_regexes)) {
165 if (settings->multiple_mode && !settings->exclude_regexes.size)
166 /*
167 * Optimization; we know that all
168 * subtests will be included, so we
169 * get to omit executing
170 * --list-subtests.
171 */
172 add_job_list_entry(job_list, strdup(buf), NULL, 0);
173 else
174 add_subtests(job_list, settings, buf,
175 NULL, &settings->exclude_regexes);
176 continue;
177 }
178
179 /*
180 * Binary name doesn't match exclude or include filters.
181 */
182 add_subtests(job_list, settings, buf,
183 &settings->include_regexes,
184 &settings->exclude_regexes);
185 }
186
187 ok = job_list->size != 0;
188 if (!ok)
189 fprintf(stderr, "Filter didn't match any job name\n");
190
191 return ok;
192 }
193
job_list_from_test_list(struct job_list * job_list,struct settings * settings)194 static bool job_list_from_test_list(struct job_list *job_list,
195 struct settings *settings)
196 {
197 FILE *f;
198 char *line = NULL;
199 size_t line_len = 0;
200 struct job_list_entry entry = {};
201 bool any = false;
202
203 if ((f = fopen(settings->test_list, "r")) == NULL) {
204 fprintf(stderr, "Cannot open test list file %s\n", settings->test_list);
205 return false;
206 }
207
208 while (1) {
209 char *binary;
210 char *delim;
211
212 if (getline(&line, &line_len, f) == -1) {
213 if (errno == EINTR)
214 continue;
215 else
216 break;
217 }
218
219 /* # starts a comment */
220 if ((delim = strchr(line, '#')) != NULL)
221 *delim = '\0';
222
223 if (settings->exclude_regexes.size && matches_any(line, &settings->exclude_regexes))
224 continue;
225
226 if (settings->include_regexes.size && !matches_any(line, &settings->include_regexes))
227 continue;
228
229 if (sscanf(line, "igt@%ms", &binary) == 1) {
230 if ((delim = strchr(binary, '@')) != NULL)
231 *delim++ = '\0';
232
233 if (!settings->multiple_mode) {
234 char **subtests = NULL;
235 if (delim) {
236 subtests = malloc(sizeof(char*));
237 subtests[0] = strdup(delim);
238 }
239 add_job_list_entry(job_list, strdup(binary),
240 subtests, (size_t)(subtests != NULL));
241 any = true;
242 free(binary);
243 binary = NULL;
244 continue;
245 }
246
247 /*
248 * If the currently built entry has the same
249 * binary, add a subtest. Otherwise submit
250 * what's already built and start a new one.
251 */
252 if (entry.binary && !strcmp(entry.binary, binary)) {
253 if (!delim) {
254 /* ... except we didn't get a subtest */
255 fprintf(stderr,
256 "Error: Unexpected test without subtests "
257 "after same test had subtests\n");
258 free(binary);
259 fclose(f);
260 return false;
261 }
262 entry.subtest_count++;
263 entry.subtests = realloc(entry.subtests,
264 entry.subtest_count *
265 sizeof(*entry.subtests));
266 entry.subtests[entry.subtest_count - 1] = strdup(delim);
267 free(binary);
268 binary = NULL;
269 continue;
270 }
271
272 if (entry.binary) {
273 add_job_list_entry(job_list, entry.binary, entry.subtests, entry.subtest_count);
274 any = true;
275 }
276
277 memset(&entry, 0, sizeof(entry));
278 entry.binary = strdup(binary);
279 if (delim) {
280 entry.subtests = malloc(sizeof(*entry.subtests));
281 entry.subtests[0] = strdup(delim);
282 entry.subtest_count = 1;
283 }
284
285 free(binary);
286 binary = NULL;
287 }
288 }
289
290 if (entry.binary) {
291 add_job_list_entry(job_list, entry.binary, entry.subtests, entry.subtest_count);
292 any = true;
293 }
294
295 free(line);
296 fclose(f);
297 return any;
298 }
299
list_all_tests(struct job_list * lst)300 void list_all_tests(struct job_list *lst)
301 {
302 char piglit_name[256];
303
304 for (size_t test_idx = 0; test_idx < lst->size; ++test_idx) {
305 struct job_list_entry *current_entry = lst->entries + test_idx;
306 char *binary = current_entry->binary;
307
308 if (current_entry->subtest_count == 0) {
309 generate_piglit_name(binary, NULL,
310 piglit_name, sizeof(piglit_name));
311 printf("%s\n", piglit_name);
312 continue;
313 }
314 for (size_t subtest_idx = 0;
315 subtest_idx < current_entry->subtest_count;
316 ++subtest_idx) {
317 generate_piglit_name(binary, current_entry->subtests[subtest_idx],
318 piglit_name, sizeof(piglit_name));
319 printf("%s\n", piglit_name);
320 }
321 }
322 }
323
lowercase(const char * str)324 static char *lowercase(const char *str)
325 {
326 char *ret = malloc(strlen(str) + 1);
327 char *q = ret;
328
329 while (*str) {
330 if (isspace(*str))
331 break;
332
333 *q++ = tolower(*str++);
334 }
335 *q = '\0';
336
337 return ret;
338 }
339
generate_piglit_name(const char * binary,const char * subtest,char * namebuf,size_t namebuf_size)340 void generate_piglit_name(const char *binary, const char *subtest,
341 char *namebuf, size_t namebuf_size)
342 {
343 char *lc_binary = lowercase(binary);
344 char *lc_subtest = NULL;
345
346 if (!subtest) {
347 snprintf(namebuf, namebuf_size, "igt@%s", lc_binary);
348 free(lc_binary);
349 return;
350 }
351
352 lc_subtest = lowercase(subtest);
353
354 snprintf(namebuf, namebuf_size, "igt@%s@%s", lc_binary, lc_subtest);
355
356 free(lc_binary);
357 free(lc_subtest);
358 }
359
init_job_list(struct job_list * job_list)360 void init_job_list(struct job_list *job_list)
361 {
362 memset(job_list, 0, sizeof(*job_list));
363 }
364
free_job_list(struct job_list * job_list)365 void free_job_list(struct job_list *job_list)
366 {
367 int i, k;
368
369 for (i = 0; i < job_list->size; i++) {
370 struct job_list_entry *entry = &job_list->entries[i];
371
372 free(entry->binary);
373 for (k = 0; k < entry->subtest_count; k++) {
374 free(entry->subtests[k]);
375 }
376 free(entry->subtests);
377 }
378 free(job_list->entries);
379 init_job_list(job_list);
380 }
381
create_job_list(struct job_list * job_list,struct settings * settings)382 bool create_job_list(struct job_list *job_list,
383 struct settings *settings)
384 {
385 int dirfd, fd;
386 bool result;
387
388 if (!settings->test_root) {
389 fprintf(stderr, "No test root set; this shouldn't happen\n");
390 return false;
391 }
392
393 free_job_list(job_list);
394
395 dirfd = open(settings->test_root, O_DIRECTORY | O_RDONLY);
396 if (dirfd < 0) {
397 fprintf(stderr, "Test directory %s cannot be opened\n", settings->test_root);
398 return false;
399 }
400
401 fd = openat(dirfd, "test-list.txt", O_RDONLY);
402 if (fd < 0) {
403 fprintf(stderr, "Cannot open %s/test-list.txt\n", settings->test_root);
404 close(dirfd);
405 return false;
406 }
407
408 /*
409 * If a test_list is given (not to be confused with
410 * test-list.txt), we use it directly without making tests
411 * list their subtests. If include/exclude filters are given
412 * we filter them directly from the test_list.
413 */
414 if (settings->test_list)
415 result = job_list_from_test_list(job_list, settings);
416 else
417 result = filtered_job_list(job_list, settings, fd);
418
419 close(fd);
420 close(dirfd);
421
422 return result;
423 }
424
425 static char joblist_filename[] = "joblist.txt";
serialize_job_list(struct job_list * job_list,struct settings * settings)426 bool serialize_job_list(struct job_list *job_list, struct settings *settings)
427 {
428 int dirfd, fd;
429 size_t i, k;
430 FILE *f;
431
432 if (!settings->results_path) {
433 fprintf(stderr, "No results-path set; this shouldn't happen\n");
434 return false;
435 }
436
437 if ((dirfd = open(settings->results_path, O_DIRECTORY | O_RDONLY)) < 0) {
438 mkdir(settings->results_path, 0777);
439 if ((dirfd = open(settings->results_path, O_DIRECTORY | O_RDONLY)) < 0) {
440 fprintf(stderr, "Creating results-path failed\n");
441 return false;
442 }
443 }
444
445 if (!settings->overwrite &&
446 faccessat(dirfd, joblist_filename, F_OK, 0) == 0) {
447 fprintf(stderr, "Job list file already exists and not overwriting\n");
448 close(dirfd);
449 return false;
450 }
451
452 if (settings->overwrite &&
453 unlinkat(dirfd, joblist_filename, 0) != 0 &&
454 errno != ENOENT) {
455 fprintf(stderr, "Error removing old job list\n");
456 close(dirfd);
457 return false;
458 }
459
460 if ((fd = openat(dirfd, joblist_filename, O_CREAT | O_EXCL | O_WRONLY, 0666)) < 0) {
461 fprintf(stderr, "Creating job list serialization file failed: %s\n", strerror(errno));
462 close(dirfd);
463 return false;
464 }
465
466 f = fdopen(fd, "w");
467 if (!f) {
468 close(fd);
469 close(dirfd);
470 return false;
471 }
472
473 for (i = 0; i < job_list->size; i++) {
474 struct job_list_entry *entry = &job_list->entries[i];
475 fputs(entry->binary, f);
476
477 if (entry->subtest_count) {
478 const char *delim = "";
479
480 fprintf(f, " ");
481
482 for (k = 0; k < entry->subtest_count; k++) {
483 fprintf(f, "%s%s", delim, entry->subtests[k]);
484 delim = ",";
485 }
486 }
487
488 fprintf(f, "\n");
489 }
490
491 if (settings->sync) {
492 fsync(fd);
493 fsync(dirfd);
494 }
495
496 fclose(f);
497 close(dirfd);
498 return true;
499 }
500
read_job_list(struct job_list * job_list,int dirfd)501 bool read_job_list(struct job_list *job_list, int dirfd)
502 {
503 int fd;
504 FILE *f;
505 ssize_t read;
506 char *line = NULL;
507 size_t line_len = 0;
508
509 free_job_list(job_list);
510
511 if ((fd = openat(dirfd, joblist_filename, O_RDONLY)) < 0)
512 return false;
513
514 f = fdopen(fd, "r");
515 if (!f) {
516 close(fd);
517 return false;
518 }
519
520 while ((read = getline(&line, &line_len, f))) {
521 char *binary, *sublist, *comma;
522 char **subtests = NULL;
523 size_t num_subtests = 0, len;
524
525 if (read < 0) {
526 if (errno == EINTR)
527 continue;
528 else
529 break;
530 }
531
532 len = strlen(line);
533 if (len > 0 && line[len - 1] == '\n')
534 line[len - 1] = '\0';
535
536 sublist = strchr(line, ' ');
537 if (!sublist) {
538 add_job_list_entry(job_list, strdup(line), NULL, 0);
539 continue;
540 }
541
542 *sublist++ = '\0';
543 binary = strdup(line);
544
545 do {
546 comma = strchr(sublist, ',');
547 if (comma) {
548 *comma++ = '\0';
549 }
550
551 ++num_subtests;
552 subtests = realloc(subtests, num_subtests * sizeof(*subtests));
553 subtests[num_subtests - 1] = strdup(sublist);
554 sublist = comma;
555 } while (comma != NULL);
556
557 add_job_list_entry(job_list, binary, subtests, num_subtests);
558 }
559
560 free(line);
561 fclose(f);
562
563 return true;
564 }
565