Replace the hacky and broken pipeline in testdriver.sh with a custom-written
authorDavid Given <dg@cowlark.com>
Tue, 29 Nov 2016 19:59:43 +0000 (20:59 +0100)
committerDavid Given <dg@cowlark.com>
Tue, 29 Nov 2016 19:59:43 +0000 (20:59 +0100)
tool in C; much more robust and easier to understand, as well as avoiding the
dependency on timeout (which isn't Posix).

tests/plat/build.lua
tests/plat/testdriver.sh
util/build/build.lua [new file with mode: 0644]
util/build/testrunner.c [new file with mode: 0644]

index 3873ed9..be65668 100644 (file)
@@ -45,10 +45,11 @@ definerule("plat_testsuite",
                                outleaves = { "stamp" },
                                ins = {
                                        bin,
-                                       "tests/plat/testdriver.sh"
+                                       "tests/plat/testdriver.sh",
+                                       "util/build+testrunner"
                                },
                                commands = {
-                                       "%{ins[2]} "..e.method.." %{ins[1]} 5",
+                                       "%{ins[2]} "..e.method.." %{ins[1]} 5 %{ins[3]}",
                                        "touch %{outs}"
                                }
                        }
index 272c6b2..6af14da 100755 (executable)
@@ -2,50 +2,49 @@
 method=$1
 img=$2
 timeout=$3
+timeoutprog=$4
 
-pipe=/tmp/$$.testdriver.pipe
-mknod $pipe p
-trap "rm -f $pipe" EXIT
+set -e
 
 result=/tmp/$$.testdriver.result
 trap "rm -f $result" EXIT
 
-pidfile=/tmp/$$.testdriver.pid
-trap "rm -f $pidfile" EXIT
-
-case $method in
-    qemu-system-*)
-        if ! command -v $method >/dev/null 2>&1 ; then
-            echo "Warning: $method not installed, skipping test"
-            exit 0
-        fi
-
-        case $method in
-            qemu-system-i386) img="-drive file=$img,if=floppy,format=raw" ;;
-            qemu-system-ppc)  img="-kernel $img" ;;
-        esac
-
-        ( $method -nographic $img 2>&1 & echo $! > $pidfile ) \
-            | tee $result \
-            | ( timeout $timeout grep -l -q @@FINISHED ; echo ) \
-            | ( read dummy && kill $(cat $pidfile) )
-        
-        ;;
-
-    qemu-*)
-        if ! command -v $method >/dev/null 2>&1 ; then
-            echo "Warning: $method not installed, skipping test"
-            exit 0
-        fi
-
-        $method $img > $result
-        ;;
-
-    *)
-        echo "Error: $method not known by testdriver"
-        exit 1
-        ;;
-esac
-
+errcho() {
+    >&2 echo "$*"
+}
+
+get_test_output() {
+    case $method in
+        qemu-system-*)
+            if ! command -v $method >/dev/null 2>&1 ; then
+                errcho "Warning: $method not installed, skipping test"
+                exit 0
+            fi
+
+            case $method in
+                qemu-system-i386) img="-drive file=$img,if=floppy,format=raw" ;;
+                qemu-system-ppc)  img="-kernel $img" ;;
+            esac
+
+            $timeoutprog -t $timeout -- $method -nographic $img > $result
+            ;;
+
+        qemu-*)
+            if ! command -v $method >/dev/null 2>&1 ; then
+                errcho "Warning: $method not installed, skipping test"
+                exit 0
+            fi
+
+            $method $img > $result
+            ;;
+
+        *)
+            errcho "Error: $method not known by testdriver"
+            exit 1
+            ;;
+    esac
+}
+
+get_test_output > $result
 ( grep -q @@FAIL $result || ! grep -q @@FINISHED $result ) && cat $result && exit 1
 exit 0
diff --git a/util/build/build.lua b/util/build/build.lua
new file mode 100644 (file)
index 0000000..76623fe
--- /dev/null
@@ -0,0 +1,7 @@
+cprogram {
+       name = "testrunner",
+       srcs = { "./testrunner.c" },
+       deps = { 
+        "modules/src/data+lib"
+    }
+}
diff --git a/util/build/testrunner.c b/util/build/testrunner.c
new file mode 100644 (file)
index 0000000..16d526d
--- /dev/null
@@ -0,0 +1,104 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <setjmp.h>
+#include "diagnostics.h"
+
+static bool timed_out = false;
+static bool child_exited = false;
+static char** command = NULL;
+static int timeout = 0;
+static jmp_buf exit_jmp;
+
+static void parse_arguments(int argc, char* const argv[])
+{
+    program_name = argv[0];
+    for (;;)
+    {
+        int c = getopt(argc, argv, "t:");
+        if (c == -1)
+            break;
+
+        switch (c)
+        {
+            case 't':
+                timeout = atoi(optarg);
+                break;
+
+            default:
+                fatal("syntax: testrunner <timeout in secs> -- <command>\n");
+        }
+    }
+
+    command = &argv[optind];
+    if (!command[0])
+        fatal("you must supply a command");
+    if (timeout <= 0)
+        fatal("timeout missing or invalid");
+}
+
+static void sigalrm_cb(int sigraised)
+{
+    timed_out = true;
+    longjmp(exit_jmp, 1);
+}
+
+int main(int argc, char* const argv[])
+{
+    const int READ = 0;
+    const int WRITE = 1;
+
+    int fds[2];
+    int pid;
+    FILE* childin;
+    int wstatus;
+    char buffer[4096];
+
+    parse_arguments(argc, argv);
+
+    if (setjmp(exit_jmp) == 0)
+    {
+        /* First time through. */
+
+        signal(SIGALRM, sigalrm_cb);
+        alarm(timeout);
+
+        pipe(fds);
+        pid = fork();
+        if (pid == 0)
+        {
+            /* Child */
+            close(fds[READ]);
+            close(0);
+            dup2(fds[WRITE], 1);
+            dup2(fds[WRITE], 2);
+            execvp(command[0], command);
+            _exit(1);
+        }
+
+        childin = fdopen(fds[READ], "r");
+        while (!timed_out)
+        {
+            if (!fgets(buffer, sizeof(buffer), childin))
+                break;
+            fputs(buffer, stdout);
+
+            if (strcmp(buffer, "@@FINISHED\n") == 0)
+                break;
+            if (strcmp(buffer, "@@FINISHED\r\n") == 0)
+                break;
+        }
+    }
+
+    /* Reached via longjmp, EOF, or seeing a @@FINISHED. */
+
+    kill(pid, SIGKILL);
+    waitpid(pid, &wstatus, 0);
+    if (timed_out)
+        exit(1);
+    exit(WEXITSTATUS(wstatus));
+}