1 #include <assert.h>
2 #include <ctype.h>
3 #include <fcntl.h>
4 #include <stdio.h>
5 #include <string.h>
6 #include <sys/mman.h>
7 #include <sys/stat.h>
8 #include <sys/types.h>
9 #include <unistd.h>
10
11 #include <json.h>
12
13 #include "igt_core.h"
14 #include "resultgen.h"
15 #include "settings.h"
16 #include "executor.h"
17 #include "output_strings.h"
18
19 #define INCOMPLETE_EXITCODE -1
20
21 _Static_assert(INCOMPLETE_EXITCODE != IGT_EXIT_SKIP, "exit code clash");
22 _Static_assert(INCOMPLETE_EXITCODE != IGT_EXIT_SUCCESS, "exit code clash");
23 _Static_assert(INCOMPLETE_EXITCODE != IGT_EXIT_INVALID, "exit code clash");
24
25 struct subtests
26 {
27 char **names;
28 size_t size;
29 };
30
31 struct results
32 {
33 struct json_object *tests;
34 struct json_object *totals;
35 struct json_object *runtimes;
36 };
37
38 /*
39 * A lot of string handling here operates on an mmapped buffer, and
40 * thus we can't assume null-terminated strings. Buffers will be
41 * passed around as pointer+size, or pointer+pointer-past-the-end, the
42 * mem*() family of functions is used instead of str*().
43 */
44
find_line_starting_with(char * haystack,const char * needle,char * end)45 static char *find_line_starting_with(char *haystack, const char *needle, char *end)
46 {
47 while (haystack < end) {
48 char *line_end = memchr(haystack, '\n', end - haystack);
49
50 if (end - haystack < strlen(needle))
51 return NULL;
52 if (!memcmp(haystack, needle, strlen(needle)))
53 return haystack;
54 if (line_end == NULL)
55 return NULL;
56 haystack = line_end + 1;
57 }
58
59 return NULL;
60 }
61
find_line_starting_with_either(char * haystack,const char * needle1,const char * needle2,char * end)62 static char *find_line_starting_with_either(char *haystack,
63 const char *needle1,
64 const char *needle2,
65 char *end)
66 {
67 while (haystack < end) {
68 char *line_end = memchr(haystack, '\n', end - haystack);
69 size_t linelen = line_end != NULL ? line_end - haystack : end - haystack;
70 if ((linelen >= strlen(needle1) && !memcmp(haystack, needle1, strlen(needle1))) ||
71 (linelen >= strlen(needle2) && !memcmp(haystack, needle2, strlen(needle2))))
72 return haystack;
73
74 if (line_end == NULL)
75 return NULL;
76
77 haystack = line_end + 1;
78 }
79
80 return NULL;
81 }
82
next_line(char * line,char * bufend)83 static char *next_line(char *line, char *bufend)
84 {
85 char *ret;
86
87 if (!line)
88 return NULL;
89
90 ret = memchr(line, '\n', bufend - line);
91 if (ret)
92 ret++;
93
94 if (ret < bufend)
95 return ret;
96 else
97 return NULL;
98 }
99
find_line_after_last(char * begin,const char * needle1,const char * needle2,char * end)100 static char *find_line_after_last(char *begin,
101 const char *needle1,
102 const char *needle2,
103 char *end)
104 {
105 char *one, *two;
106 char *current_pos = begin;
107 char *needle1_newline = malloc(strlen(needle1) + 2);
108 char *needle2_newline = malloc(strlen(needle2) + 2);
109
110 needle1_newline[0] = needle2_newline[0] = '\n';
111 strcpy(needle1_newline + 1, needle1);
112 strcpy(needle2_newline + 1, needle2);
113
114 while (true) {
115 one = memmem(current_pos, end - current_pos, needle1_newline, strlen(needle1_newline));
116 two = memmem(current_pos, end - current_pos, needle2_newline, strlen(needle2_newline));
117 if (one == NULL && two == NULL)
118 break;
119
120 if (one != NULL && current_pos < one)
121 current_pos = one;
122 if (two != NULL && current_pos < two)
123 current_pos = two;
124
125 one = next_line(current_pos, end);
126 if (one != NULL)
127 current_pos = one;
128 }
129 free(needle1_newline);
130 free(needle2_newline);
131
132 one = memchr(current_pos, '\n', end - current_pos);
133 if (one != NULL)
134 return ++one;
135
136 return current_pos;
137 }
138
count_lines(const char * buf,const char * bufend)139 static size_t count_lines(const char *buf, const char *bufend)
140 {
141 size_t ret = 0;
142 while (buf < bufend && (buf = memchr(buf, '\n', bufend - buf)) != NULL) {
143 ret++;
144 buf++;
145 }
146
147 return ret;
148 }
149
append_line(char ** buf,size_t * buflen,char * line)150 static void append_line(char **buf, size_t *buflen, char *line)
151 {
152 size_t linelen = strlen(line);
153
154 *buf = realloc(*buf, *buflen + linelen + 1);
155 strcpy(*buf + *buflen, line);
156 *buflen += linelen;
157 }
158
159 static const struct {
160 const char *output_str;
161 const char *result_str;
162 } resultmap[] = {
163 { "SUCCESS", "pass" },
164 { "SKIP", "skip" },
165 { "FAIL", "fail" },
166 { "CRASH", "crash" },
167 { "TIMEOUT", "timeout" },
168 };
parse_result_string(char * resultstring,size_t len,const char ** result,double * time)169 static void parse_result_string(char *resultstring, size_t len, const char **result, double *time)
170 {
171 size_t i;
172 size_t wordlen = 0;
173
174 while (wordlen < len && !isspace(resultstring[wordlen])) {
175 wordlen++;
176 }
177
178 *result = NULL;
179 for (i = 0; i < (sizeof(resultmap) / sizeof(resultmap[0])); i++) {
180 if (!strncmp(resultstring, resultmap[i].output_str, wordlen)) {
181 *result = resultmap[i].result_str;
182 break;
183 }
184 }
185
186 /* If the result string is unknown, use incomplete */
187 if (!*result)
188 *result = "incomplete";
189
190 /*
191 * Check for subtest runtime after the result. The string is
192 * '(' followed by the runtime in seconds as floating point,
193 * followed by 's)'.
194 */
195 wordlen++;
196 if (wordlen < len && resultstring[wordlen] == '(') {
197 char *dup;
198
199 wordlen++;
200 dup = malloc(len - wordlen + 1);
201 memcpy(dup, resultstring + wordlen, len - wordlen);
202 dup[len - wordlen] = '\0';
203 *time = strtod(dup, NULL);
204
205 free(dup);
206 }
207 }
208
parse_subtest_result(char * subtest,const char ** result,double * time,char * buf,char * bufend)209 static void parse_subtest_result(char *subtest, const char **result, double *time, char *buf, char *bufend)
210 {
211 char *line;
212 char *line_end;
213 char *resultstring;
214 size_t linelen;
215 size_t subtestlen = strlen(subtest);
216
217 *result = NULL;
218 *time = 0.0;
219
220 if (!buf) return;
221
222 /*
223 * The result line structure is:
224 *
225 * - The string "Subtest " (`SUBTEST_RESULT` from output_strings.h)
226 * - The subtest name
227 * - The characters ':' and ' '
228 * - Subtest result string
229 * - Optional:
230 * -- The characters ' ' and '('
231 * -- Subtest runtime in seconds as floating point
232 * -- The characters 's' and ')'
233 *
234 * Example:
235 * Subtest subtestname: PASS (0.003s)
236 */
237
238 line = find_line_starting_with(buf, SUBTEST_RESULT, bufend);
239 if (!line) {
240 *result = "incomplete";
241 return;
242 }
243
244 line_end = memchr(line, '\n', bufend - line);
245 linelen = line_end != NULL ? line_end - line : bufend - line;
246
247 if (strlen(SUBTEST_RESULT) + subtestlen + strlen(": ") > linelen ||
248 strncmp(line + strlen(SUBTEST_RESULT), subtest, subtestlen))
249 return parse_subtest_result(subtest, result, time, line + linelen, bufend);
250
251 resultstring = line + strlen(SUBTEST_RESULT) + subtestlen + strlen(": ");
252 parse_result_string(resultstring, linelen - (resultstring - line), result, time);
253 }
254
get_or_create_json_object(struct json_object * base,const char * key)255 static struct json_object *get_or_create_json_object(struct json_object *base,
256 const char *key)
257 {
258 struct json_object *ret;
259
260 if (json_object_object_get_ex(base, key, &ret))
261 return ret;
262
263 ret = json_object_new_object();
264 json_object_object_add(base, key, ret);
265
266 return ret;
267 }
268
set_result(struct json_object * obj,const char * result)269 static void set_result(struct json_object *obj, const char *result)
270 {
271 json_object_object_add(obj, "result",
272 json_object_new_string(result));
273 }
274
add_runtime(struct json_object * obj,double time)275 static void add_runtime(struct json_object *obj, double time)
276 {
277 double oldtime;
278 struct json_object *timeobj = get_or_create_json_object(obj, "time");
279 struct json_object *oldend;
280
281 json_object_object_add(timeobj, "__type__",
282 json_object_new_string("TimeAttribute"));
283 json_object_object_add(timeobj, "start",
284 json_object_new_double(0.0));
285
286 if (!json_object_object_get_ex(timeobj, "end", &oldend)) {
287 json_object_object_add(timeobj, "end",
288 json_object_new_double(time));
289 return;
290 }
291
292 /* Add the runtime to the existing runtime. */
293 oldtime = json_object_get_double(oldend);
294 time += oldtime;
295 json_object_object_add(timeobj, "end",
296 json_object_new_double(time));
297 }
298
set_runtime(struct json_object * obj,double time)299 static void set_runtime(struct json_object *obj, double time)
300 {
301 struct json_object *timeobj = get_or_create_json_object(obj, "time");
302
303 json_object_object_add(timeobj, "__type__",
304 json_object_new_string("TimeAttribute"));
305 json_object_object_add(timeobj, "start",
306 json_object_new_double(0.0));
307 json_object_object_add(timeobj, "end",
308 json_object_new_double(time));
309 }
310
fill_from_output(int fd,const char * binary,const char * key,struct subtests * subtests,struct json_object * tests)311 static bool fill_from_output(int fd, const char *binary, const char *key,
312 struct subtests *subtests,
313 struct json_object *tests)
314 {
315 char *buf, *bufend, *nullchr;
316 struct stat statbuf;
317 char piglit_name[256];
318 char *igt_version = NULL;
319 size_t igt_version_len = 0;
320 struct json_object *current_test = NULL;
321 size_t i;
322
323 if (fstat(fd, &statbuf))
324 return false;
325
326 if (statbuf.st_size != 0) {
327 buf = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
328 if (buf == MAP_FAILED)
329 return false;
330 } else {
331 buf = NULL;
332 }
333
334 /*
335 * Avoid null characters: Just pretend the output stops at the
336 * first such character, if any.
337 */
338 if ((nullchr = memchr(buf, '\0', statbuf.st_size)) != NULL) {
339 statbuf.st_size = nullchr - buf;
340 }
341
342 bufend = buf + statbuf.st_size;
343
344 igt_version = find_line_starting_with(buf, IGT_VERSIONSTRING, bufend);
345 if (igt_version) {
346 char *newline = memchr(igt_version, '\n', bufend - igt_version);
347 igt_version_len = newline - igt_version;
348 }
349
350 /* TODO: Refactor to helper functions */
351 if (subtests->size == 0) {
352 /* No subtests */
353 generate_piglit_name(binary, NULL, piglit_name, sizeof(piglit_name));
354 current_test = get_or_create_json_object(tests, piglit_name);
355
356 json_object_object_add(current_test, key,
357 json_object_new_string_len(buf, statbuf.st_size));
358 if (igt_version)
359 json_object_object_add(current_test, "igt-version",
360 json_object_new_string_len(igt_version,
361 igt_version_len));
362
363 return true;
364 }
365
366 for (i = 0; i < subtests->size; i++) {
367 char *this_sub_begin, *this_sub_result;
368 const char *resulttext;
369 char *beg, *end, *startline;
370 double time;
371 int begin_len;
372 int result_len;
373
374 generate_piglit_name(binary, subtests->names[i], piglit_name, sizeof(piglit_name));
375 current_test = get_or_create_json_object(tests, piglit_name);
376
377 begin_len = asprintf(&this_sub_begin, "%s%s\n", STARTING_SUBTEST, subtests->names[i]);
378 result_len = asprintf(&this_sub_result, "%s%s: ", SUBTEST_RESULT, subtests->names[i]);
379
380 if (begin_len < 0 || result_len < 0) {
381 fprintf(stderr, "Failure generating strings\n");
382 return false;
383 }
384
385 beg = find_line_starting_with(buf, this_sub_begin, bufend);
386 end = find_line_starting_with(buf, this_sub_result, bufend);
387 startline = beg;
388
389 free(this_sub_begin);
390 free(this_sub_result);
391
392 if (beg == NULL && end == NULL) {
393 /* No output at all */
394 beg = bufend;
395 end = bufend;
396 }
397
398 if (beg == NULL) {
399 /*
400 * Subtest didn't start, probably skipped from
401 * fixture already. Start from the result
402 * line, it gets adjusted below.
403 */
404 beg = end;
405 }
406
407 /* Include the output after the previous subtest output */
408 beg = find_line_after_last(buf,
409 STARTING_SUBTEST,
410 SUBTEST_RESULT,
411 beg);
412
413 if (end == NULL) {
414 /* Incomplete result. Find the next starting subtest or result. */
415 end = next_line(startline, bufend);
416 if (end != NULL) {
417 end = find_line_starting_with_either(end,
418 STARTING_SUBTEST,
419 SUBTEST_RESULT,
420 bufend);
421 }
422 if (end == NULL) {
423 end = bufend;
424 }
425 } else {
426 /*
427 * Now pointing to the line where this sub's
428 * result is. We need to include that of
429 * course.
430 */
431 char *nexttest = next_line(end, bufend);
432
433 /* Stretch onwards until the next subtest begins or ends */
434 if (nexttest != NULL) {
435 nexttest = find_line_starting_with_either(nexttest,
436 STARTING_SUBTEST,
437 SUBTEST_RESULT,
438 bufend);
439 }
440 if (nexttest != NULL) {
441 end = nexttest;
442 } else {
443 end = bufend;
444 }
445 }
446
447 json_object_object_add(current_test, key,
448 json_object_new_string_len(beg, end - beg));
449
450 if (igt_version) {
451 json_object_object_add(current_test, "igt-version",
452 json_object_new_string_len(igt_version,
453 igt_version_len));
454 }
455
456 if (!json_object_object_get_ex(current_test, "result", NULL)) {
457 parse_subtest_result(subtests->names[i], &resulttext, &time, beg, end);
458 set_result(current_test, resulttext);
459 set_runtime(current_test, time);
460 }
461 }
462
463 return true;
464 }
465
466 /*
467 * This regexp controls the kmsg handling. All kernel log records that
468 * have log level of warning or higher convert the result to
469 * dmesg-warn/dmesg-fail unless they match this regexp.
470 *
471 * TODO: Move this to external files, i915-suppressions.txt,
472 * general-suppressions.txt et al.
473 */
474
475 #define _ "|"
476 static const char igt_dmesg_whitelist[] =
477 "ACPI: button: The lid device is not compliant to SW_LID" _
478 "ACPI: .*: Unable to dock!" _
479 "IRQ [0-9]+: no longer affine to CPU[0-9]+" _
480 "IRQ fixup: irq [0-9]+ move in progress, old vector [0-9]+" _
481 /* i915 tests set module options, expected message */
482 "Setting dangerous option [a-z_]+ - tainting kernel" _
483 /* Raw printk() call, uses default log level (warn) */
484 "Suspending console\\(s\\) \\(use no_console_suspend to debug\\)" _
485 "atkbd serio[0-9]+: Failed to (deactivate|enable) keyboard on isa[0-9]+/serio[0-9]+" _
486 "cache: parent cpu[0-9]+ should not be sleeping" _
487 "hpet[0-9]+: lost [0-9]+ rtc interrupts" _
488 /* i915 selftests terminate normally with ENODEV from the
489 * module load after the testing finishes, which produces this
490 * message.
491 */
492 "i915: probe of [0-9:.]+ failed with error -25" _
493 /* swiotbl warns even when asked not to */
494 "mock: DMA: Out of SW-IOMMU space for [0-9]+ bytes" _
495 "usb usb[0-9]+: root hub lost power or was reset"
496 ;
497 #undef _
498
499 static const char igt_piglit_style_dmesg_blacklist[] =
500 "(\\[drm:|drm_|intel_|i915_)";
501
init_regex_whitelist(struct settings * settings,GRegex ** re)502 static bool init_regex_whitelist(struct settings* settings, GRegex **re)
503 {
504 GError *err = NULL;
505 const char *regex = settings->piglit_style_dmesg ?
506 igt_piglit_style_dmesg_blacklist :
507 igt_dmesg_whitelist;
508
509 *re = g_regex_new(regex, G_REGEX_OPTIMIZE, 0, &err);
510 if (err) {
511 fprintf(stderr, "Cannot compile dmesg regexp\n");
512 g_error_free(err);
513 return false;
514 }
515
516 return true;
517 }
518
parse_dmesg_line(char * line,unsigned * flags,unsigned long long * ts_usec,char * continuation,char ** message)519 static bool parse_dmesg_line(char* line,
520 unsigned *flags, unsigned long long *ts_usec,
521 char *continuation, char **message)
522 {
523 unsigned long long seq;
524 int s;
525
526 s = sscanf(line, "%u,%llu,%llu,%c;", flags, &seq, ts_usec, continuation);
527 if (s != 4) {
528 /*
529 * Machine readable key/value pairs begin with
530 * a space. We ignore them.
531 */
532 if (line[0] != ' ') {
533 fprintf(stderr, "Cannot parse kmsg record: %s\n", line);
534 }
535 return false;
536 }
537
538 *message = strchr(line, ';');
539 if (!message) {
540 fprintf(stderr, "No ; found in kmsg record, this shouldn't happen\n");
541 return false;
542 }
543 (*message)++;
544
545 return true;
546 }
547
generate_formatted_dmesg_line(char * message,unsigned flags,unsigned long long ts_usec,char ** formatted)548 static void generate_formatted_dmesg_line(char *message,
549 unsigned flags,
550 unsigned long long ts_usec,
551 char **formatted)
552 {
553 char prefix[512];
554 size_t messagelen;
555 size_t prefixlen;
556 char *p, *f;
557
558 snprintf(prefix, sizeof(prefix),
559 "<%u> [%llu.%06llu] ",
560 flags & 0x07,
561 ts_usec / 1000000,
562 ts_usec % 1000000);
563
564 messagelen = strlen(message);
565 prefixlen = strlen(prefix);
566
567 /*
568 * Decoding the hex escapes only makes the string shorter, so
569 * we can use the original length
570 */
571 *formatted = malloc(strlen(prefix) + messagelen + 1);
572 strcpy(*formatted, prefix);
573
574 f = *formatted + prefixlen;
575 for (p = message; *p; p++, f++) {
576 if (p - message + 4 < messagelen &&
577 p[0] == '\\' && p[1] == 'x') {
578 int c = 0;
579 /* newline and tab are not isprint(), but they are isspace() */
580 if (sscanf(p, "\\x%2x", &c) == 1 &&
581 (isprint(c) || isspace(c))) {
582 *f = c;
583 p += 3;
584 continue;
585 }
586 }
587 *f = *p;
588 }
589 *f = '\0';
590 }
591
add_dmesg(struct json_object * obj,const char * dmesg,size_t dmesglen,const char * warnings,size_t warningslen)592 static void add_dmesg(struct json_object *obj,
593 const char *dmesg, size_t dmesglen,
594 const char *warnings, size_t warningslen)
595 {
596 json_object_object_add(obj, "dmesg",
597 json_object_new_string_len(dmesg, dmesglen));
598
599 if (warnings) {
600 json_object_object_add(obj, "dmesg-warnings",
601 json_object_new_string_len(warnings, warningslen));
602 }
603 }
604
add_empty_dmesgs_where_missing(struct json_object * tests,char * binary,struct subtests * subtests)605 static void add_empty_dmesgs_where_missing(struct json_object *tests,
606 char *binary,
607 struct subtests *subtests)
608 {
609 struct json_object *current_test;
610 char piglit_name[256];
611 size_t i;
612
613 for (i = 0; i < subtests->size; i++) {
614 generate_piglit_name(binary, subtests->names[i], piglit_name, sizeof(piglit_name));
615 current_test = get_or_create_json_object(tests, piglit_name);
616 if (!json_object_object_get_ex(current_test, "dmesg", NULL)) {
617 add_dmesg(current_test, "", 0, NULL, 0);
618 }
619 }
620
621 }
622
fill_from_dmesg(int fd,struct settings * settings,char * binary,struct subtests * subtests,struct json_object * tests)623 static bool fill_from_dmesg(int fd,
624 struct settings *settings,
625 char *binary,
626 struct subtests *subtests,
627 struct json_object *tests)
628 {
629 char *line = NULL, *warnings = NULL, *dmesg = NULL;
630 size_t linelen = 0, warningslen = 0, dmesglen = 0;
631 struct json_object *current_test = NULL;
632 FILE *f = fdopen(fd, "r");
633 char piglit_name[256];
634 ssize_t read;
635 size_t i;
636 GRegex *re;
637
638 if (!f) {
639 return false;
640 }
641
642 if (!init_regex_whitelist(settings, &re)) {
643 fclose(f);
644 return false;
645 }
646
647 while ((read = getline(&line, &linelen, f)) > 0) {
648 char *formatted;
649 unsigned flags;
650 unsigned long long ts_usec;
651 char continuation;
652 char *message, *subtest;
653
654 if (!parse_dmesg_line(line, &flags, &ts_usec, &continuation, &message))
655 continue;
656
657 generate_formatted_dmesg_line(message, flags, ts_usec, &formatted);
658
659 if ((subtest = strstr(message, STARTING_SUBTEST_DMESG)) != NULL) {
660 if (current_test != NULL) {
661 /* Done with the previous subtest, file up */
662 add_dmesg(current_test, dmesg, dmesglen, warnings, warningslen);
663
664 free(dmesg);
665 free(warnings);
666 dmesg = warnings = NULL;
667 dmesglen = warningslen = 0;
668 }
669
670 subtest += strlen(STARTING_SUBTEST_DMESG);
671 generate_piglit_name(binary, subtest, piglit_name, sizeof(piglit_name));
672 current_test = get_or_create_json_object(tests, piglit_name);
673 }
674
675 if (settings->piglit_style_dmesg) {
676 if ((flags & 0x07) <= settings->dmesg_warn_level && continuation != 'c' &&
677 g_regex_match(re, message, 0, NULL)) {
678 append_line(&warnings, &warningslen, formatted);
679 }
680 } else {
681 if ((flags & 0x07) <= settings->dmesg_warn_level && continuation != 'c' &&
682 !g_regex_match(re, message, 0, NULL)) {
683 append_line(&warnings, &warningslen, formatted);
684 }
685 }
686 append_line(&dmesg, &dmesglen, formatted);
687 free(formatted);
688 }
689 free(line);
690
691 if (current_test != NULL) {
692 add_dmesg(current_test, dmesg, dmesglen, warnings, warningslen);
693 } else {
694 /*
695 * Didn't get any subtest messages at all. If there
696 * are subtests, add all of the dmesg gotten to all of
697 * them.
698 */
699 for (i = 0; i < subtests->size; i++) {
700 generate_piglit_name(binary, subtests->names[i], piglit_name, sizeof(piglit_name));
701 current_test = get_or_create_json_object(tests, piglit_name);
702 /*
703 * Don't bother with warnings, any subtests
704 * there are would have skip as their result
705 * anyway.
706 */
707 add_dmesg(current_test, dmesg, dmesglen, NULL, 0);
708 }
709
710 if (subtests->size == 0) {
711 generate_piglit_name(binary, NULL, piglit_name, sizeof(piglit_name));
712 current_test = get_or_create_json_object(tests, piglit_name);
713 add_dmesg(current_test, dmesg, dmesglen, warnings, warningslen);
714 }
715 }
716
717 add_empty_dmesgs_where_missing(tests, binary, subtests);
718
719 free(dmesg);
720 free(warnings);
721 g_regex_unref(re);
722 fclose(f);
723 return true;
724 }
725
result_from_exitcode(int exitcode)726 static const char *result_from_exitcode(int exitcode)
727 {
728 switch (exitcode) {
729 case IGT_EXIT_SKIP:
730 return "skip";
731 case IGT_EXIT_SUCCESS:
732 return "pass";
733 case IGT_EXIT_INVALID:
734 return "notrun";
735 case INCOMPLETE_EXITCODE:
736 return "incomplete";
737 default:
738 return "fail";
739 }
740 }
741
add_subtest(struct subtests * subtests,char * subtest)742 static void add_subtest(struct subtests *subtests, char *subtest)
743 {
744 size_t len = strlen(subtest);
745 size_t i;
746
747 if (len == 0)
748 return;
749
750 if (subtest[len - 1] == '\n')
751 subtest[len - 1] = '\0';
752
753 /* Don't add if we already have this subtest */
754 for (i = 0; i < subtests->size; i++)
755 if (!strcmp(subtest, subtests->names[i]))
756 return;
757
758 subtests->size++;
759 subtests->names = realloc(subtests->names, sizeof(*subtests->names) * subtests->size);
760 subtests->names[subtests->size - 1] = subtest;
761 }
762
free_subtests(struct subtests * subtests)763 static void free_subtests(struct subtests *subtests)
764 {
765 size_t i;
766
767 for (i = 0; i < subtests->size; i++)
768 free(subtests->names[i]);
769 free(subtests->names);
770 }
771
fill_from_journal(int fd,struct job_list_entry * entry,struct subtests * subtests,struct results * results)772 static void fill_from_journal(int fd,
773 struct job_list_entry *entry,
774 struct subtests *subtests,
775 struct results *results)
776 {
777 FILE *f = fdopen(fd, "r");
778 char *line = NULL;
779 size_t linelen = 0;
780 ssize_t read;
781 char exitline[] = "exit:";
782 char timeoutline[] = "timeout:";
783 int exitcode = INCOMPLETE_EXITCODE;
784 bool has_timeout = false;
785 struct json_object *tests = results->tests;
786 struct json_object *runtimes = results->runtimes;
787
788 while ((read = getline(&line, &linelen, f)) > 0) {
789 if (read >= strlen(exitline) && !memcmp(line, exitline, strlen(exitline))) {
790 char *p = strchr(line, '(');
791 char piglit_name[256];
792 double time = 0.0;
793 struct json_object *obj;
794
795 exitcode = atoi(line + strlen(exitline));
796
797 if (p)
798 time = strtod(p + 1, NULL);
799
800 generate_piglit_name(entry->binary, NULL, piglit_name, sizeof(piglit_name));
801 obj = get_or_create_json_object(runtimes, piglit_name);
802 add_runtime(obj, time);
803
804 /* If no subtests, the test result node also gets the runtime */
805 if (subtests->size == 0 && entry->subtest_count == 0) {
806 obj = get_or_create_json_object(tests, piglit_name);
807 add_runtime(obj, time);
808 }
809 } else if (read >= strlen(timeoutline) && !memcmp(line, timeoutline, strlen(timeoutline))) {
810 has_timeout = true;
811
812 if (subtests->size) {
813 /* Assign the timeout to the previously appeared subtest */
814 char *last_subtest = subtests->names[subtests->size - 1];
815 char piglit_name[256];
816 char *p = strchr(line, '(');
817 double time = 0.0;
818 struct json_object *obj;
819
820 generate_piglit_name(entry->binary, last_subtest, piglit_name, sizeof(piglit_name));
821 obj = get_or_create_json_object(tests, piglit_name);
822
823 set_result(obj, "timeout");
824
825 if (p)
826 time = strtod(p + 1, NULL);
827
828 /* Add runtime for the subtest... */
829 add_runtime(obj, time);
830
831 /* ... and also for the binary */
832 generate_piglit_name(entry->binary, NULL, piglit_name, sizeof(piglit_name));
833 obj = get_or_create_json_object(runtimes, piglit_name);
834 add_runtime(obj, time);
835 }
836 } else {
837 add_subtest(subtests, strdup(line));
838 }
839 }
840
841 if (subtests->size == 0) {
842 char *subtestname = NULL;
843 char piglit_name[256];
844 struct json_object *obj;
845 const char *result = has_timeout ? "timeout" : result_from_exitcode(exitcode);
846
847 /*
848 * If the test was killed before it printed that it's
849 * entering a subtest, we would incorrectly generate
850 * results as the binary had no subtests. If we know
851 * otherwise, do otherwise.
852 */
853 if (entry->subtest_count > 0) {
854 subtestname = entry->subtests[0];
855 add_subtest(subtests, strdup(subtestname));
856 }
857
858 generate_piglit_name(entry->binary, subtestname, piglit_name, sizeof(piglit_name));
859 obj = get_or_create_json_object(tests, piglit_name);
860 set_result(obj, result);
861 }
862
863 free(line);
864 fclose(f);
865 }
866
override_result_single(struct json_object * obj)867 static void override_result_single(struct json_object *obj)
868 {
869 const char *errtext = "", *result = "";
870 struct json_object *textobj;
871 bool dmesgwarns = false;
872
873 if (json_object_object_get_ex(obj, "err", &textobj))
874 errtext = json_object_get_string(textobj);
875 if (json_object_object_get_ex(obj, "result", &textobj))
876 result = json_object_get_string(textobj);
877 if (json_object_object_get_ex(obj, "dmesg-warnings", &textobj))
878 dmesgwarns = true;
879
880 if (!strcmp(result, "pass") &&
881 count_lines(errtext, errtext + strlen(errtext)) > 2) {
882 set_result(obj, "warn");
883 result = "warn";
884 }
885
886 if (dmesgwarns) {
887 if (!strcmp(result, "pass") || !strcmp(result, "warn")) {
888 set_result(obj, "dmesg-warn");
889 } else if (!strcmp(result, "fail")) {
890 set_result(obj, "dmesg-fail");
891 }
892 }
893 }
894
override_results(char * binary,struct subtests * subtests,struct json_object * tests)895 static void override_results(char *binary,
896 struct subtests *subtests,
897 struct json_object *tests)
898 {
899 struct json_object *obj;
900 char piglit_name[256];
901 size_t i;
902
903 if (subtests->size == 0) {
904 generate_piglit_name(binary, NULL, piglit_name, sizeof(piglit_name));
905 obj = get_or_create_json_object(tests, piglit_name);
906 override_result_single(obj);
907 return;
908 }
909
910 for (i = 0; i < subtests->size; i++) {
911 generate_piglit_name(binary, subtests->names[i], piglit_name, sizeof(piglit_name));
912 obj = get_or_create_json_object(tests, piglit_name);
913 override_result_single(obj);
914 }
915 }
916
get_totals_object(struct json_object * totals,const char * key)917 static struct json_object *get_totals_object(struct json_object *totals,
918 const char *key)
919 {
920 struct json_object *obj = NULL;
921
922 if (json_object_object_get_ex(totals, key, &obj))
923 return obj;
924
925 obj = json_object_new_object();
926 json_object_object_add(totals, key, obj);
927
928 json_object_object_add(obj, "crash", json_object_new_int(0));
929 json_object_object_add(obj, "pass", json_object_new_int(0));
930 json_object_object_add(obj, "dmesg-fail", json_object_new_int(0));
931 json_object_object_add(obj, "dmesg-warn", json_object_new_int(0));
932 json_object_object_add(obj, "skip", json_object_new_int(0));
933 json_object_object_add(obj, "incomplete", json_object_new_int(0));
934 json_object_object_add(obj, "timeout", json_object_new_int(0));
935 json_object_object_add(obj, "notrun", json_object_new_int(0));
936 json_object_object_add(obj, "fail", json_object_new_int(0));
937 json_object_object_add(obj, "warn", json_object_new_int(0));
938
939 return obj;
940 }
941
add_result_to_totals(struct json_object * totals,const char * result)942 static void add_result_to_totals(struct json_object *totals,
943 const char *result)
944 {
945 json_object *numobj = NULL;
946 int old;
947
948 if (!json_object_object_get_ex(totals, result, &numobj)) {
949 fprintf(stderr, "Warning: Totals object without count for %s\n", result);
950 return;
951 }
952
953 old = json_object_get_int(numobj);
954 json_object_object_add(totals, result, json_object_new_int(old + 1));
955 }
956
add_to_totals(const char * binary,struct subtests * subtests,struct results * results)957 static void add_to_totals(const char *binary,
958 struct subtests *subtests,
959 struct results *results)
960 {
961 struct json_object *test, *resultobj, *emptystrtotal, *roottotal, *binarytotal;
962 char piglit_name[256];
963 const char *result;
964 size_t i;
965
966 generate_piglit_name(binary, NULL, piglit_name, sizeof(piglit_name));
967 emptystrtotal = get_totals_object(results->totals, "");
968 roottotal = get_totals_object(results->totals, "root");
969 binarytotal = get_totals_object(results->totals, piglit_name);
970
971 if (subtests->size == 0) {
972 test = get_or_create_json_object(results->tests, piglit_name);
973 if (!json_object_object_get_ex(test, "result", &resultobj)) {
974 fprintf(stderr, "Warning: No results set for %s\n", piglit_name);
975 return;
976 }
977 result = json_object_get_string(resultobj);
978 add_result_to_totals(emptystrtotal, result);
979 add_result_to_totals(roottotal, result);
980 add_result_to_totals(binarytotal, result);
981 return;
982 }
983
984 for (i = 0; i < subtests->size; i++) {
985 generate_piglit_name(binary, subtests->names[i], piglit_name, sizeof(piglit_name));
986 test = get_or_create_json_object(results->tests, piglit_name);
987 if (!json_object_object_get_ex(test, "result", &resultobj)) {
988 fprintf(stderr, "Warning: No results set for %s\n", piglit_name);
989 return;
990 }
991 result = json_object_get_string(resultobj);
992 add_result_to_totals(emptystrtotal, result);
993 add_result_to_totals(roottotal, result);
994 add_result_to_totals(binarytotal, result);
995 }
996 }
997
parse_test_directory(int dirfd,struct job_list_entry * entry,struct settings * settings,struct results * results)998 static bool parse_test_directory(int dirfd,
999 struct job_list_entry *entry,
1000 struct settings *settings,
1001 struct results *results)
1002 {
1003 int fds[_F_LAST];
1004 struct subtests subtests = {};
1005 bool status = true;
1006
1007 if (!open_output_files(dirfd, fds, false)) {
1008 fprintf(stderr, "Error opening output files\n");
1009 return false;
1010 }
1011
1012 /*
1013 * fill_from_journal fills the subtests struct and adds
1014 * timeout results where applicable.
1015 */
1016 fill_from_journal(fds[_F_JOURNAL], entry, &subtests, results);
1017
1018 if (!fill_from_output(fds[_F_OUT], entry->binary, "out", &subtests, results->tests) ||
1019 !fill_from_output(fds[_F_ERR], entry->binary, "err", &subtests, results->tests) ||
1020 !fill_from_dmesg(fds[_F_DMESG], settings, entry->binary, &subtests, results->tests)) {
1021 fprintf(stderr, "Error parsing output files\n");
1022 status = false;
1023 goto parse_output_end;
1024 }
1025
1026 override_results(entry->binary, &subtests, results->tests);
1027 add_to_totals(entry->binary, &subtests, results);
1028
1029 parse_output_end:
1030 close_outputs(fds);
1031 free_subtests(&subtests);
1032
1033 return status;
1034 }
1035
try_add_notrun_results(const struct job_list_entry * entry,const struct settings * settings,struct results * results)1036 static void try_add_notrun_results(const struct job_list_entry *entry,
1037 const struct settings *settings,
1038 struct results *results)
1039 {
1040 struct subtests subtests = {};
1041 struct json_object *current_test;
1042 size_t i;
1043
1044 if (entry->subtest_count == 0) {
1045 char piglit_name[256];
1046
1047 /* We cannot distinguish no-subtests from run-all-subtests in multiple-mode */
1048 if (settings->multiple_mode)
1049 return;
1050 generate_piglit_name(entry->binary, NULL, piglit_name, sizeof(piglit_name));
1051 current_test = get_or_create_json_object(results->tests, piglit_name);
1052 json_object_object_add(current_test, "out", json_object_new_string(""));
1053 json_object_object_add(current_test, "err", json_object_new_string(""));
1054 json_object_object_add(current_test, "dmesg", json_object_new_string(""));
1055 json_object_object_add(current_test, "result", json_object_new_string("notrun"));
1056 }
1057
1058 for (i = 0; i < entry->subtest_count; i++) {
1059 char piglit_name[256];
1060
1061 generate_piglit_name(entry->binary, entry->subtests[i], piglit_name, sizeof(piglit_name));
1062 current_test = get_or_create_json_object(results->tests, piglit_name);
1063 json_object_object_add(current_test, "out", json_object_new_string(""));
1064 json_object_object_add(current_test, "err", json_object_new_string(""));
1065 json_object_object_add(current_test, "dmesg", json_object_new_string(""));
1066 json_object_object_add(current_test, "result", json_object_new_string("notrun"));
1067 add_subtest(&subtests, strdup(entry->subtests[i]));
1068 }
1069
1070 add_to_totals(entry->binary, &subtests, results);
1071 free_subtests(&subtests);
1072 }
1073
create_result_root_nodes(struct json_object * root,struct results * results)1074 static void create_result_root_nodes(struct json_object *root,
1075 struct results *results)
1076 {
1077 results->tests = json_object_new_object();
1078 json_object_object_add(root, "tests", results->tests);
1079 results->totals = json_object_new_object();
1080 json_object_object_add(root, "totals", results->totals);
1081 results->runtimes = json_object_new_object();
1082 json_object_object_add(root, "runtimes", results->runtimes);
1083 }
1084
generate_results_json(int dirfd)1085 struct json_object *generate_results_json(int dirfd)
1086 {
1087 struct settings settings;
1088 struct job_list job_list;
1089 struct json_object *obj, *elapsed;
1090 struct results results;
1091 int testdirfd, fd;
1092 size_t i;
1093
1094 init_settings(&settings);
1095 init_job_list(&job_list);
1096
1097 if (!read_settings_from_dir(&settings, dirfd)) {
1098 fprintf(stderr, "resultgen: Cannot parse settings\n");
1099 return NULL;
1100 }
1101
1102 if (!read_job_list(&job_list, dirfd)) {
1103 fprintf(stderr, "resultgen: Cannot parse job list\n");
1104 return NULL;
1105 }
1106
1107 obj = json_object_new_object();
1108 json_object_object_add(obj, "__type__", json_object_new_string("TestrunResult"));
1109 json_object_object_add(obj, "results_version", json_object_new_int(10));
1110 json_object_object_add(obj, "name",
1111 settings.name ?
1112 json_object_new_string(settings.name) :
1113 json_object_new_string(""));
1114
1115 if ((fd = openat(dirfd, "uname.txt", O_RDONLY)) >= 0) {
1116 char buf[128];
1117 ssize_t r = read(fd, buf, sizeof(buf));
1118
1119 if (r > 0 && buf[r - 1] == '\n')
1120 r--;
1121
1122 json_object_object_add(obj, "uname",
1123 json_object_new_string_len(buf, r));
1124 close(fd);
1125 }
1126
1127 elapsed = json_object_new_object();
1128 json_object_object_add(elapsed, "__type__", json_object_new_string("TimeAttribute"));
1129 if ((fd = openat(dirfd, "starttime.txt", O_RDONLY)) >= 0) {
1130 char buf[128] = {};
1131 read(fd, buf, sizeof(buf));
1132 json_object_object_add(elapsed, "start", json_object_new_double(atof(buf)));
1133 close(fd);
1134 }
1135 if ((fd = openat(dirfd, "endtime.txt", O_RDONLY)) >= 0) {
1136 char buf[128] = {};
1137 read(fd, buf, sizeof(buf));
1138 json_object_object_add(elapsed, "end", json_object_new_double(atof(buf)));
1139 close(fd);
1140 }
1141 json_object_object_add(obj, "time_elapsed", elapsed);
1142
1143 create_result_root_nodes(obj, &results);
1144
1145 /*
1146 * Result fields that won't be added:
1147 *
1148 * - glxinfo
1149 * - wglinfo
1150 * - clinfo
1151 *
1152 * Result fields that are TODO:
1153 *
1154 * - lspci
1155 * - options
1156 */
1157
1158 for (i = 0; i < job_list.size; i++) {
1159 char name[16];
1160
1161 snprintf(name, 16, "%zd", i);
1162 if ((testdirfd = openat(dirfd, name, O_DIRECTORY | O_RDONLY)) < 0) {
1163 try_add_notrun_results(&job_list.entries[i], &settings, &results);
1164 continue;
1165 }
1166
1167 if (!parse_test_directory(testdirfd, &job_list.entries[i], &settings, &results)) {
1168 close(testdirfd);
1169 return NULL;
1170 }
1171 close(testdirfd);
1172 }
1173
1174 if ((fd = openat(dirfd, "aborted.txt", O_RDONLY)) >= 0) {
1175 char buf[4096];
1176 char piglit_name[] = "igt@runner@aborted";
1177 struct subtests abortsub = {};
1178 struct json_object *aborttest = get_or_create_json_object(results.tests, piglit_name);
1179 ssize_t s;
1180
1181 add_subtest(&abortsub, strdup("aborted"));
1182
1183 s = read(fd, buf, sizeof(buf));
1184
1185 json_object_object_add(aborttest, "out",
1186 json_object_new_string_len(buf, s));
1187 json_object_object_add(aborttest, "err",
1188 json_object_new_string(""));
1189 json_object_object_add(aborttest, "dmesg",
1190 json_object_new_string(""));
1191 json_object_object_add(aborttest, "result",
1192 json_object_new_string("fail"));
1193
1194 add_to_totals("runner", &abortsub, &results);
1195
1196 free_subtests(&abortsub);
1197 }
1198
1199 free_settings(&settings);
1200 free_job_list(&job_list);
1201
1202 return obj;
1203 }
1204
generate_results(int dirfd)1205 bool generate_results(int dirfd)
1206 {
1207 struct json_object *obj = generate_results_json(dirfd);
1208 const char *json_string;
1209 int resultsfd;
1210
1211 if (obj == NULL)
1212 return false;
1213
1214 /* TODO: settings.overwrite */
1215 if ((resultsfd = openat(dirfd, "results.json", O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) {
1216 fprintf(stderr, "resultgen: Cannot create results file\n");
1217 return false;
1218 }
1219
1220 json_string = json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PRETTY);
1221 write(resultsfd, json_string, strlen(json_string));
1222 close(resultsfd);
1223 return true;
1224 }
1225
generate_results_path(char * resultspath)1226 bool generate_results_path(char *resultspath)
1227 {
1228 int dirfd = open(resultspath, O_DIRECTORY | O_RDONLY);
1229
1230 if (dirfd < 0)
1231 return false;
1232
1233 return generate_results(dirfd);
1234 }
1235