summaryrefslogtreecommitdiff
path: root/appl/cmd/itest.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/itest.b')
-rw-r--r--appl/cmd/itest.b478
1 files changed, 478 insertions, 0 deletions
diff --git a/appl/cmd/itest.b b/appl/cmd/itest.b
new file mode 100644
index 00000000..aa24e4de
--- /dev/null
+++ b/appl/cmd/itest.b
@@ -0,0 +1,478 @@
+implement Itest;
+
+include "sys.m";
+ sys: Sys;
+include "string.m";
+ str: String;
+include "draw.m";
+include "daytime.m";
+ daytime: Daytime;
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+include "readdir.m";
+ readdir: Readdir;
+include "arg.m";
+include "itslib.m";
+ S_INFO, S_WARN, S_ERROR, S_FATAL, S_STIME, S_ETIME: import Itslib;
+include "env.m";
+ env: Env;
+include "sh.m";
+
+SUMFILE: con "summary";
+MSGFILE: con "msgs";
+README: con "README";
+
+configfile := "";
+cflag := -1;
+verbosity := 3;
+repcount := 1;
+recroot := "";
+display_stderr := 0;
+display_stdout := 0;
+now := 0;
+
+stdout: ref Sys->FD;
+stderr: ref Sys->FD;
+context: ref Draw->Context;
+
+Test: adt {
+ spec: string;
+ fullspec: string;
+ cmd: Command;
+ recdir: string;
+ stdout: string;
+ stderr: string;
+ nruns: int;
+ nwarns: int;
+ nerrors: int;
+ nfatals: int;
+ failed: int;
+};
+
+
+Itest: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ stdout = sys->fildes(1);
+ stderr = sys->fildes(2);
+ context = ctxt;
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ nomod(Arg->PATH);
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ nomod(Daytime->PATH);
+ str = load String String->PATH;
+ bufio = load Bufio Bufio->PATH;
+ if(bufio == nil)
+ nomod(Bufio->PATH);
+ if(str == nil)
+ nomod(String->PATH);
+ readdir = load Readdir Readdir->PATH;
+ if(readdir == nil)
+ nomod(Readdir->PATH);
+ env = load Env Env->PATH;
+ if(env == nil)
+ nomod(Env->PATH);
+ arg->init(args);
+ while((o := arg->opt()) != 0)
+ case o {
+ 'c' => cflag = toint("c", arg->arg(), 0, 9);
+ 'e' => display_stderr++;
+ 'o' => display_stdout++;
+ 'r' => repcount = toint("r", arg->arg(), 0, -1);
+ 'v' => verbosity = toint("v", arg->arg(), 0, 9);
+ 'C' => configfile = arg->arg();
+ 'R' => recroot = arg->arg();
+ * => usage();
+ }
+ args = arg->argv();
+ arg = nil;
+ testlist : array of ref Test;
+ if (args != nil)
+ testlist = arg_tests(args);
+ else if (configfile != "")
+ testlist = config_tests(configfile);
+ if (testlist == nil)
+ fatal("No tests to run");
+ sys->pctl(Sys->FORKENV, nil);
+ if (env->setenv(Itslib->ENV_VERBOSITY, string verbosity))
+ fatal("Failed to set environment variable " + Itslib->ENV_VERBOSITY);
+ if (repcount)
+ reps := string repcount;
+ else
+ reps = "infinite";
+ if (len testlist == 1) ts := "";
+ else ts = "s";
+ if (repcount == 1) rs := "";
+ else rs = "s";
+ mreport(0, S_INFO, 2, sys->sprint("Starting tests - %s run%s of %d test%s", reps, rs, len testlist, ts));
+ run := big 1;
+ tlist := testlist;
+ if (recroot != nil)
+ recn := highest(recroot) + 1;
+ while (repcount == 0 || run <= big repcount) {
+ mreport(1, S_INFO, 3, sys->sprint("Starting run %bd", run));
+ for (i:=0; i<len testlist; i++) {
+ t := testlist[i];
+ if (recroot != nil) {
+ t.recdir = sys->sprint("%s/%d", recroot, recn++);
+ mreport(2, S_INFO, 3, sys->sprint("Recording in %s", t.recdir));
+ rfd := sys->create(t.recdir, Sys->OREAD, Sys->DMDIR | 8r770);
+ if (rfd == nil)
+ fatal(sys->sprint("Failed to create directory %s: %r\n", t.recdir));
+ rfd = nil;
+ }
+ runtest(t);
+ }
+ mreport(1, S_INFO, 3, sys->sprint("Finished run %bd", run));
+ run++;
+ }
+ mreport(0, S_INFO, 2, "Finished tests");
+}
+
+usage()
+{
+ sys->fprint(stderr, "Usage itest [-eo] [-c cflag] [-r count] [-v vlevel] [-C cfile] [-R recroot] [testdir ...]\n");
+ raise "fail: usage";
+}
+
+fatal(s: string)
+{
+ sys->fprint(stderr, "%s\n", s);
+ raise "fail: error";
+}
+
+nomod(mod: string)
+{
+ sys->fprint(stderr, "Failed to load %s\n", mod);
+ raise "fail: module";
+}
+
+toint(opt, s: string, min, max: int): int
+{
+ if (len s == 0 || str->take(s, "[0-9]+-") != s)
+ fatal(sys->sprint("no value specified for option %s", opt));
+ v := int s;
+ if (v < min)
+ fatal(sys->sprint("option %s value is less than minimum of %d: %d", opt, v, min));
+ if (max != -1 && v > max)
+ fatal(sys->sprint("option %s value is greater than maximum of %d: %d", opt, v, max));
+ return v;
+}
+
+arg_tests(args: list of string): array of ref Test
+{
+ al := len args;
+ ta := array[al] of ref Test;
+ for (i:=0; i<al; i++) {
+ tspec := hd args;
+ args = tl args;
+ ta[i] = ref Test(tspec, "", nil, "", "", "", 0, 0, 0, 0, 0);
+ tcheck(ta[i]);
+ }
+ return ta;
+}
+
+config_tests(cf: string): array of ref Test
+{
+ cl := linelist(cf);
+ if (cl == nil)
+ fatal("No tests in config file");
+ al := len cl;
+ ta := array[al] of ref Test;
+ for (i:=0; i<al; i++) {
+ tspec := hd cl;
+ cl = tl cl;
+ ta[i] = ref Test(tspec, "", nil, "", "", "", 0, 0, 0, 0, 0);
+ tcheck(ta[i]);
+ }
+ return ta;
+
+}
+
+highest(path: string): int
+{
+ (da, nd) := readdir->init(path, Readdir->NAME);
+ high := 0;
+ for (i:=0; i<nd; i++) {
+ n := int da[i].name;
+ if (n > high)
+ high = n;
+ }
+ return high;
+}
+
+tcheck(t: ref Test): int
+{
+ td := t.spec;
+ if (!checkdir(td)) {
+ fatal(sys->sprint("Failed to find test %s\n", td));
+ return 0;
+ }
+ tf1 := t.spec + "/t.sh";
+ tf2 := t.spec + "/t.dis";
+ if (checkexec(tf1)) {
+ t.fullspec = tf1;
+ return 1;
+ }
+ if (checkexec(tf2)) {
+ t.fullspec = tf2;
+ return 1;
+ }
+ fatal(sys->sprint("Could not find executable files %s or %s\n", tf1, tf2));
+ return 0;
+}
+
+checkdir(d: string): int
+{
+ (ok, dir) := sys->stat(d);
+ if (ok != 0 || ! dir.qid.qtype & Sys->QTDIR)
+ return 0;
+ return 1;
+}
+
+checkexec(d: string): int
+{
+ (ok, dir) := sys->stat(d);
+ if (ok != 0 || ! dir.mode & 8r100)
+ return 0;
+ return 1;
+}
+
+
+set_cflag(f: int)
+{
+ wfile("/dev/jit", string f, 0);
+
+}
+
+runtest(t: ref Test)
+{
+ if (t.failed)
+ return;
+ path := t.fullspec;
+ if (cflag != -1) {
+ mreport(0, S_INFO, 7, sys->sprint("Setting cflag to %d", cflag));
+ set_cflag(cflag);
+ }
+ readme := t.spec + "/" + README;
+ mreport(2, S_INFO, 3, sys->sprint("Starting test %s cflag=%s", t.spec, rfile("/dev/jit")));
+ if (verbosity > 8)
+ display_file(readme);
+ sync := chan of int;
+ spawn monitor(t, sync);
+ pid := <-sync;
+}
+
+monitor(t: ref Test, sync: chan of int)
+{
+ pid := sys->pctl(Sys->FORKFD|Sys->FORKNS|Sys->FORKENV|Sys->NEWPGRP, nil);
+ pa := array[2] of ref Sys->FD;
+ if (sys->pipe(pa))
+ fatal("Failed to set up pipe");
+ if (env->setenv(Itslib->ENV_MFD, string pa[0].fd))
+ fatal("Failed to set environment variable " + Itslib->ENV_MFD);
+ mlfd: ref Sys->FD;
+ if (t.recdir != nil) {
+ mfile := t.recdir+"/"+MSGFILE;
+ mlfd = sys->create(mfile, Sys->OWRITE, 8r660);
+ if (mlfd == nil)
+ fatal(sys->sprint("Failed to create %s: %r'\n", mfile));
+ t.stdout = t.recdir+"/stdout";
+ t.stderr = t.recdir+"/stderr";
+ } else {
+ t.stdout = "/tmp/itest.stdout";
+ t.stderr = "/tmp/itest.stderr";
+ }
+ cf := int rfile("/dev/jit");
+ stime := sys->millisec();
+ swhen := daytime->now();
+ etime := -1;
+ rsync := chan of int;
+ spawn runit(t.fullspec, t.stdout, t.stderr, t.spec, pa[0], rsync);
+ rpid := <-rsync;
+ pa[0] = nil;
+ (nwarns, nerrors, nfatals) := (0, 0, 0);
+ while (1) {
+ mbuf := array[Sys->ATOMICIO] of byte;
+ n := sys->read(pa[1], mbuf, len mbuf);
+ if (n <= 0) break;
+ msg := string mbuf[:n];
+ sev := int msg[0:1];
+ verb := int msg[1:2];
+ body := msg[2:];
+ if (sev == S_STIME)
+ stime = int body;
+ else if (sev == S_ETIME)
+ etime = int body;
+ else {
+ if (sev == S_WARN) {
+ nwarns++;
+ t.nwarns++;
+ }
+ else if (sev == S_ERROR) {
+ nerrors++;
+ t.nerrors++;
+ }
+ else if (sev == S_FATAL) {
+ nfatals++;
+ t.nfatals++;
+ }
+ mreport(3, sev, verb, sys->sprint("%s: %s", severs(sev), body));
+ }
+ if (mlfd != nil)
+ sys->fprint(mlfd, "%d:%s", now, msg);
+ }
+ if (etime < 0) {
+ etime = sys->millisec();
+ if (mlfd != nil)
+ sys->fprint(mlfd, "%d:%s", now, sys->sprint("%d0%d\n", S_ETIME, etime));
+ }
+ elapsed := etime-stime;
+ errsum := sys->sprint("WRN:%d ERR:%d FTL:%d", nwarns, nerrors, nfatals);
+ mreport(2, S_INFO, 3, sys->sprint("Finished test %s after %dms - %s", t.spec, elapsed, errsum));
+ if (t.recdir != "") {
+ wfile(t.recdir+"/"+SUMFILE, sys->sprint("%d %d %d %s\n", swhen, elapsed, cf, t.fullspec), 1);
+ }
+ if (display_stdout) {
+ mreport(2, 0, 0, "Stdout from test:");
+ display_file(t.stdout);
+ }
+ if (display_stderr) {
+ mreport(2, 0, 0, "Stderr from test:");
+ display_file(t.stderr);
+ }
+ sync <-= pid;
+}
+
+runit(fullspec, sofile, sefile, tpath: string, mfd: ref Sys->FD, sync: chan of int)
+{
+ pid := sys->pctl(Sys->NEWFD|Sys->FORKNS, mfd.fd::nil);
+ o, e: ref Sys->FD;
+ o = sys->create(sofile, Sys->OWRITE, 8r660);
+ if (o == nil)
+ treport(mfd, S_ERROR, 0, "Failed to open stdout: %r\n");
+ else
+ sys->dup(o.fd, 1);
+ o = nil;
+ e = sys->create(sefile, Sys->OWRITE, 8r660);
+ if (e == nil)
+ treport(mfd, S_ERROR, 0, "Failed to open stderr: %r\n");
+ else
+ sys->dup(e.fd, 2);
+ e = nil;
+ sync <-= pid;
+ args := list of {fullspec};
+ if (fullspec[len fullspec-1] == 's')
+ cmd := load Command fullspec;
+ else {
+ cmd = load Command "/dis/sh.dis";
+ args = fullspec :: args;
+ }
+ if (cmd == nil) {
+ treport(mfd, S_FATAL, 0, sys->sprint("Failed to load Command from %s", "/dis/sh.dis"));
+ return;
+ }
+ if (sys->chdir(tpath))
+ treport(mfd, S_FATAL, 0, "Failed to cd to " + tpath);
+ {
+ cmd->init(context, args);
+ } exception ex {
+ "*" =>
+ treport(mfd, S_FATAL, 0, sys->sprint("Exception %s in test %s", ex, fullspec));
+ }
+}
+
+severs(sevs: int): string
+{
+ SEVMAP := array[] of {"INF", "WRN", "ERR", "FTL"};
+ if (sevs >= len SEVMAP)
+ sstr := "UNK";
+ else
+ sstr = SEVMAP[sevs];
+ return sstr;
+}
+
+
+rfile(file: string): string
+{
+ fd := sys->open(file, Sys->OREAD);
+ if (fd == nil) return nil;
+ buf := array[Sys->ATOMICIO] of byte;
+ n := sys->read(fd, buf, len buf);
+ return string buf[:n];
+}
+
+
+wfile(file: string, text: string, create: int): int
+{
+ if (create)
+ fd := sys->create(file, Sys->OWRITE, 8r660);
+ else
+ fd = sys->open(file, Sys->OWRITE);
+ if (fd == nil) {
+ sys->fprint(stderr, "Failed to open %s: %r\n", file);
+ return 0;
+ }
+ a := array of byte text;
+ al := len a;
+ if (sys->write(fd, a, al) != al) {
+ sys->fprint(stderr, "Failed to write to %s: %r\n", file);
+ return 0;
+ }
+ fd = nil;
+ return 1;
+}
+
+linelist(file: string): list of string
+{
+ bf := bufio->open(file, Bufio->OREAD);
+ if (bf == nil)
+ return nil;
+ cl : list of string;
+ while ((line := bf.gets('\n')) != nil) {
+ if (line[len line -1] == '\n')
+ line = line[:len line - 1];
+ cl = line :: cl;
+ }
+ bf = nil;
+ return cl;
+}
+
+display_file(file: string)
+{
+ bf := bufio->open(file, Bufio->OREAD);
+ if (bf == nil)
+ return;
+ while ((line := bf.gets('\n')) != nil) {
+ sys->print(" %s", line);
+ }
+}
+
+mreport(indent: int, sev: int, verb: int, msg: string)
+{
+ now = daytime->now();
+ tm := daytime->local(now);
+ time := sys->sprint("%4d%02d%02d %02d:%02d:%02d", tm.year+1900, tm.mon-1, tm.mday, tm.hour, tm.min, tm.sec);
+ pad := "---"[:indent];
+ term := "";
+ if (len msg && msg[len msg-1] != '\n')
+ term = "\n";
+ if (sev || verb <= verbosity)
+ sys->print("%s %s%s%s", time, pad, msg, term);
+}
+
+
+treport(mfd: ref Sys->FD, sev: int, verb: int, msg: string)
+{
+ sys->fprint(mfd, "%d%d%s\n", sev, verb, msg);
+}