diff options
-rw-r--r-- | runner/executor.c | 186 | ||||
-rw-r--r-- | runner/runner_tests.c | 3 | ||||
-rw-r--r-- | runner/settings.c | 79 | ||||
-rw-r--r-- | runner/settings.h | 7 |
4 files changed, 265 insertions, 10 deletions
diff --git a/runner/executor.c b/runner/executor.c index 15bd53dd..ab00900c 100644 --- a/runner/executor.c +++ b/runner/executor.c @@ -1,3 +1,4 @@ +#include <ctype.h> #include <errno.h> #include <fcntl.h> #include <glib.h> @@ -1693,6 +1694,140 @@ static bool should_die_because_signal(int sigfd) return false; } +static char *code_coverage_name(struct settings *settings) +{ + const char *start, *end, *fname; + char *name; + int size; + + if (settings->name && *settings->name) + return settings->name; + else if (!settings->test_list) + return NULL; + + /* Use only the base of the test_list, without path and extension */ + fname = settings->test_list; + + start = strrchr(fname,'/'); + if (!start) + start = fname; + + end = strrchr(start, '.'); + if (end) + size = end - start; + else + size = strlen(start); + + name = malloc(size + 1); + strncpy(name, fname, size); + name[size] = '\0'; + + return name; +} + +static void run_as_root(char * const argv[], int sigfd, char **abortreason) +{ + struct signalfd_siginfo siginfo; + int status = 0, ret; + pid_t child; + + child = fork(); + if (child < 0) { + *abortreason = strdup("Failed to fork"); + return; + } + + if (child == 0) { + execv(argv[0], argv); + perror (argv[0]); + exit(IGT_EXIT_INVALID); + } + + if (sigfd >= 0) { + while (1) { + ret = read(sigfd, &siginfo, sizeof(siginfo)); + if (ret < 0) { + errf("Error reading from signalfd: %m\n"); + continue; + } else if (siginfo.ssi_signo == SIGCHLD) { + if (child != waitpid(child, &status, WNOHANG)) { + errf("Failed to reap child\n"); + status = 9999; + continue; + } + break; + } + } + } else { + waitpid(child, &status, 0); + } + + if (WIFSIGNALED(status)) + asprintf(abortreason, "%s received signal %d while running\n",argv[0], WTERMSIG(status)); + else if (!WIFEXITED(status)) + asprintf(abortreason, "%s aborted with unknown status\n", argv[0]); + else if (WEXITSTATUS(status)) + asprintf(abortreason, "%s returned error %d\n", argv[0], WEXITSTATUS(status)); +} + +static void code_coverage_start(struct settings *settings, int sigfd, char **abortreason) +{ + int fd; + + fd = open(GCOV_RESET, O_WRONLY); + if (fd < 0) { + asprintf(abortreason, "Failed to open %s", GCOV_RESET); + return; + } + if (write(fd, "0\n", 2) < 0) + *abortreason = strdup("Failed to reset gcov counters"); + + close(fd); +} + +static void code_coverage_stop(struct settings *settings, const char *job_name, + int sigfd, char **abortreason) +{ + int i, j = 0, last_was_escaped = 1; + char fname[PATH_MAX]; + char name[PATH_MAX]; + char *argv[3] = {}; + + /* If name is empty, use a default */ + if (!job_name || !*job_name) + job_name = "code_coverage"; + + /* + * Use only letters, numbers and '_' + * + * This way, the tarball name can be used as testname when lcov runs + */ + for (i = 0; i < strlen(job_name); i++) { + if (!isalpha(job_name[i]) && !isalnum(job_name[i])) { + if (last_was_escaped) + continue; + name[j++] = '_'; + last_was_escaped = 1; + } else { + name[j++] = job_name[i]; + last_was_escaped = 0; + } + } + if (j && last_was_escaped) + j--; + name[j] = '\0'; + + strcpy(fname, settings->results_path); + strcat(fname, CODE_COV_RESULTS_PATH "/"); + strcat(fname, name); + + argv[0] = settings->code_coverage_script; + argv[1] = fname; + + outf("Storing code coverage results...\n"); + run_as_root(argv, sigfd, abortreason); +} + bool execute(struct execute_state *state, struct settings *settings, struct job_list *job_list) @@ -1709,6 +1844,17 @@ bool execute(struct execute_state *state, return true; } + if (settings->enable_code_coverage && !settings->cov_results_per_test) { + char *reason = NULL; + + code_coverage_start(settings, -1, &reason); + if (reason != NULL) { + errf("%s\n", reason); + free(reason); + status = false; + } + } + if ((resdirfd = open(settings->results_path, O_DIRECTORY | O_RDONLY)) < 0) { /* Initialize state should have done this */ errf("Error: Failure opening results path %s\n", @@ -1795,6 +1941,7 @@ bool execute(struct execute_state *state, for (; state->next < job_list->size; state->next++) { char *reason = NULL; + char *job_name; int result; if (should_die_because_signal(sigfd)) { @@ -1802,14 +1949,26 @@ bool execute(struct execute_state *state, goto end; } - result = execute_next_entry(state, - job_list->size, - &time_spent, - settings, - &job_list->entries[state->next], - testdirfd, resdirfd, - sigfd, &sigmask, - &reason); + if (settings->cov_results_per_test) { + code_coverage_start(settings, sigfd, &reason); + job_name = entry_display_name(&job_list->entries[state->next]); + } + + if (reason == NULL) { + result = execute_next_entry(state, + job_list->size, + &time_spent, + settings, + &job_list->entries[state->next], + testdirfd, resdirfd, + sigfd, &sigmask, + &reason); + + if (settings->cov_results_per_test) { + code_coverage_stop(settings, job_name, sigfd, &reason); + free(job_name); + } + } if (reason != NULL || (reason = need_to_abort(settings)) != NULL) { char *prev = entry_display_name(&job_list->entries[state->next]); @@ -1864,6 +2023,17 @@ bool execute(struct execute_state *state, } end: + if (settings->enable_code_coverage && !settings->cov_results_per_test) { + char *reason = NULL; + + code_coverage_stop(settings, code_coverage_name(settings), -1, &reason); + if (reason != NULL) { + errf("%s\n", reason); + free(reason); + status = false; + } + } + close_watchdogs(settings); sigprocmask(SIG_UNBLOCK, &sigmask, NULL); /* make sure that we do not leave any signals unhandled */ diff --git a/runner/runner_tests.c b/runner/runner_tests.c index e67e08a8..96ffbf1f 100644 --- a/runner/runner_tests.c +++ b/runner/runner_tests.c @@ -443,6 +443,9 @@ igt_main "--use-watchdog", "--piglit-style-dmesg", "--dmesg-warn-level=3", + "--collect-code-cov", + "--coverage-per-test", + "--collect-script", "/usr/bin/true", "test-root-dir", "path-to-results", }; diff --git a/runner/settings.c b/runner/settings.c index ac090b08..a7a12f50 100644 --- a/runner/settings.c +++ b/runner/settings.c @@ -25,6 +25,9 @@ enum { OPT_OVERALL_TIMEOUT, OPT_PER_TEST_TIMEOUT, OPT_ALLOW_NON_ROOT, + OPT_CODE_COV_SCRIPT, + OPT_ENABLE_CODE_COVERAGE, + OPT_COV_RESULTS_PER_TEST, OPT_VERSION, OPT_HELP = 'h', OPT_NAME = 'n', @@ -240,6 +243,13 @@ static const char *usage_str = " Exclude all test matching to regexes from FILENAME\n" " (can be used more than once)\n" " -L, --list-all List all matching subtests instead of running\n" + " --collect-code-cov Enables gcov-based collect of code coverage for tests.\n" + " Requires --collect-script FILENAME\n" + " --coverage-per-test Stores code coverage results per each test.\n" + " Requires --collect-script FILENAME\n" + " --collect-script FILENAME\n" + " Use FILENAME as script to collect code coverage data.\n" + "\n" " [test_root] Directory that contains the IGT tests. The environment\n" " variable IGT_TEST_ROOT will be used if set, overriding\n" " this option if given.\n" @@ -338,11 +348,21 @@ static void free_regexes(struct regex_list *regexes) free(regexes->regexes); } -static bool readable_file(char *filename) +static bool readable_file(const char *filename) { return !access(filename, R_OK); } +static bool writeable_file(const char *filename) +{ + return !access(filename, W_OK); +} + +static bool executable_file(const char *filename) +{ + return !access(filename, X_OK); +} + static void print_version(void) { struct utsname uts; @@ -393,6 +413,9 @@ bool parse_options(int argc, char **argv, {"test-list", required_argument, NULL, OPT_TEST_LIST}, {"overwrite", no_argument, NULL, OPT_OVERWRITE}, {"ignore-missing", no_argument, NULL, OPT_IGNORE_MISSING}, + {"collect-code-cov", no_argument, NULL, OPT_ENABLE_CODE_COVERAGE}, + {"coverage-per-test", no_argument, NULL, OPT_COV_RESULTS_PER_TEST}, + {"collect-script", required_argument, NULL, OPT_CODE_COV_SCRIPT}, {"multiple-mode", no_argument, NULL, OPT_MULTIPLE}, {"inactivity-timeout", required_argument, NULL, OPT_TIMEOUT}, {"per-test-timeout", required_argument, NULL, OPT_PER_TEST_TIMEOUT}, @@ -465,6 +488,16 @@ bool parse_options(int argc, char **argv, case OPT_IGNORE_MISSING: /* Ignored, piglit compatibility */ break; + case OPT_ENABLE_CODE_COVERAGE: + settings->enable_code_coverage = true; + break; + case OPT_COV_RESULTS_PER_TEST: + settings->cov_results_per_test = true; + break; + case OPT_CODE_COV_SCRIPT: + settings->code_coverage_script = absolute_path(optarg); + break; + case OPT_MULTIPLE: settings->multiple_mode = true; break; @@ -597,6 +630,29 @@ bool validate_settings(struct settings *settings) close(fd); close(dirfd); + /* enables code coverage when --coverage-per-test is used */ + if (settings->cov_results_per_test) + settings->enable_code_coverage = true; + + if (!settings->allow_non_root && (getuid() != 0)) { + fprintf(stderr, "Runner needs to run with UID 0 (root).\n"); + return false; + } + + if (settings->enable_code_coverage) { + if (!executable_file(settings->code_coverage_script)) { + fprintf(stderr, "%s doesn't exist or is not executable\n", settings->code_coverage_script); + return false; + } + if (!writeable_file(GCOV_RESET)) { + if (getuid() != 0) + fprintf(stderr, "Code coverage requires root.\n"); + else + fprintf(stderr, "Is GCOV enabled? Can't access %s stat.\n", GCOV_RESET); + return false; + } + } + return true; } @@ -645,7 +701,8 @@ bool serialize_settings(struct settings *settings) { #define SERIALIZE_LINE(f, s, name, format) fprintf(f, "%s : " format "\n", #name, s->name) - int dirfd, fd; + int dirfd, covfd, fd; + char path[PATH_MAX]; FILE *f; if (!settings->results_path) { @@ -660,6 +717,18 @@ bool serialize_settings(struct settings *settings) return false; } } + if (settings->enable_code_coverage) { + strcpy(path, settings->results_path); + strcat(path, CODE_COV_RESULTS_PATH); + if ((covfd = open(path, O_DIRECTORY | O_RDONLY)) < 0) { + if (mkdir(path, 0755)) { + usage("Creating code coverage path failed", stderr); + return false; + } + } else { + close(covfd); + } + } if (!settings->overwrite && faccessat(dirfd, settings_filename, F_OK, 0) == 0) { @@ -712,6 +781,9 @@ bool serialize_settings(struct settings *settings) SERIALIZE_LINE(f, settings, dmesg_warn_level, "%d"); SERIALIZE_LINE(f, settings, test_root, "%s"); SERIALIZE_LINE(f, settings, results_path, "%s"); + SERIALIZE_LINE(f, settings, enable_code_coverage, "%d"); + SERIALIZE_LINE(f, settings, cov_results_per_test, "%d"); + SERIALIZE_LINE(f, settings, code_coverage_script, "%s"); if (settings->sync) { fsync(fd); @@ -760,6 +832,9 @@ bool read_settings_from_file(struct settings *settings, FILE *f) PARSE_LINE(settings, name, val, dmesg_warn_level, numval); PARSE_LINE(settings, name, val, test_root, val ? strdup(val) : NULL); PARSE_LINE(settings, name, val, results_path, val ? strdup(val) : NULL); + PARSE_LINE(settings, name, val, enable_code_coverage, numval); + PARSE_LINE(settings, name, val, cov_results_per_test, numval); + PARSE_LINE(settings, name, val, code_coverage_script, val ? strdup(val) : NULL); printf("Warning: Unknown field in settings file: %s = %s\n", name, val); diff --git a/runner/settings.h b/runner/settings.h index bc61faeb..3c2a3ee9 100644 --- a/runner/settings.h +++ b/runner/settings.h @@ -20,6 +20,10 @@ enum { _Static_assert(ABORT_ALL == (ABORT_TAINT | ABORT_LOCKDEP | ABORT_PING), "ABORT_ALL must be all conditions bitwise or'd"); +#define GCOV_DIR "/sys/kernel/debug/gcov" +#define GCOV_RESET GCOV_DIR "/reset" +#define CODE_COV_RESULTS_PATH "/code_cov" + struct regex_list { char **regex_strings; GRegex **regexes; @@ -48,6 +52,9 @@ struct settings { bool piglit_style_dmesg; int dmesg_warn_level; bool list_all; + char *code_coverage_script; + bool enable_code_coverage; + bool cov_results_per_test; }; /** |