summaryrefslogtreecommitdiff
path: root/runner/executor.c
diff options
context:
space:
mode:
authorMauro Carvalho Chehab <mchehab@kernel.org>2022-03-16 15:59:55 +0100
committerPetri Latvala <petri.latvala@intel.com>2022-03-21 18:34:59 +0200
commit7eb558a8297ff1ae4f9aaf10ac89d9ddce7ad56c (patch)
tree23af2def2b12b0ccc6f651c8ffea4c56fde38a4d /runner/executor.c
parent4b88a9253443ecd910a5f4c7bfe624a5f29d35b9 (diff)
runner: Add support for code coverage
The gcc compiler has a feature that enables checking the code coverage in runtime[1]. [1] See https://www.kernel.org/doc/html/latest/dev-tools/gcov.html The Linux Kernel comes with an option to enable such feature: ./scripts/config -e DEBUG_FS -e GCOV_KERNEL The driver's Makefile also needs change to enable it. For instance, in order to enable GCOV for all DRM drivers, one would need to run: for i in $(find drivers/gpu/drm/ -name Makefile); do sed '1 a GCOV_PROFILE := y' -i $i done This patch adds support for it by: a) Implementing a logic to cleanup the code coverage counters via sysfs; b) Calling a script responsible for collecging code coverage data. The implementation works with two modes: 1) It zeroes the counters, run all IGT tests and collects the code coverage results at the end. This implies that no tests would crash the driver, as otherwise the results won't be collected; This is faster, as collecting code coverage data can take several seconds. 2) For each test, it will clean the code coverage counters, run the and collect the results. This is more reliable, as a Kernel crash/OOPS won't affect the results of the previously ran tests. Reviewed-by: Petri Latvala <petri.latvala@intel.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
Diffstat (limited to 'runner/executor.c')
-rw-r--r--runner/executor.c186
1 files changed, 178 insertions, 8 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 */