• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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