#include #include #include #include #include #include #include #include #include #include #include "igt_core.h" #include "resultgen.h" #include "settings.h" #include "executor.h" #include "output_strings.h" #define INCOMPLETE_EXITCODE -1 _Static_assert(INCOMPLETE_EXITCODE != IGT_EXIT_SKIP, "exit code clash"); _Static_assert(INCOMPLETE_EXITCODE != IGT_EXIT_SUCCESS, "exit code clash"); _Static_assert(INCOMPLETE_EXITCODE != IGT_EXIT_INVALID, "exit code clash"); struct subtests { char **names; size_t size; }; struct results { struct json_object *tests; struct json_object *totals; struct json_object *runtimes; }; /* * A lot of string handling here operates on an mmapped buffer, and * thus we can't assume null-terminated strings. Buffers will be * passed around as pointer+size, or pointer+pointer-past-the-end, the * mem*() family of functions is used instead of str*(). */ static char *find_line_starting_with(char *haystack, const char *needle, char *end) { while (haystack < end) { char *line_end = memchr(haystack, '\n', end - haystack); if (end - haystack < strlen(needle)) return NULL; if (!memcmp(haystack, needle, strlen(needle))) return haystack; if (line_end == NULL) return NULL; haystack = line_end + 1; } return NULL; } static char *find_line_starting_with_either(char *haystack, const char *needle1, const char *needle2, char *end) { while (haystack < end) { char *line_end = memchr(haystack, '\n', end - haystack); size_t linelen = line_end != NULL ? line_end - haystack : end - haystack; if ((linelen >= strlen(needle1) && !memcmp(haystack, needle1, strlen(needle1))) || (linelen >= strlen(needle2) && !memcmp(haystack, needle2, strlen(needle2)))) return haystack; if (line_end == NULL) return NULL; haystack = line_end + 1; } return NULL; } static char *next_line(char *line, char *bufend) { char *ret; if (!line) return NULL; ret = memchr(line, '\n', bufend - line); if (ret) ret++; if (ret < bufend) return ret; else return NULL; } static char *find_line_after_last(char *begin, const char *needle1, const char *needle2, char *end) { char *one, *two; char *current_pos = begin; char *needle1_newline = malloc(strlen(needle1) + 2); char *needle2_newline = malloc(strlen(needle2) + 2); needle1_newline[0] = needle2_newline[0] = '\n'; strcpy(needle1_newline + 1, needle1); strcpy(needle2_newline + 1, needle2); while (true) { one = memmem(current_pos, end - current_pos, needle1_newline, strlen(needle1_newline)); two = memmem(current_pos, end - current_pos, needle2_newline, strlen(needle2_newline)); if (one == NULL && two == NULL) break; if (one != NULL && current_pos < one) current_pos = one; if (two != NULL && current_pos < two) current_pos = two; one = next_line(current_pos, end); if (one != NULL) current_pos = one; } free(needle1_newline); free(needle2_newline); one = memchr(current_pos, '\n', end - current_pos); if (one != NULL) return ++one; return current_pos; } static size_t count_lines(const char *buf, const char *bufend) { size_t ret = 0; while (buf < bufend && (buf = memchr(buf, '\n', bufend - buf)) != NULL) { ret++; buf++; } return ret; } static void append_line(char **buf, size_t *buflen, char *line) { size_t linelen = strlen(line); *buf = realloc(*buf, *buflen + linelen + 1); strcpy(*buf + *buflen, line); *buflen += linelen; } static const struct { const char *output_str; const char *result_str; } resultmap[] = { { "SUCCESS", "pass" }, { "SKIP", "skip" }, { "FAIL", "fail" }, { "CRASH", "crash" }, { "TIMEOUT", "timeout" }, }; static void parse_result_string(char *resultstring, size_t len, const char **result, double *time) { size_t i; size_t wordlen = 0; while (wordlen < len && !isspace(resultstring[wordlen])) { wordlen++; } *result = NULL; for (i = 0; i < (sizeof(resultmap) / sizeof(resultmap[0])); i++) { if (!strncmp(resultstring, resultmap[i].output_str, wordlen)) { *result = resultmap[i].result_str; break; } } /* If the result string is unknown, use incomplete */ if (!*result) *result = "incomplete"; /* * Check for subtest runtime after the result. The string is * '(' followed by the runtime in seconds as floating point, * followed by 's)'. */ wordlen++; if (wordlen < len && resultstring[wordlen] == '(') { char *dup; wordlen++; dup = malloc(len - wordlen + 1); memcpy(dup, resultstring + wordlen, len - wordlen); dup[len - wordlen] = '\0'; *time = strtod(dup, NULL); free(dup); } } static void parse_subtest_result(char *subtest, const char **result, double *time, char *buf, char *bufend) { char *line; char *line_end; char *resultstring; size_t linelen; size_t subtestlen = strlen(subtest); *result = NULL; *time = 0.0; if (!buf) return; /* * The result line structure is: * * - The string "Subtest " (`SUBTEST_RESULT` from output_strings.h) * - The subtest name * - The characters ':' and ' ' * - Subtest result string * - Optional: * -- The characters ' ' and '(' * -- Subtest runtime in seconds as floating point * -- The characters 's' and ')' * * Example: * Subtest subtestname: PASS (0.003s) */ line = find_line_starting_with(buf, SUBTEST_RESULT, bufend); if (!line) { *result = "incomplete"; return; } line_end = memchr(line, '\n', bufend - line); linelen = line_end != NULL ? line_end - line : bufend - line; if (strlen(SUBTEST_RESULT) + subtestlen + strlen(": ") > linelen || strncmp(line + strlen(SUBTEST_RESULT), subtest, subtestlen)) return parse_subtest_result(subtest, result, time, line + linelen, bufend); resultstring = line + strlen(SUBTEST_RESULT) + subtestlen + strlen(": "); parse_result_string(resultstring, linelen - (resultstring - line), result, time); } static struct json_object *get_or_create_json_object(struct json_object *base, const char *key) { struct json_object *ret; if (json_object_object_get_ex(base, key, &ret)) return ret; ret = json_object_new_object(); json_object_object_add(base, key, ret); return ret; } static void set_result(struct json_object *obj, const char *result) { json_object_object_add(obj, "result", json_object_new_string(result)); } static void add_runtime(struct json_object *obj, double time) { double oldtime; struct json_object *timeobj = get_or_create_json_object(obj, "time"); struct json_object *oldend; json_object_object_add(timeobj, "__type__", json_object_new_string("TimeAttribute")); json_object_object_add(timeobj, "start", json_object_new_double(0.0)); if (!json_object_object_get_ex(timeobj, "end", &oldend)) { json_object_object_add(timeobj, "end", json_object_new_double(time)); return; } /* Add the runtime to the existing runtime. */ oldtime = json_object_get_double(oldend); time += oldtime; json_object_object_add(timeobj, "end", json_object_new_double(time)); } static void set_runtime(struct json_object *obj, double time) { struct json_object *timeobj = get_or_create_json_object(obj, "time"); json_object_object_add(timeobj, "__type__", json_object_new_string("TimeAttribute")); json_object_object_add(timeobj, "start", json_object_new_double(0.0)); json_object_object_add(timeobj, "end", json_object_new_double(time)); } static bool fill_from_output(int fd, const char *binary, const char *key, struct subtests *subtests, struct json_object *tests) { char *buf, *bufend, *nullchr; struct stat statbuf; char piglit_name[256]; char *igt_version = NULL; size_t igt_version_len = 0; struct json_object *current_test = NULL; size_t i; if (fstat(fd, &statbuf)) return false; if (statbuf.st_size != 0) { buf = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); if (buf == MAP_FAILED) return false; } else { buf = NULL; } /* * Avoid null characters: Just pretend the output stops at the * first such character, if any. */ if ((nullchr = memchr(buf, '\0', statbuf.st_size)) != NULL) { statbuf.st_size = nullchr - buf; } bufend = buf + statbuf.st_size; igt_version = find_line_starting_with(buf, IGT_VERSIONSTRING, bufend); if (igt_version) { char *newline = memchr(igt_version, '\n', bufend - igt_version); igt_version_len = newline - igt_version; } /* TODO: Refactor to helper functions */ if (subtests->size == 0) { /* No subtests */ generate_piglit_name(binary, NULL, piglit_name, sizeof(piglit_name)); current_test = get_or_create_json_object(tests, piglit_name); json_object_object_add(current_test, key, json_object_new_string_len(buf, statbuf.st_size)); if (igt_version) json_object_object_add(current_test, "igt-version", json_object_new_string_len(igt_version, igt_version_len)); return true; } for (i = 0; i < subtests->size; i++) { char *this_sub_begin, *this_sub_result; const char *resulttext; char *beg, *end, *startline; double time; int begin_len; int result_len; generate_piglit_name(binary, subtests->names[i], piglit_name, sizeof(piglit_name)); current_test = get_or_create_json_object(tests, piglit_name); begin_len = asprintf(&this_sub_begin, "%s%s\n", STARTING_SUBTEST, subtests->names[i]); result_len = asprintf(&this_sub_result, "%s%s: ", SUBTEST_RESULT, subtests->names[i]); if (begin_len < 0 || result_len < 0) { fprintf(stderr, "Failure generating strings\n"); return false; } beg = find_line_starting_with(buf, this_sub_begin, bufend); end = find_line_starting_with(buf, this_sub_result, bufend); startline = beg; free(this_sub_begin); free(this_sub_result); if (beg == NULL && end == NULL) { /* No output at all */ beg = bufend; end = bufend; } if (beg == NULL) { /* * Subtest didn't start, probably skipped from * fixture already. Start from the result * line, it gets adjusted below. */ beg = end; } /* Include the output after the previous subtest output */ beg = find_line_after_last(buf, STARTING_SUBTEST, SUBTEST_RESULT, beg); if (end == NULL) { /* Incomplete result. Find the next starting subtest or result. */ end = next_line(startline, bufend); if (end != NULL) { end = find_line_starting_with_either(end, STARTING_SUBTEST, SUBTEST_RESULT, bufend); } if (end == NULL) { end = bufend; } } else { /* * Now pointing to the line where this sub's * result is. We need to include that of * course. */ char *nexttest = next_line(end, bufend); /* Stretch onwards until the next subtest begins or ends */ if (nexttest != NULL) { nexttest = find_line_starting_with_either(nexttest, STARTING_SUBTEST, SUBTEST_RESULT, bufend); } if (nexttest != NULL) { end = nexttest; } else { end = bufend; } } json_object_object_add(current_test, key, json_object_new_string_len(beg, end - beg)); if (igt_version) { json_object_object_add(current_test, "igt-version", json_object_new_string_len(igt_version, igt_version_len)); } if (!json_object_object_get_ex(current_test, "result", NULL)) { parse_subtest_result(subtests->names[i], &resulttext, &time, beg, end); set_result(current_test, resulttext); set_runtime(current_test, time); } } return true; } /* * This regexp controls the kmsg handling. All kernel log records that * have log level of warning or higher convert the result to * dmesg-warn/dmesg-fail unless they match this regexp. * * TODO: Move this to external files, i915-suppressions.txt, * general-suppressions.txt et al. */ #define _ "|" static const char igt_dmesg_whitelist[] = "ACPI: button: The lid device is not compliant to SW_LID" _ "ACPI: .*: Unable to dock!" _ "IRQ [0-9]+: no longer affine to CPU[0-9]+" _ "IRQ fixup: irq [0-9]+ move in progress, old vector [0-9]+" _ /* i915 tests set module options, expected message */ "Setting dangerous option [a-z_]+ - tainting kernel" _ /* Raw printk() call, uses default log level (warn) */ "Suspending console\\(s\\) \\(use no_console_suspend to debug\\)" _ "atkbd serio[0-9]+: Failed to (deactivate|enable) keyboard on isa[0-9]+/serio[0-9]+" _ "cache: parent cpu[0-9]+ should not be sleeping" _ "hpet[0-9]+: lost [0-9]+ rtc interrupts" _ /* i915 selftests terminate normally with ENODEV from the * module load after the testing finishes, which produces this * message. */ "i915: probe of [0-9:.]+ failed with error -25" _ /* swiotbl warns even when asked not to */ "mock: DMA: Out of SW-IOMMU space for [0-9]+ bytes" _ "usb usb[0-9]+: root hub lost power or was reset" ; #undef _ static const char igt_piglit_style_dmesg_blacklist[] = "(\\[drm:|drm_|intel_|i915_)"; static bool init_regex_whitelist(struct settings* settings, regex_t* re) { const char *regex = settings->piglit_style_dmesg ? igt_piglit_style_dmesg_blacklist : igt_dmesg_whitelist; if (regcomp(re, regex, REG_EXTENDED | REG_NOSUB) != 0) { fprintf(stderr, "Cannot compile dmesg regexp\n"); return false; } return true; } static bool parse_dmesg_line(char* line, unsigned *flags, unsigned long long *ts_usec, char *continuation, char **message) { unsigned long long seq; int s; s = sscanf(line, "%u,%llu,%llu,%c;", flags, &seq, ts_usec, continuation); if (s != 4) { /* * Machine readable key/value pairs begin with * a space. We ignore them. */ if (line[0] != ' ') { fprintf(stderr, "Cannot parse kmsg record: %s\n", line); } return false; } *message = strchr(line, ';'); if (!message) { fprintf(stderr, "No ; found in kmsg record, this shouldn't happen\n"); return false; } (*message)++; return true; } static void generate_formatted_dmesg_line(char *message, unsigned flags, unsigned long long ts_usec, char **formatted) { char prefix[512]; size_t messagelen; size_t prefixlen; char *p, *f; snprintf(prefix, sizeof(prefix), "<%u> [%llu.%06llu] ", flags & 0x07, ts_usec / 1000000, ts_usec % 1000000); messagelen = strlen(message); prefixlen = strlen(prefix); /* * Decoding the hex escapes only makes the string shorter, so * we can use the original length */ *formatted = malloc(strlen(prefix) + messagelen + 1); strcpy(*formatted, prefix); f = *formatted + prefixlen; for (p = message; *p; p++, f++) { if (p - message + 4 < messagelen && p[0] == '\\' && p[1] == 'x') { int c = 0; /* newline and tab are not isprint(), but they are isspace() */ if (sscanf(p, "\\x%2x", &c) == 1 && (isprint(c) || isspace(c))) { *f = c; p += 3; continue; } } *f = *p; } *f = '\0'; } static void add_dmesg(struct json_object *obj, const char *dmesg, size_t dmesglen, const char *warnings, size_t warningslen) { json_object_object_add(obj, "dmesg", json_object_new_string_len(dmesg, dmesglen)); if (warnings) { json_object_object_add(obj, "dmesg-warnings", json_object_new_string_len(warnings, warningslen)); } } static void add_empty_dmesgs_where_missing(struct json_object *tests, char *binary, struct subtests *subtests) { struct json_object *current_test; char piglit_name[256]; size_t i; for (i = 0; i < subtests->size; i++) { generate_piglit_name(binary, subtests->names[i], piglit_name, sizeof(piglit_name)); current_test = get_or_create_json_object(tests, piglit_name); if (!json_object_object_get_ex(current_test, "dmesg", NULL)) { add_dmesg(current_test, "", 0, NULL, 0); } } } static bool fill_from_dmesg(int fd, struct settings *settings, char *binary, struct subtests *subtests, struct json_object *tests) { char *line = NULL, *warnings = NULL, *dmesg = NULL; size_t linelen = 0, warningslen = 0, dmesglen = 0; struct json_object *current_test = NULL; FILE *f = fdopen(fd, "r"); char piglit_name[256]; ssize_t read; size_t i; regex_t re; if (!f) { return false; } if (!init_regex_whitelist(settings, &re)) { fclose(f); return false; } while ((read = getline(&line, &linelen, f)) > 0) { char *formatted; unsigned flags; unsigned long long ts_usec; char continuation; char *message, *subtest; if (!parse_dmesg_line(line, &flags, &ts_usec, &continuation, &message)) continue; generate_formatted_dmesg_line(message, flags, ts_usec, &formatted); if ((subtest = strstr(message, STARTING_SUBTEST_DMESG)) != NULL) { if (current_test != NULL) { /* Done with the previous subtest, file up */ add_dmesg(current_test, dmesg, dmesglen, warnings, warningslen); free(dmesg); free(warnings); dmesg = warnings = NULL; dmesglen = warningslen = 0; } subtest += strlen(STARTING_SUBTEST_DMESG); generate_piglit_name(binary, subtest, piglit_name, sizeof(piglit_name)); current_test = get_or_create_json_object(tests, piglit_name); } if (settings->piglit_style_dmesg) { if ((flags & 0x07) <= settings->dmesg_warn_level && continuation != 'c' && regexec(&re, message, (size_t)0, NULL, 0) != REG_NOMATCH) { append_line(&warnings, &warningslen, formatted); } } else { if ((flags & 0x07) <= settings->dmesg_warn_level && continuation != 'c' && regexec(&re, message, (size_t)0, NULL, 0) == REG_NOMATCH) { append_line(&warnings, &warningslen, formatted); } } append_line(&dmesg, &dmesglen, formatted); free(formatted); } free(line); if (current_test != NULL) { add_dmesg(current_test, dmesg, dmesglen, warnings, warningslen); } else { /* * Didn't get any subtest messages at all. If there * are subtests, add all of the dmesg gotten to all of * them. */ for (i = 0; i < subtests->size; i++) { generate_piglit_name(binary, subtests->names[i], piglit_name, sizeof(piglit_name)); current_test = get_or_create_json_object(tests, piglit_name); /* * Don't bother with warnings, any subtests * there are would have skip as their result * anyway. */ add_dmesg(current_test, dmesg, dmesglen, NULL, 0); } if (subtests->size == 0) { generate_piglit_name(binary, NULL, piglit_name, sizeof(piglit_name)); current_test = get_or_create_json_object(tests, piglit_name); add_dmesg(current_test, dmesg, dmesglen, warnings, warningslen); } } add_empty_dmesgs_where_missing(tests, binary, subtests); free(dmesg); free(warnings); regfree(&re); fclose(f); return true; } static const char *result_from_exitcode(int exitcode) { switch (exitcode) { case IGT_EXIT_SKIP: return "skip"; case IGT_EXIT_SUCCESS: return "pass"; case IGT_EXIT_INVALID: return "notrun"; case INCOMPLETE_EXITCODE: return "incomplete"; default: return "fail"; } } static void add_subtest(struct subtests *subtests, char *subtest) { size_t len = strlen(subtest); if (len == 0) return; if (subtest[len - 1] == '\n') subtest[len - 1] = '\0'; subtests->size++; subtests->names = realloc(subtests->names, sizeof(*subtests->names) * subtests->size); subtests->names[subtests->size - 1] = subtest; } static void free_subtests(struct subtests *subtests) { size_t i; for (i = 0; i < subtests->size; i++) free(subtests->names[i]); free(subtests->names); } static void fill_from_journal(int fd, struct job_list_entry *entry, struct subtests *subtests, struct results *results) { FILE *f = fdopen(fd, "r"); char *line = NULL; size_t linelen = 0; ssize_t read; char exitline[] = "exit:"; char timeoutline[] = "timeout:"; int exitcode = INCOMPLETE_EXITCODE; bool has_timeout = false; struct json_object *tests = results->tests; struct json_object *runtimes = results->runtimes; while ((read = getline(&line, &linelen, f)) > 0) { if (read >= strlen(exitline) && !memcmp(line, exitline, strlen(exitline))) { char *p = strchr(line, '('); char piglit_name[256]; double time = 0.0; struct json_object *obj; exitcode = atoi(line + strlen(exitline)); if (p) time = strtod(p + 1, NULL); generate_piglit_name(entry->binary, NULL, piglit_name, sizeof(piglit_name)); obj = get_or_create_json_object(runtimes, piglit_name); add_runtime(obj, time); /* If no subtests, the test result node also gets the runtime */ if (subtests->size == 0 && entry->subtest_count == 0) { obj = get_or_create_json_object(tests, piglit_name); add_runtime(obj, time); } } else if (read >= strlen(timeoutline) && !memcmp(line, timeoutline, strlen(timeoutline))) { has_timeout = true; if (subtests->size) { /* Assign the timeout to the previously appeared subtest */ char *last_subtest = subtests->names[subtests->size - 1]; char piglit_name[256]; char *p = strchr(line, '('); double time = 0.0; struct json_object *obj; generate_piglit_name(entry->binary, last_subtest, piglit_name, sizeof(piglit_name)); obj = get_or_create_json_object(tests, piglit_name); set_result(obj, "timeout"); if (p) time = strtod(p + 1, NULL); /* Add runtime for the subtest... */ add_runtime(obj, time); /* ... and also for the binary */ generate_piglit_name(entry->binary, NULL, piglit_name, sizeof(piglit_name)); obj = get_or_create_json_object(runtimes, piglit_name); add_runtime(obj, time); } } else { add_subtest(subtests, strdup(line)); } } if (subtests->size == 0) { char *subtestname = NULL; char piglit_name[256]; struct json_object *obj; const char *result = has_timeout ? "timeout" : result_from_exitcode(exitcode); /* * If the test was killed before it printed that it's * entering a subtest, we would incorrectly generate * results as the binary had no subtests. If we know * otherwise, do otherwise. */ if (entry->subtest_count > 0) { subtestname = entry->subtests[0]; add_subtest(subtests, strdup(subtestname)); } generate_piglit_name(entry->binary, subtestname, piglit_name, sizeof(piglit_name)); obj = get_or_create_json_object(tests, piglit_name); set_result(obj, result); } free(line); fclose(f); } static void override_result_single(struct json_object *obj) { const char *errtext = NULL, *result = NULL; struct json_object *textobj; bool dmesgwarns = false; if (json_object_object_get_ex(obj, "err", &textobj)) errtext = json_object_get_string(textobj); if (json_object_object_get_ex(obj, "result", &textobj)) result = json_object_get_string(textobj); if (json_object_object_get_ex(obj, "dmesg-warnings", &textobj)) dmesgwarns = true; if (!strcmp(result, "pass") && count_lines(errtext, errtext + strlen(errtext)) > 2) { set_result(obj, "warn"); result = "warn"; } if (dmesgwarns) { if (!strcmp(result, "pass") || !strcmp(result, "warn")) { set_result(obj, "dmesg-warn"); } else if (!strcmp(result, "fail")) { set_result(obj, "dmesg-fail"); } } } static void override_results(char *binary, struct subtests *subtests, struct json_object *tests) { struct json_object *obj; char piglit_name[256]; size_t i; if (subtests->size == 0) { generate_piglit_name(binary, NULL, piglit_name, sizeof(piglit_name)); obj = get_or_create_json_object(tests, piglit_name); override_result_single(obj); return; } for (i = 0; i < subtests->size; i++) { generate_piglit_name(binary, subtests->names[i], piglit_name, sizeof(piglit_name)); obj = get_or_create_json_object(tests, piglit_name); override_result_single(obj); } } static struct json_object *get_totals_object(struct json_object *totals, const char *key) { struct json_object *obj = NULL; if (json_object_object_get_ex(totals, key, &obj)) return obj; obj = json_object_new_object(); json_object_object_add(totals, key, obj); json_object_object_add(obj, "crash", json_object_new_int(0)); json_object_object_add(obj, "pass", json_object_new_int(0)); json_object_object_add(obj, "dmesg-fail", json_object_new_int(0)); json_object_object_add(obj, "dmesg-warn", json_object_new_int(0)); json_object_object_add(obj, "skip", json_object_new_int(0)); json_object_object_add(obj, "incomplete", json_object_new_int(0)); json_object_object_add(obj, "timeout", json_object_new_int(0)); json_object_object_add(obj, "notrun", json_object_new_int(0)); json_object_object_add(obj, "fail", json_object_new_int(0)); json_object_object_add(obj, "warn", json_object_new_int(0)); return obj; } static void add_result_to_totals(struct json_object *totals, const char *result) { json_object *numobj = NULL; int old; if (!json_object_object_get_ex(totals, result, &numobj)) { fprintf(stderr, "Warning: Totals object without count for %s\n", result); return; } old = json_object_get_int(numobj); json_object_object_add(totals, result, json_object_new_int(old + 1)); } static void add_to_totals(const char *binary, struct subtests *subtests, struct results *results) { struct json_object *test, *resultobj, *emptystrtotal, *roottotal, *binarytotal; char piglit_name[256]; const char *result; size_t i; generate_piglit_name(binary, NULL, piglit_name, sizeof(piglit_name)); emptystrtotal = get_totals_object(results->totals, ""); roottotal = get_totals_object(results->totals, "root"); binarytotal = get_totals_object(results->totals, piglit_name); if (subtests->size == 0) { test = get_or_create_json_object(results->tests, piglit_name); if (!json_object_object_get_ex(test, "result", &resultobj)) { fprintf(stderr, "Warning: No results set for %s\n", piglit_name); return; } result = json_object_get_string(resultobj); add_result_to_totals(emptystrtotal, result); add_result_to_totals(roottotal, result); add_result_to_totals(binarytotal, result); return; } for (i = 0; i < subtests->size; i++) { generate_piglit_name(binary, subtests->names[i], piglit_name, sizeof(piglit_name)); test = get_or_create_json_object(results->tests, piglit_name); if (!json_object_object_get_ex(test, "result", &resultobj)) { fprintf(stderr, "Warning: No results set for %s\n", piglit_name); return; } result = json_object_get_string(resultobj); add_result_to_totals(emptystrtotal, result); add_result_to_totals(roottotal, result); add_result_to_totals(binarytotal, result); } } static bool parse_test_directory(int dirfd, struct job_list_entry *entry, struct settings *settings, struct results *results) { int fds[_F_LAST]; struct subtests subtests = {}; bool status = true; if (!open_output_files(dirfd, fds, false)) { fprintf(stderr, "Error opening output files\n"); return false; } /* * fill_from_journal fills the subtests struct and adds * timeout results where applicable. */ fill_from_journal(fds[_F_JOURNAL], entry, &subtests, results); if (!fill_from_output(fds[_F_OUT], entry->binary, "out", &subtests, results->tests) || !fill_from_output(fds[_F_ERR], entry->binary, "err", &subtests, results->tests) || !fill_from_dmesg(fds[_F_DMESG], settings, entry->binary, &subtests, results->tests)) { fprintf(stderr, "Error parsing output files\n"); status = false; goto parse_output_end; } override_results(entry->binary, &subtests, results->tests); add_to_totals(entry->binary, &subtests, results); parse_output_end: close_outputs(fds); free_subtests(&subtests); return status; } static void try_add_notrun_results(const struct job_list_entry *entry, const struct settings *settings, struct results *results) { struct subtests subtests = {}; struct json_object *current_test; size_t i; if (entry->subtest_count == 0) { char piglit_name[256]; /* We cannot distinguish no-subtests from run-all-subtests in multiple-mode */ if (settings->multiple_mode) return; generate_piglit_name(entry->binary, NULL, piglit_name, sizeof(piglit_name)); current_test = get_or_create_json_object(results->tests, piglit_name); json_object_object_add(current_test, "out", json_object_new_string("")); json_object_object_add(current_test, "err", json_object_new_string("")); json_object_object_add(current_test, "dmesg", json_object_new_string("")); json_object_object_add(current_test, "result", json_object_new_string("notrun")); } for (i = 0; i < entry->subtest_count; i++) { char piglit_name[256]; generate_piglit_name(entry->binary, entry->subtests[i], piglit_name, sizeof(piglit_name)); current_test = get_or_create_json_object(results->tests, piglit_name); json_object_object_add(current_test, "out", json_object_new_string("")); json_object_object_add(current_test, "err", json_object_new_string("")); json_object_object_add(current_test, "dmesg", json_object_new_string("")); json_object_object_add(current_test, "result", json_object_new_string("notrun")); add_subtest(&subtests, strdup(entry->subtests[i])); } add_to_totals(entry->binary, &subtests, results); free_subtests(&subtests); } static void create_result_root_nodes(struct json_object *root, struct results *results) { results->tests = json_object_new_object(); json_object_object_add(root, "tests", results->tests); results->totals = json_object_new_object(); json_object_object_add(root, "totals", results->totals); results->runtimes = json_object_new_object(); json_object_object_add(root, "runtimes", results->runtimes); } struct json_object *generate_results_json(int dirfd) { struct settings settings; struct job_list job_list; struct json_object *obj, *elapsed; struct results results; int testdirfd, fd; size_t i; init_settings(&settings); init_job_list(&job_list); if (!read_settings_from_dir(&settings, dirfd)) { fprintf(stderr, "resultgen: Cannot parse settings\n"); return NULL; } if (!read_job_list(&job_list, dirfd)) { fprintf(stderr, "resultgen: Cannot parse job list\n"); return NULL; } obj = json_object_new_object(); json_object_object_add(obj, "__type__", json_object_new_string("TestrunResult")); json_object_object_add(obj, "results_version", json_object_new_int(10)); json_object_object_add(obj, "name", settings.name ? json_object_new_string(settings.name) : json_object_new_string("")); if ((fd = openat(dirfd, "uname.txt", O_RDONLY)) >= 0) { char buf[128]; ssize_t r = read(fd, buf, sizeof(buf)); if (r > 0 && buf[r - 1] == '\n') r--; json_object_object_add(obj, "uname", json_object_new_string_len(buf, r)); close(fd); } elapsed = json_object_new_object(); json_object_object_add(elapsed, "__type__", json_object_new_string("TimeAttribute")); if ((fd = openat(dirfd, "starttime.txt", O_RDONLY)) >= 0) { char buf[128] = {}; read(fd, buf, sizeof(buf)); json_object_object_add(elapsed, "start", json_object_new_double(atof(buf))); close(fd); } if ((fd = openat(dirfd, "endtime.txt", O_RDONLY)) >= 0) { char buf[128] = {}; read(fd, buf, sizeof(buf)); json_object_object_add(elapsed, "end", json_object_new_double(atof(buf))); close(fd); } json_object_object_add(obj, "time_elapsed", elapsed); create_result_root_nodes(obj, &results); /* * Result fields that won't be added: * * - glxinfo * - wglinfo * - clinfo * * Result fields that are TODO: * * - lspci * - options */ for (i = 0; i < job_list.size; i++) { char name[16]; snprintf(name, 16, "%zd", i); if ((testdirfd = openat(dirfd, name, O_DIRECTORY | O_RDONLY)) < 0) { try_add_notrun_results(&job_list.entries[i], &settings, &results); continue; } if (!parse_test_directory(testdirfd, &job_list.entries[i], &settings, &results)) { close(testdirfd); return NULL; } close(testdirfd); } if ((fd = openat(dirfd, "aborted.txt", O_RDONLY)) >= 0) { char buf[4096]; char piglit_name[] = "igt@runner@aborted"; struct subtests abortsub = {}; struct json_object *aborttest = get_or_create_json_object(results.tests, piglit_name); ssize_t s; add_subtest(&abortsub, strdup("aborted")); s = read(fd, buf, sizeof(buf)); json_object_object_add(aborttest, "out", json_object_new_string_len(buf, s)); json_object_object_add(aborttest, "err", json_object_new_string("")); json_object_object_add(aborttest, "dmesg", json_object_new_string("")); json_object_object_add(aborttest, "result", json_object_new_string("fail")); add_to_totals("runner", &abortsub, &results); free_subtests(&abortsub); } free_settings(&settings); free_job_list(&job_list); return obj; } bool generate_results(int dirfd) { struct json_object *obj = generate_results_json(dirfd); const char *json_string; int resultsfd; if (obj == NULL) return false; /* TODO: settings.overwrite */ if ((resultsfd = openat(dirfd, "results.json", O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) { fprintf(stderr, "resultgen: Cannot create results file\n"); return false; } json_string = json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PRETTY); write(resultsfd, json_string, strlen(json_string)); close(resultsfd); return true; } bool generate_results_path(char *resultspath) { int dirfd = open(resultspath, O_DIRECTORY | O_RDONLY); if (dirfd < 0) return false; return generate_results(dirfd); }