1implement Itest; 2 3include "sys.m"; 4 sys: Sys; 5include "string.m"; 6 str: String; 7include "draw.m"; 8include "daytime.m"; 9 daytime: Daytime; 10include "bufio.m"; 11 bufio: Bufio; 12 Iobuf: import bufio; 13include "readdir.m"; 14 readdir: Readdir; 15include "arg.m"; 16include "itslib.m"; 17 S_INFO, S_WARN, S_ERROR, S_FATAL, S_STIME, S_ETIME: import Itslib; 18include "env.m"; 19 env: Env; 20include "sh.m"; 21 22SUMFILE: con "summary"; 23MSGFILE: con "msgs"; 24README: con "README"; 25 26configfile := ""; 27cflag := -1; 28verbosity := 3; 29repcount := 1; 30recroot := ""; 31display_stderr := 0; 32display_stdout := 0; 33now := 0; 34 35stdout: ref Sys->FD; 36stderr: ref Sys->FD; 37context: ref Draw->Context; 38 39Test: adt { 40 spec: string; 41 fullspec: string; 42 cmd: Command; 43 recdir: string; 44 stdout: string; 45 stderr: string; 46 nruns: int; 47 nwarns: int; 48 nerrors: int; 49 nfatals: int; 50 failed: int; 51}; 52 53 54Itest: module 55{ 56 init: fn(ctxt: ref Draw->Context, argv: list of string); 57}; 58 59 60 61init(ctxt: ref Draw->Context, args: list of string) 62{ 63 sys = load Sys Sys->PATH; 64 stdout = sys->fildes(1); 65 stderr = sys->fildes(2); 66 context = ctxt; 67 arg := load Arg Arg->PATH; 68 if(arg == nil) 69 nomod(Arg->PATH); 70 daytime = load Daytime Daytime->PATH; 71 if(daytime == nil) 72 nomod(Daytime->PATH); 73 str = load String String->PATH; 74 bufio = load Bufio Bufio->PATH; 75 if(bufio == nil) 76 nomod(Bufio->PATH); 77 if(str == nil) 78 nomod(String->PATH); 79 readdir = load Readdir Readdir->PATH; 80 if(readdir == nil) 81 nomod(Readdir->PATH); 82 env = load Env Env->PATH; 83 if(env == nil) 84 nomod(Env->PATH); 85 arg->init(args); 86 while((o := arg->opt()) != 0) 87 case o { 88 'c' => cflag = toint("c", arg->arg(), 0, 9); 89 'e' => display_stderr++; 90 'o' => display_stdout++; 91 'r' => repcount = toint("r", arg->arg(), 0, -1); 92 'v' => verbosity = toint("v", arg->arg(), 0, 9); 93 'C' => configfile = arg->arg(); 94 'R' => recroot = arg->arg(); 95 * => usage(); 96 } 97 args = arg->argv(); 98 arg = nil; 99 testlist : array of ref Test; 100 if (args != nil) 101 testlist = arg_tests(args); 102 else if (configfile != "") 103 testlist = config_tests(configfile); 104 if (testlist == nil) 105 fatal("No tests to run"); 106 sys->pctl(Sys->FORKENV, nil); 107 if (env->setenv(Itslib->ENV_VERBOSITY, string verbosity)) 108 fatal("Failed to set environment variable " + Itslib->ENV_VERBOSITY); 109 if (repcount) 110 reps := string repcount; 111 else 112 reps = "infinite"; 113 if (len testlist == 1) ts := ""; 114 else ts = "s"; 115 if (repcount == 1) rs := ""; 116 else rs = "s"; 117 mreport(0, S_INFO, 2, sys->sprint("Starting tests - %s run%s of %d test%s", reps, rs, len testlist, ts)); 118 run := big 1; 119 120 if (recroot != nil) 121 recn := highest(recroot) + 1; 122 while (repcount == 0 || run <= big repcount) { 123 mreport(1, S_INFO, 3, sys->sprint("Starting run %bd", run)); 124 for (i:=0; i<len testlist; i++) { 125 t := testlist[i]; 126 if (recroot != nil) { 127 t.recdir = sys->sprint("%s/%d", recroot, recn++); 128 mreport(2, S_INFO, 3, sys->sprint("Recording in %s", t.recdir)); 129 rfd := sys->create(t.recdir, Sys->OREAD, Sys->DMDIR | 8r770); 130 if (rfd == nil) 131 fatal(sys->sprint("Failed to create directory %s: %r\n", t.recdir)); 132 rfd = nil; 133 } 134 runtest(t); 135 } 136 mreport(1, S_INFO, 3, sys->sprint("Finished run %bd", run)); 137 run++; 138 } 139 mreport(0, S_INFO, 2, "Finished tests"); 140} 141 142usage() 143{ 144 sys->fprint(stderr, "Usage itest [-eo] [-c cflag] [-r count] [-v vlevel] [-C cfile] [-R recroot] [testdir ...]\n"); 145 raise "fail: usage"; 146} 147 148fatal(s: string) 149{ 150 sys->fprint(stderr, "%s\n", s); 151 raise "fail: error"; 152} 153 154nomod(mod: string) 155{ 156 sys->fprint(stderr, "Failed to load %s\n", mod); 157 raise "fail: module"; 158} 159 160toint(opt, s: string, min, max: int): int 161{ 162 if (len s == 0 || str->take(s, "[0-9]+-") != s) 163 fatal(sys->sprint("no value specified for option %s", opt)); 164 v := int s; 165 if (v < min) 166 fatal(sys->sprint("option %s value is less than minimum of %d: %d", opt, v, min)); 167 if (max != -1 && v > max) 168 fatal(sys->sprint("option %s value is greater than maximum of %d: %d", opt, v, max)); 169 return v; 170} 171 172arg_tests(args: list of string): array of ref Test 173{ 174 al := len args; 175 ta := array[al] of ref Test; 176 for (i:=0; i<al; i++) { 177 tspec := hd args; 178 args = tl args; 179 ta[i] = ref Test(tspec, "", nil, "", "", "", 0, 0, 0, 0, 0); 180 tcheck(ta[i]); 181 } 182 return ta; 183} 184 185config_tests(cf: string): array of ref Test 186{ 187 cl := linelist(cf); 188 if (cl == nil) 189 fatal("No tests in config file"); 190 al := len cl; 191 ta := array[al] of ref Test; 192 for (i:=0; i<al; i++) { 193 tspec := hd cl; 194 cl = tl cl; 195 ta[i] = ref Test(tspec, "", nil, "", "", "", 0, 0, 0, 0, 0); 196 tcheck(ta[i]); 197 } 198 return ta; 199 200} 201 202highest(path: string): int 203{ 204 (da, nd) := readdir->init(path, Readdir->NAME); 205 high := 0; 206 for (i:=0; i<nd; i++) { 207 n := int da[i].name; 208 if (n > high) 209 high = n; 210 } 211 return high; 212} 213 214tcheck(t: ref Test): int 215{ 216 td := t.spec; 217 if (!checkdir(td)) { 218 fatal(sys->sprint("Failed to find test %s\n", td)); 219 return 0; 220 } 221 tf1 := t.spec + "/t.sh"; 222 tf2 := t.spec + "/t.dis"; 223 if (checkexec(tf1)) { 224 t.fullspec = tf1; 225 return 1; 226 } 227 if (checkexec(tf2)) { 228 t.fullspec = tf2; 229 return 1; 230 } 231 fatal(sys->sprint("Could not find executable files %s or %s\n", tf1, tf2)); 232 return 0; 233} 234 235checkdir(d: string): int 236{ 237 (ok, dir) := sys->stat(d); 238 if (ok != 0 || ! dir.qid.qtype & Sys->QTDIR) 239 return 0; 240 return 1; 241} 242 243checkexec(d: string): int 244{ 245 (ok, dir) := sys->stat(d); 246 if (ok != 0 || ! dir.mode & 8r100) 247 return 0; 248 return 1; 249} 250 251 252set_cflag(f: int) 253{ 254 wfile("/dev/jit", string f, 0); 255 256} 257 258runtest(t: ref Test) 259{ 260 if (t.failed) 261 return; 262 263 if (cflag != -1) { 264 mreport(0, S_INFO, 7, sys->sprint("Setting cflag to %d", cflag)); 265 set_cflag(cflag); 266 } 267 readme := t.spec + "/" + README; 268 mreport(2, S_INFO, 3, sys->sprint("Starting test %s cflag=%s", t.spec, rfile("/dev/jit"))); 269 if (verbosity > 8) 270 display_file(readme); 271 sync := chan of int; 272 spawn monitor(t, sync); 273 <-sync; 274} 275 276monitor(t: ref Test, sync: chan of int) 277{ 278 pid := sys->pctl(Sys->FORKFD|Sys->FORKNS|Sys->FORKENV|Sys->NEWPGRP, nil); 279 pa := array[2] of ref Sys->FD; 280 if (sys->pipe(pa)) 281 fatal("Failed to set up pipe"); 282 if (env->setenv(Itslib->ENV_MFD, string pa[0].fd)) 283 fatal("Failed to set environment variable " + Itslib->ENV_MFD); 284 mlfd: ref Sys->FD; 285 if (t.recdir != nil) { 286 mfile := t.recdir+"/"+MSGFILE; 287 mlfd = sys->create(mfile, Sys->OWRITE, 8r660); 288 if (mlfd == nil) 289 fatal(sys->sprint("Failed to create %s: %r'\n", mfile)); 290 t.stdout = t.recdir+"/stdout"; 291 t.stderr = t.recdir+"/stderr"; 292 } else { 293 t.stdout = "/tmp/itest.stdout"; 294 t.stderr = "/tmp/itest.stderr"; 295 } 296 cf := int rfile("/dev/jit"); 297 stime := sys->millisec(); 298 swhen := daytime->now(); 299 etime := -1; 300 rsync := chan of int; 301 spawn runit(t.fullspec, t.stdout, t.stderr, t.spec, pa[0], rsync); 302 <-rsync; 303 pa[0] = nil; 304 (nwarns, nerrors, nfatals) := (0, 0, 0); 305 while (1) { 306 mbuf := array[Sys->ATOMICIO] of byte; 307 n := sys->read(pa[1], mbuf, len mbuf); 308 if (n <= 0) break; 309 msg := string mbuf[:n]; 310 sev := int msg[0:1]; 311 verb := int msg[1:2]; 312 body := msg[2:]; 313 if (sev == S_STIME) 314 stime = int body; 315 else if (sev == S_ETIME) 316 etime = int body; 317 else { 318 if (sev == S_WARN) { 319 nwarns++; 320 t.nwarns++; 321 } 322 else if (sev == S_ERROR) { 323 nerrors++; 324 t.nerrors++; 325 } 326 else if (sev == S_FATAL) { 327 nfatals++; 328 t.nfatals++; 329 } 330 mreport(3, sev, verb, sys->sprint("%s: %s", severs(sev), body)); 331 } 332 if (mlfd != nil) 333 sys->fprint(mlfd, "%d:%s", now, msg); 334 } 335 if (etime < 0) { 336 etime = sys->millisec(); 337 if (mlfd != nil) 338 sys->fprint(mlfd, "%d:%s", now, sys->sprint("%d0%d\n", S_ETIME, etime)); 339 } 340 elapsed := etime-stime; 341 errsum := sys->sprint("WRN:%d ERR:%d FTL:%d", nwarns, nerrors, nfatals); 342 mreport(2, S_INFO, 3, sys->sprint("Finished test %s after %dms - %s", t.spec, elapsed, errsum)); 343 if (t.recdir != "") { 344 wfile(t.recdir+"/"+SUMFILE, sys->sprint("%d %d %d %s\n", swhen, elapsed, cf, t.fullspec), 1); 345 } 346 if (display_stdout) { 347 mreport(2, 0, 0, "Stdout from test:"); 348 display_file(t.stdout); 349 } 350 if (display_stderr) { 351 mreport(2, 0, 0, "Stderr from test:"); 352 display_file(t.stderr); 353 } 354 sync <-= pid; 355} 356 357runit(fullspec, sofile, sefile, tpath: string, mfd: ref Sys->FD, sync: chan of int) 358{ 359 pid := sys->pctl(Sys->NEWFD|Sys->FORKNS, mfd.fd::nil); 360 o, e: ref Sys->FD; 361 o = sys->create(sofile, Sys->OWRITE, 8r660); 362 if (o == nil) 363 treport(mfd, S_ERROR, 0, "Failed to open stdout: %r\n"); 364 else 365 sys->dup(o.fd, 1); 366 o = nil; 367 e = sys->create(sefile, Sys->OWRITE, 8r660); 368 if (e == nil) 369 treport(mfd, S_ERROR, 0, "Failed to open stderr: %r\n"); 370 else 371 sys->dup(e.fd, 2); 372 e = nil; 373 sync <-= pid; 374 args := list of {fullspec}; 375 if (fullspec[len fullspec-1] == 's') 376 cmd := load Command fullspec; 377 else { 378 cmd = load Command "/dis/sh.dis"; 379 args = fullspec :: args; 380 } 381 if (cmd == nil) { 382 treport(mfd, S_FATAL, 0, sys->sprint("Failed to load Command from %s", "/dis/sh.dis")); 383 return; 384 } 385 if (sys->chdir(tpath)) 386 treport(mfd, S_FATAL, 0, "Failed to cd to " + tpath); 387 { 388 cmd->init(context, args); 389 } exception ex { 390 "*" => 391 treport(mfd, S_FATAL, 0, sys->sprint("Exception %s in test %s", ex, fullspec)); 392 } 393} 394 395severs(sevs: int): string 396{ 397 SEVMAP := array[] of {"INF", "WRN", "ERR", "FTL"}; 398 if (sevs >= len SEVMAP) 399 sstr := "UNK"; 400 else 401 sstr = SEVMAP[sevs]; 402 return sstr; 403} 404 405 406rfile(file: string): string 407{ 408 fd := sys->open(file, Sys->OREAD); 409 if (fd == nil) return nil; 410 buf := array[Sys->ATOMICIO] of byte; 411 n := sys->read(fd, buf, len buf); 412 return string buf[:n]; 413} 414 415 416wfile(file: string, text: string, create: int): int 417{ 418 if (create) 419 fd := sys->create(file, Sys->OWRITE, 8r660); 420 else 421 fd = sys->open(file, Sys->OWRITE); 422 if (fd == nil) { 423 sys->fprint(stderr, "Failed to open %s: %r\n", file); 424 return 0; 425 } 426 a := array of byte text; 427 al := len a; 428 if (sys->write(fd, a, al) != al) { 429 sys->fprint(stderr, "Failed to write to %s: %r\n", file); 430 return 0; 431 } 432 fd = nil; 433 return 1; 434} 435 436linelist(file: string): list of string 437{ 438 bf := bufio->open(file, Bufio->OREAD); 439 if (bf == nil) 440 return nil; 441 cl : list of string; 442 while ((line := bf.gets('\n')) != nil) { 443 if (line[len line -1] == '\n') 444 line = line[:len line - 1]; 445 cl = line :: cl; 446 } 447 bf = nil; 448 return cl; 449} 450 451display_file(file: string) 452{ 453 bf := bufio->open(file, Bufio->OREAD); 454 if (bf == nil) 455 return; 456 while ((line := bf.gets('\n')) != nil) { 457 sys->print(" %s", line); 458 } 459} 460 461mreport(indent: int, sev: int, verb: int, msg: string) 462{ 463 now = daytime->now(); 464 tm := daytime->local(now); 465 time := sys->sprint("%4d%02d%02d %02d:%02d:%02d", tm.year+1900, tm.mon-1, tm.mday, tm.hour, tm.min, tm.sec); 466 pad := "---"[:indent]; 467 term := ""; 468 if (len msg && msg[len msg-1] != '\n') 469 term = "\n"; 470 if (sev || verb <= verbosity) 471 sys->print("%s %s%s%s", time, pad, msg, term); 472} 473 474 475treport(mfd: ref Sys->FD, sev: int, verb: int, msg: string) 476{ 477 sys->fprint(mfd, "%d%d%s\n", sev, verb, msg); 478} 479