summaryrefslogtreecommitdiff
path: root/lib/qemu-wrapper.c
diff options
context:
space:
mode:
authorKent Overstreet <kent.overstreet@gmail.com>2021-05-19 17:27:25 -0400
committerKent Overstreet <kent.overstreet@gmail.com>2021-05-19 18:33:52 -0400
commitf9795f10f6bd645bde8610b8347084aa7a1c8916 (patch)
treeff54c95fe5e803b21c78faea03f2e72dae7655bd /lib/qemu-wrapper.c
parenta2909f635cb2a00413dc70c13966071605b6d5f0 (diff)
New qemu-wrapper
This adds a wrapper written in C to scrape qemu's output for test success/failure, as well as implementing timeouts. Now we should have test automation working again. Signed-off-by: Kent Overstreet <kent.overstreet@gmail.com>
Diffstat (limited to 'lib/qemu-wrapper.c')
-rw-r--r--lib/qemu-wrapper.c151
1 files changed, 151 insertions, 0 deletions
diff --git a/lib/qemu-wrapper.c b/lib/qemu-wrapper.c
new file mode 100644
index 0000000..5f52d0c
--- /dev/null
+++ b/lib/qemu-wrapper.c
@@ -0,0 +1,151 @@
+#include <getopt.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#define die(msg, ...) \
+do { \
+ fprintf(stderr, msg "\n", ##__VA_ARGS__); \
+ exit(EXIT_FAILURE); \
+} while (0)
+
+static pid_t child;
+static bool print_timeout = true;
+
+static void alarm_handler(int sig)
+{
+ char msg[] = "TEST TIMEOUT\n";
+ kill(child, SIGKILL);
+ if (print_timeout)
+ write(STDOUT_FILENO, msg, strlen(msg));
+ _exit(EXIT_FAILURE);
+}
+
+static void usage(void)
+{
+ puts("qemu-wrapper - wrapper for qemu to catch test success/failure\n"
+ "Usage: qemu-wrapper [OPTIONS] -- <qemu-command>\n"
+ "\n"
+ "Options\n"
+ " -S Exit on success\n"
+ " -F Exit on failure\n"
+ " -T TIMEOUT Timeout after TIMEOUT seconds\n"
+ " -h Display this help and exit\n");
+}
+
+int main(int argc, char *argv[])
+{
+ bool exit_on_success = false;
+ bool exit_on_failure = false;
+ unsigned long timeout = 0;
+ int opt, ret = EXIT_FAILURE;
+ struct timespec start, ts;
+
+ setlinebuf(stdin);
+ setlinebuf(stdout);
+
+ if (clock_gettime(CLOCK_MONOTONIC, &start))
+ die("clock_gettime error: %m");
+
+ while ((opt = getopt(argc, argv, "SFT:h")) != -1) {
+ switch (opt) {
+ case 'S':
+ exit_on_success = true;
+ break;
+ case 'F':
+ exit_on_failure = true;
+ break;
+ case 'T':
+ errno = 0;
+ timeout = strtoul(optarg, NULL, 10);
+ if (errno)
+ die("error parsing timeout: %m");
+ break;
+ case 'h':
+ usage();
+ exit(EXIT_SUCCESS);
+ case '?':
+ usage();
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ int pipefd[2];
+ if (pipe(pipefd))
+ die("error creating pipe: %m");
+
+ child = fork();
+ if (child < 0)
+ die("fork error: %m");
+
+ if (!child) {
+ if (dup2(pipefd[1], STDOUT_FILENO) < 0)
+ die("dup2 error: %m");
+ if (dup2(pipefd[1], STDERR_FILENO) < 0)
+ die("dup2 error: %m");
+ close(pipefd[1]);
+
+ int devnull = open("/dev/null", O_RDONLY);
+ if (devnull < 0)
+ die("error opening /dev/null; %m");
+ if (dup2(devnull, STDIN_FILENO) < 0)
+ die("dup2 error: %m");
+ close(devnull);
+
+ execvp(argv[optind], argv + optind);
+ die("error execing %s: %m", argv[optind]);
+ }
+
+ struct sigaction alarm_action = { .sa_handler = alarm_handler };
+ if (sigaction(SIGALRM, &alarm_action, NULL))
+ die("sigaction error: %m");
+
+ if (timeout)
+ alarm(timeout);
+
+ FILE *childf = fdopen(pipefd[0], "r");
+ if (!childf) {
+ fprintf(stderr, "fdopen error: %m\n");
+ goto out;
+ }
+
+ size_t n = 0, len;
+ char *line = NULL;
+
+ while ((len = getline(&line, &n, childf)) >= 0) {
+ if (clock_gettime(CLOCK_MONOTONIC, &ts)) {
+ fprintf(stderr, "clock_gettime error: %m\n");
+ break;
+ }
+
+ unsigned long elapsed = ts.tv_sec - start.tv_sec;
+ printf("%.5lu ", elapsed);
+ fputs(line, stdout);
+
+ if (exit_on_success &&
+ strstr(line, "TEST SUCCESS")) {
+ ret = 0;
+ break;
+ }
+
+ if (exit_on_failure && strstr(line, "TEST FAILED"))
+ break;
+
+ if (exit_on_failure && strstr(line, "Kernel panic")) {
+ /* Read output for five more seconds, then exit */
+ print_timeout = false;
+ alarm(5);
+ }
+ }
+out:
+ kill(child, SIGKILL);
+ exit(ret);
+}