diff --git a/Makefile b/Makefile index febd87aff..4399f0c28 100644 --- a/Makefile +++ b/Makefile @@ -63,7 +63,7 @@ PLATDEP = $(INSDIR)/lib/ack .NOTPARALLEL: -MAKECMDGOALS ?= +ack +MAKECMDGOALS ?= +ack +tests BUILD_FILES = $(shell find * -name '*.lua') ifneq ($(shell which ninja),) diff --git a/build.lua b/build.lua index dc9c0e03d..506011370 100644 --- a/build.lua +++ b/build.lua @@ -47,8 +47,18 @@ installable { "examples+pkg", plat_packages }, - deps = { - test_packages - } } +normalrule { + name = "tests", + ins = { + "first/testsummary.sh", + test_packages + }, + outleaves = { + "stamp" + }, + commands = { + "%{ins}" + } +} diff --git a/first/testsummary.sh b/first/testsummary.sh new file mode 100755 index 000000000..6301cb977 --- /dev/null +++ b/first/testsummary.sh @@ -0,0 +1,37 @@ +#!/bin/sh +echo "" + +succeeding="$(find "$@" -size 0)" +notsucceeding="$(find "$@" ! -size 0)" +skipped="$(grep -l @@SKIPPED $notsucceeding)" +timedout="$(grep -l @@TIMEDOUT $notsucceeding)" +failed="$(grep -l @@FAIL $notsucceeding)" + +for a in $failed $timedout; do + echo "**** $a" + cat $a + echo "" +done + +echo "$(echo $succeeding | wc -w) tests passed" +echo "$(echo $notsucceeding | wc -w) tests failed to pass" +echo "$(echo $skipped | wc -w) were skipped (see build log for details)" +echo "$(echo $timedout | wc -w) timed out" +echo "$(echo $failed | wc -w) failed" +echo "" + +if [ "$failed" != "" -o "$timedout" != "" ]; then + echo "Test status: SAD FACE (tests are failing)" + exit 1 +fi +if [ "$succeeding" = "" ]; then + echo "Test status: PUZZLED FACE (all tests were skipped)" + exit 0 +fi +if [ "$skipped" != "" ]; then + echo "Test status: MILDLY PLEASED FACE (some tests were skipped, but the rest pass)" + exit 0 +fi +echo "Test status: HAPPY FACE (all tests are passing)" +exit 0 + diff --git a/tests/plat/_dummy.c b/tests/plat/_dummy.c index a4693300f..48104b5aa 100644 --- a/tests/plat/_dummy.c +++ b/tests/plat/_dummy.c @@ -6,4 +6,3 @@ void _m_a_i_n(void) ASSERT(0 == 0); finished(); } - diff --git a/tests/plat/build.lua b/tests/plat/build.lua index 3873ed9a5..f5885a190 100644 --- a/tests/plat/build.lua +++ b/tests/plat/build.lua @@ -42,23 +42,21 @@ definerule("plat_testsuite", tests[#tests+1] = normalrule { name = fs, - outleaves = { "stamp" }, + outleaves = { e.plat.."-"..fs.."-testlog.txt" }, ins = { bin, - "tests/plat/testdriver.sh" + "tests/plat/testdriver.sh", + "util/build+testrunner" }, commands = { - "%{ins[2]} "..e.method.." %{ins[1]} 5", - "touch %{outs}" + "(%{ins[2]} "..e.method.." %{ins[1]} 5 %{ins[3]} || echo FAILED) 2>&1 > %{outs}", } } end - return normalrule { + return bundle { name = e.name, - outleaves = { "stamp" }, - ins = tests, - commands = { "touch %{outs}" } + srcs = tests, } end ) \ No newline at end of file diff --git a/tests/plat/lib/test.c b/tests/plat/lib/test.c index df00e1089..426f9944a 100644 --- a/tests/plat/lib/test.c +++ b/tests/plat/lib/test.c @@ -26,7 +26,10 @@ void writehex(uint32_t code) void fail(uint32_t code) { - write(1, "@@FAIL 0x", 10); + static const char fail_msg[] = "@@FAIL 0x"; + static const char nl_msg[] = "\n"; + + write(1, fail_msg, sizeof(fail_msg)-1); writehex(code); - write(1, "\n", 1); + write(1, nl_msg, sizeof(nl_msg)-1); } diff --git a/tests/plat/testdriver.sh b/tests/plat/testdriver.sh index 272c6b2b7..5e8e5b899 100755 --- a/tests/plat/testdriver.sh +++ b/tests/plat/testdriver.sh @@ -2,50 +2,51 @@ 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 +errcho() { + >&2 echo "$*" +} -case $method in - qemu-system-*) - if ! command -v $method >/dev/null 2>&1 ; then - echo "Warning: $method not installed, skipping test" - exit 0 - fi +get_test_output() { + case $method in + qemu-system-*) + if ! command -v $method >/dev/null 2>&1 ; then + errcho "Warning: $method not installed, skipping test" + echo "@@SKIPPED" + exit 0 + fi - case $method in - qemu-system-i386) img="-drive file=$img,if=floppy,format=raw" ;; - qemu-system-ppc) img="-kernel $img" ;; - esac + 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) ) - - ;; + $timeoutprog -t $timeout -- $method -nographic $img 2>&1 > $result + ;; - qemu-*) - if ! command -v $method >/dev/null 2>&1 ; then - echo "Warning: $method not installed, skipping test" - exit 0 - fi + qemu-*) + if ! command -v $method >/dev/null 2>&1 ; then + errcho "Warning: $method not installed, skipping test" + echo "@@SKIPPED" + exit 0 + fi - $method $img > $result - ;; + $method $img 2>&1 > $result + ;; - *) - echo "Error: $method not known by testdriver" - exit 1 - ;; -esac + *) + errcho "Error: $method not known by testdriver" + exit 1 + ;; + esac +} -( grep -q @@FAIL $result || ! grep -q @@FINISHED $result ) && cat $result && exit 1 +get_test_output +( grep -q '@@FAIL\|@@SKIPPED' $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 index 000000000..41bd0b6ad --- /dev/null +++ b/util/build/build.lua @@ -0,0 +1,8 @@ +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 index 000000000..fb2add562 --- /dev/null +++ b/util/build/testrunner.c @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "diagnostics.h" + +static bool timed_out = false; +static bool child_exited = false; +static char* const* command = NULL; +static int timeout = 0; +static int pid = 0; + +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 -- \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; + if (pid) + kill(pid, SIGKILL); +} + +int main(int argc, char* const argv[]) +{ + const int READ = 0; + const int WRITE = 1; + + int fds[2]; + FILE* childin; + int wstatus; + char buffer[4096]; + char* p; + + parse_arguments(argc, argv); + + 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); + } + else + { + /* Parent */ + close(fds[WRITE]); + signal(SIGALRM, sigalrm_cb); + alarm(timeout); + } + + childin = fdopen(fds[READ], "r"); + if (!childin) + fatal("cannot open pipe"); + + while (!timed_out) + { + if (!fgets(buffer, sizeof(buffer), childin)) + break; + fputs(buffer, stdout); + + p = buffer; + while (isspace(*p)) + p++; + + if (strcmp(p, "@@FINISHED\n") == 0) + break; + if (strcmp(p, "@@FINISHED\r\n") == 0) + break; + } + + /* Reached via EOF or seeing a @@FINISHED. */ + + alarm(0); + + kill(pid, SIGKILL); + waitpid(pid, &wstatus, 0); + if (timed_out) + { + fprintf(stderr, "@@TIMEDOUT\n"); + exit(1); + } + exit(WEXITSTATUS(wstatus)); +}