wwfixture: declarative fixture runner

Discovers test/wcc/data, hard-pins corpus identity (1,224 fixtures,
314 error / 12 compile / 136 run / 762 run-exit, sha256 name hash),
schedules both frontend cells per fixture through os.exec with
per-cell capture dirs, and emits the strict wwfix TSV result stream.
integration.sh is the black-box owner of the CLI/protocol boundary.
This commit is contained in:
2026-08-07 23:02:24 +09:00
parent d456263f7e
commit aa0f77b891
7 changed files with 2252 additions and 0 deletions

View File

@@ -0,0 +1,150 @@
package wwfixture;
import fmt;
import os;
import strings;
type options = struct {
root: str,
patterns: []str,
jobs: i32,
list: bool,
keep: bool,
timeoutms: i64,
input: str,
};
fn usage() void = {
fmt.errorln("usage: wwfixture [-root path] [-run glob]... [-list] [-j N] [-keep] [-timeout-ms N]");
fmt.errorln(" wwfixture validate [-root path] [-run glob]... STREAM");
};
fn positive(s: str, limit: i64, out: *i64) bool = {
if (s.len == 0) { return false; };
let n: i64 = 0i64;
let i: i32 = 0;
for (i < s.len) {
let c: u8 = s[i];
if (c < '0': u8 || c > '9': u8) { return false; };
let digit: i64 = (c - '0': u8): i64;
if (n > (limit - digit) / 10i64) { return false; };
n = n * 10i64 + digit;
i += 1;
};
if (n == 0i64) { return false; };
*out = n;
return true;
};
fn initoptions(out: *options) void = {
out.root = "";
let patterns: []str = alloc([], 16u64)!;
out.patterns = patterns;
out.jobs = 4;
out.list = false;
out.keep = false;
out.timeoutms = 30000i64;
out.input = "";
};
fn parseoptions(args: []str, start: i32, validate: bool,
out: *options) bool = {
initoptions(out);
let i: i32 = start;
for (i < args.len) {
let a: str = args[i];
if (a == "-root" || a == "-run") {
if (i + 1 >= args.len) { return false; };
if (a == "-root") {
if (out.root.len != 0) { return false; };
out.root = args[i + 1];
} else { append(out.patterns, args[i + 1]); };
i += 2;
continue;
};
if (!validate && a == "-list") {
if (out.list) { return false; };
out.list = true; i += 1; continue;
};
if (!validate && a == "-keep") {
if (out.keep) { return false; };
out.keep = true; i += 1; continue;
};
if (!validate && (a == "-j" || a == "-timeout-ms")) {
if (i + 1 >= args.len) { return false; };
let n: i64 = 0i64;
let limit: i64 = 64i64;
if (a == "-timeout-ms") { limit = 3600000i64; };
if (!positive(args[i + 1], limit, &n)) { return false; };
if (a == "-j") { out.jobs = n: i32; }
else { out.timeoutms = n; };
i += 2;
continue;
};
if (validate && a.len > 0 && a[0] != '-': u8 && out.input.len == 0) {
out.input = a; i += 1; continue;
};
return false;
};
if (validate && out.input.len == 0) { return false; };
return true;
};
fn listfixtures(c: *corpus, ids: []identity) bool = {
let i: i32 = 0;
for (i < ids.len) {
let f: *fixture = &c.fixtures[ids[i].fixtureindex];
let line: str = strings.concat(f.id, "\t", stageword(ids[i].stage), "\n");
if (!writeall(os.STDOUT_FILENO, line)) { return false; };
i += 1;
};
return true;
};
fn validatecommand(opts: *options) int = {
let c: corpus;
if (!loadcorpus(opts.root, &c)) {
fmt.errorln("wwfixture validate: ", error());
return 1;
};
let ids: []identity;
selectidentities(&c, opts.patterns, &ids);
let data: str;
if (!readfile(opts.input, &data)) {
fmt.errorln("wwfixture validate: ", error());
return 1;
};
if (!validatestream(data, &c, ids)) {
fmt.errorln("wwfixture validate: ", protocolerror());
return 1;
};
return 0;
};
export fn command(args: []str) int = {
if (args.len < 1) { usage(); return 2; };
let validate: bool = args.len > 1 && args[1] == "validate";
let start: i32 = 1;
if (validate) { start = 2; };
let opts: options;
if (!parseoptions(args, start, validate, &opts)) {
usage();
return 2;
};
if (validate) { return validatecommand(&opts); };
let c: corpus;
if (!loadcorpus(opts.root, &c)) {
fmt.errorln("wwfixture: ", error());
return 1;
};
let ids: []identity;
selectidentities(&c, opts.patterns, &ids);
if (opts.list) {
if (!listfixtures(&c, ids)) {
fmt.errorln("wwfixture: result output failed");
return 1;
};
return 0;
};
return runfixtures(&opts, &c, ids);
};

View File

@@ -0,0 +1,464 @@
package wwfixture;
import crypto.sha256;
import encoding.hex;
import fnmatch;
import hash;
import os;
import strings;
def dirbufsize: i32 = 8192;
def typemask: u32 = 61440u32;
let fixtureerror: str = "";
export fn error() str = { return fixtureerror; };
fn seterror(message: str, detail: str) bool = {
if (detail.len == 0) { fixtureerror = strings.dup(message); }
else { fixtureerror = strings.concat(message, ": ", detail); };
return false;
};
fn startswith(s: str, prefix: str) bool = {
if (s.len < prefix.len) { return false; };
let i: i32 = 0;
for (i < prefix.len) {
if (s[i] != prefix[i]) { return false; };
i += 1;
};
return true;
};
fn safeatom(s: str) bool = {
if (s.len == 0) { return false; };
let i: i32 = 0;
for (i < s.len) {
let c: u8 = s[i];
if (!((c >= 'a': u8 && c <= 'z': u8)
|| (c >= '0': u8 && c <= '9': u8) || c == '_': u8)) {
return false;
};
i += 1;
};
return true;
};
fn join(a: str, b: str) str = {
if (a.len == 0) { return strings.dup(b); };
if (a[a.len - 1] == '/': u8) { return strings.concat(a, b); };
return strings.concat(a, "/", b);
};
fn currentdir(out: *str) bool = {
let b: []u8 = alloc([], os.PATH_MAX: u64)!;
b.len = os.PATH_MAX;
let n: i64 = os.getcwd(b.ptr, b.len: u64);
if (n <= 1i64 || n > b.len: i64) { return seterror("cannot resolve current directory", ""); };
b.len = (n - 1i64): i32;
*out = strings.dup(strings.frombytes(b));
return true;
};
fn absoluteroot(root: str, out: *str) bool = {
if (root.len == 0) { return currentdir(out); };
if (root[0] == '/': u8) { *out = strings.dup(root); return true; };
let cwd: str;
if (!currentdir(&cwd)) { return false; };
*out = join(cwd, root);
return true;
};
fn readfile(path: str, out: *str) bool = {
let st: os.filestat;
match (os.stat(&st, path)) {
case void => void;
case let e: os.oserror => return seterror("cannot stat file", path);
};
if (st.sz > 2147483647u64) { return seterror("file is too large", path); };
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
if (fd < 0) { return seterror("cannot open file", path); };
if (st.sz == 0u64) {
if (os.close(fd) != 0) { return seterror("cannot close file", path); };
let empty: str;
empty.ptr = nil: *u8; empty.len = 0;
*out = empty;
return true;
};
let b: []u8 = alloc([], st.sz)!;
b.len = st.sz: i32;
let got: i64 = match (os.readall(fd, b.ptr, st.sz)) {
case let n: i64 => yield n;
case let e: os.oserror => {
os.close(fd);
return seterror("cannot read file", path);
};
};
if (os.close(fd) != 0) { return seterror("cannot close file", path); };
if (got != st.sz: i64) { return seterror("short read", path); };
*out = strings.dup(strings.frombytes(b));
return true;
};
fn writeall(fd: i32, s: str) bool = {
let off: i32 = 0;
for (off < s.len) {
let n: i64 = os.write(fd, s.ptr + (off: u64), (s.len - off): u64);
if (n == -4i64) { continue; };
if (n <= 0i64) { return false; };
off += n: i32;
};
return true;
};
fn writefile(path: str, data: str) bool = {
let fd: i32 = os.open(path,
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 384i32);
if (fd < 0) { return false; };
let ok: bool = writeall(fd, data);
if (os.close(fd) != 0) { ok = false; };
return ok;
};
fn writeharness(dir: str, message: str) bool = {
let path: str = join(dir, "harness.txt");
return writefile(path, strings.concat(message, "\n"));
};
fn direntname(buf: []u8, off: i32, reclen: i32, out: *str) bool = {
if (reclen < 20 || off < 0 || off + reclen > buf.len) { return false; };
let first: i32 = off + 19;
let last: i32 = first;
let limit: i32 = off + reclen;
for (last < limit && buf[last] != 0u8) { last += 1; };
if (last == limit) { return false; };
out.ptr = buf.ptr + (first: u64);
out.len = last - first;
return true;
};
fn filekind(path: str, out: *u32) i32 = {
let st: os.filestat;
match (os.lstat(&st, path)) {
case void => { *out = (st.mode: u32) & typemask; return 1; };
case let e: os.oserror => {
if ((e: i64) == -2i64) { return 0; };
seterror("cannot inspect path", path);
return -1;
};
};
};
fn sortnames(names: []str) void = {
let i: i32 = 1;
for (i < names.len) {
let v: str = names[i];
let j: i32 = i;
for (j > 0 && strings.compare(names[j - 1], v) > 0) {
names[j] = names[j - 1];
j -= 1;
};
names[j] = v;
i += 1;
};
};
fn parsenumber(s: str, out: *i32) bool = {
if (s.len == 0 || (s.len > 1 && s[0] == '0': u8)) { return false; };
let value: i32 = 0;
let i: i32 = 0;
for (i < s.len) {
let c: u8 = s[i];
if (c < '0': u8 || c > '9': u8) { return false; };
value = value * 10 + ((c - '0': u8): i32);
if (value > 255) { return false; };
i += 1;
};
*out = value;
return true;
};
fn parsedirective(path: str, data: str, out: *fixture) bool = {
if (data.len == 0) { return seterror("empty fixture", path); };
let end: i32 = 0;
for (end < data.len && data[end] != 10u8) { end += 1; };
if (end == data.len) { return seterror("fixture directive line is not LF terminated", path); };
let line: str;
line.ptr = data.ptr; line.len = end;
out.diagnostic = "";
out.wwdiagnostic = "";
out.exitcode = 0;
if (line == "//ww:run") { out.directive = directive.RUN; return true; };
let runexit: str = "//ww:run-exit ";
if (startswith(line, runexit)) {
let tail: str;
tail.ptr = line.ptr + (runexit.len: u64);
tail.len = line.len - runexit.len;
if (!parsenumber(tail, &out.exitcode)) {
return seterror("malformed run-exit directive", path);
};
out.directive = directive.RUNEXIT;
return true;
};
let stagedprefix: str = "//ww:error c \"";
if (startswith(line, stagedprefix)) {
let cend: i32 = stagedprefix.len;
for (cend < line.len && line[cend] != 34u8) { cend += 1; };
if (cend == stagedprefix.len || cend + 6 >= line.len
|| line[cend] != 34u8 || line[cend + 1] != ' ': u8
|| line[cend + 2] != 'w': u8 || line[cend + 3] != 'w': u8
|| line[cend + 4] != ' ': u8 || line[cend + 5] != 34u8
|| line[line.len - 1] != 34u8 || cend + 6 >= line.len - 1) {
return seterror("malformed stage-specific error directive", path);
};
let cbody: str;
cbody.ptr = line.ptr + (stagedprefix.len: u64);
cbody.len = cend - stagedprefix.len;
let wwbody: str;
wwbody.ptr = line.ptr + ((cend + 6): u64);
wwbody.len = line.len - cend - 7;
let i: i32 = 0;
for (i < cbody.len) {
if (cbody[i] == 34u8 || cbody[i] == 9u8 || cbody[i] == 13u8) {
return seterror("malformed stage-specific error directive", path);
};
i += 1;
};
i = 0;
for (i < wwbody.len) {
if (wwbody[i] == 34u8 || wwbody[i] == 9u8 || wwbody[i] == 13u8) {
return seterror("malformed stage-specific error directive", path);
};
i += 1;
};
out.directive = directive.ERROR;
out.diagnostic = strings.dup(cbody);
out.wwdiagnostic = strings.dup(wwbody);
return true;
};
let errprefix: str = "//ww:error \"";
if (startswith(line, errprefix) && line.len > errprefix.len + 1
&& line[line.len - 1] == 34u8) {
let body: str;
body.ptr = line.ptr + (errprefix.len: u64);
body.len = line.len - errprefix.len - 1;
let i: i32 = 0;
for (i < body.len) {
if (body[i] == 34u8 || body[i] == 9u8 || body[i] == 13u8) {
return seterror("malformed error directive", path);
};
i += 1;
};
out.directive = directive.ERROR;
out.diagnostic = strings.dup(body);
return true;
};
if (line == "//ww:compile") {
out.directive = directive.COMPILE;
return true;
};
return seterror("malformed fixture directive", path);
};
fn discovernames(root: str, out: *[]str) bool = {
let datadir: str = join(root, "test/wcc/data");
let fd: i32 = os.open(datadir, os.flag.RDONLY, 0i32);
if (fd < 0) { return seterror("cannot open fixture root", datadir); };
let names: []str = alloc([], 128u64)!;
let buf: []u8 = alloc([], dirbufsize: u64)!;
buf.len = dirbufsize;
for (true) {
let n: i64 = os.getdents64(fd, buf.ptr, buf.len: u64);
if (n < 0i64) { os.close(fd); return seterror("cannot enumerate fixture root", datadir); };
if (n == 0i64) { break; };
let off: i32 = 0;
for (off < n: i32) {
if (off + 18 >= n: i32) { os.close(fd); return seterror("malformed directory record", datadir); };
let reclen: i32 = (buf[off + 16]: i32) + ((buf[off + 17]: i32) * 256);
if (reclen <= 0 || off + reclen > n: i32) {
os.close(fd); return seterror("malformed directory record", datadir);
};
let name: str;
if (!direntname(buf, off, reclen, &name)) {
os.close(fd); return seterror("malformed directory record", datadir);
};
if (name != "." && name != "..") {
let dir: str = join(datadir, name);
let kind: u32 = 0u32;
let status: i32 = filekind(dir, &kind);
if (status < 0) { os.close(fd); return false; };
if (status == 1 && kind == (os.mode.LINK: u32)) {
os.close(fd); return seterror("symlink fixture directory is forbidden", dir);
};
if (status == 1 && kind == (os.mode.DIR: u32)) {
let source: str = join(dir, "case.ww");
let sourcekind: u32 = 0u32;
let sourcestatus: i32 = filekind(source, &sourcekind);
if (sourcestatus < 0) { os.close(fd); return false; };
if (sourcestatus == 1 && sourcekind == (os.mode.LINK: u32)) {
os.close(fd); return seterror("symlink fixture source is forbidden", source);
};
if (sourcestatus == 1 && sourcekind == (os.mode.REG: u32)) {
if (!safeatom(name)) { os.close(fd); return seterror("invalid fixture directory name", name); };
append(names, strings.dup(name));
};
};
};
off += reclen;
};
};
if (os.close(fd) != 0) { return seterror("cannot close fixture root", datadir); };
sortnames(names);
*out = names;
return true;
};
fn identityhash(names: []str) str = {
let state: sha256.state = sha256.sha256();
let h: *hash.hash = (&state): *hash.hash;
let nl: [1]u8 = [10u8];
let i: i32 = 0;
for (i < names.len) {
hash.write(h, strings.toutf8(names[i]));
hash.write(h, nl[0:1]);
i += 1;
};
let digest: [32]u8;
hash.sum(h, digest[0:32]);
return strings.dup(hex.encodestr(digest[0:32]));
};
fn loadcorpus(rootarg: str, out: *corpus) bool = {
fixtureerror = "";
let root: str;
if (!absoluteroot(rootarg, &root)) { return false; };
let names: []str;
if (!discovernames(root, &names)) { return false; };
let digest: str = identityhash(names);
if (names.len != corpuscount || digest != corpushash) {
return seterror("compiler corpus identity drift",
strings.concat("expected ", rundecimal(corpuscount), "/",
corpushash, ", observed ", rundecimal(names.len), "/", digest));
};
let fixtures: []fixture = alloc([], names.len: u64)!;
let errors: i32 = 0;
let compiles: i32 = 0;
let runs: i32 = 0;
let runexits: i32 = 0;
let i: i32 = 0;
for (i < names.len) {
let f: fixture;
f.name = names[i];
f.id = strings.concat("compiler.", f.name);
f.source = join(join(join(root, "test/wcc/data"), f.name), "case.ww");
let data: str;
if (!readfile(f.source, &data)) { return false; };
if (!parsedirective(f.source, data, &f)) { return false; };
if (f.directive == directive.ERROR) { errors += 1; }
else { if (f.directive == directive.COMPILE) { compiles += 1; }
else { if (f.directive == directive.RUN) { runs += 1; }
else { if (f.directive == directive.RUNEXIT) { runexits += 1; };
}; }; };
append(fixtures, f);
i += 1;
};
let derivednative: i32 = fixtures.len * 2;
if (errors != errorcount || compiles != compilecount
|| runs != runcount || runexits != runexitcount
|| derivednative != nativecount) {
return seterror("compiler stage matrix drift",
strings.concat("expected ", rundecimal(errorcount), " errors, ",
rundecimal(compilecount), " compiles, ",
rundecimal(runcount), " runs, and ",
rundecimal(runexitcount), " run-exits"));
};
out.root = root;
out.fixtures = fixtures;
return true;
};
fn cellname(f: *fixture, s: stage) str = {
return strings.concat(f.id, "/", stageword(s));
};
fn selectedby(patterns: []str, f: *fixture, s: stage) bool = {
if (patterns.len == 0) { return true; };
let full: str = cellname(f, s);
let i: i32 = 0;
for (i < patterns.len) {
if (fnmatch.fnmatch(patterns[i], f.id, fnmatch.flag.NONE)
|| fnmatch.fnmatch(patterns[i], full, fnmatch.flag.NONE)) {
return true;
};
i += 1;
};
return false;
};
fn selectidentities(c: *corpus, patterns: []str, out: *[]identity) bool = {
let ids: []identity = alloc([], nativecount: u64)!;
let ordinal: i32 = 0;
let i: i32 = 0;
for (i < c.fixtures.len) {
let si: i32 = 0;
for (si < 2) {
let s: stage = stage.C;
if (si == 1) { s = stage.WW; };
if (selectedby(patterns, &c.fixtures[i], s)) {
ordinal += 1;
let id: identity;
id.ordinal = ordinal;
id.fixtureindex = i;
id.stage = s;
append(ids, id);
};
si += 1;
};
i += 1;
};
*out = ids;
return true;
};
fn isdot(name: str) bool = { return name == "." || name == ".."; };
// Callers restrict this to directories minted beneath one temp.dir result;
// lstat plus the opened-directory inode check keeps cleanup from following a
// replaced path outside that ownership boundary.
fn removeall(path: str) bool = {
let st: os.filestat;
match (os.lstat(&st, path)) {
case void => void;
case let e: os.oserror => return false;
};
let kind: u32 = (st.mode: u32) & typemask;
if (kind != (os.mode.DIR: u32)) { return os.remove(path) == 0; };
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
if (fd < 0) { return false; };
let opened: os.filestat;
match (os.fstat(&opened, fd)) {
case void => void;
case let e: os.oserror => { os.close(fd); return false; };
};
if (((opened.mode: u32) & typemask) != (os.mode.DIR: u32)
|| opened.inode != st.inode) { os.close(fd); return false; };
let ok: bool = true;
let buf: []u8 = alloc([], dirbufsize: u64)!;
buf.len = dirbufsize;
let n: i64 = os.getdents64(fd, buf.ptr, buf.len: u64);
for (n > 0i64) {
let off: i32 = 0;
for (off < n: i32) {
let reclen: i32 = (buf[off + 16]: i32) + ((buf[off + 17]: i32) * 256);
if (reclen < 20 || off + reclen > n: i32) { ok = false; break; };
let name: str;
if (!direntname(buf, off, reclen, &name)) { ok = false; break; };
if (!isdot(name) && !removeall(join(path, name))) { ok = false; };
off += reclen;
};
n = os.getdents64(fd, buf.ptr, buf.len: u64);
};
if (n < 0i64 || os.close(fd) != 0) { ok = false; };
if (os.rmdir(path) != 0) { ok = false; };
return ok;
};

View File

@@ -0,0 +1,524 @@
package wwfixture;
import os;
import strings;
let protocolerr: str = "";
export fn protocolerror() str = { return protocolerr; };
fn protocolfail(message: str) bool = {
protocolerr = strings.dup(message);
return false;
};
fn protocolput(b: *[]u8, s: str) void = {
let i: i32 = 0;
for (i < s.len) {
append(*b, s[i]);
i += 1;
};
};
fn protocolfield(b: *[]u8, s: str) void = {
append(*b, '\t': u8);
protocolput(b, s);
};
fn protocolu64(b: *[]u8, value: u64) void = {
let digits: [20]u8;
let n: i32 = 0;
let v: u64 = value;
if (v == 0u64) {
append(*b, '0': u8);
return;
};
for (v != 0u64) {
digits[n] = '0': u8 + (v % 10u64): u8;
n += 1;
v /= 10u64;
};
for (n > 0) {
n -= 1;
append(*b, digits[n]);
};
};
fn protocoli64(b: *[]u8, value: i64) void = {
if (value < 0i64) {
append(*b, '-': u8);
protocolu64(b, (-value): u64);
return;
};
protocolu64(b, value: u64);
};
fn protocolintfield(b: *[]u8, value: i64) void = {
append(*b, '\t': u8);
protocoli64(b, value);
};
fn protocolnewline(b: *[]u8) void = { append(*b, '\n': u8); };
fn protocolwriteall(fd: i32, b: []u8) bool = {
let off: i32 = 0;
for (off < b.len) {
let n: i64 = os.write(fd, b.ptr + (off: u64),
(b.len - off): u64);
if (n == -4i64) { continue; };
if (n <= 0i64) { return protocolfail("cannot write terminal stream"); };
off += n: i32;
};
return true;
};
fn safeprotocolfield(s: str) bool = {
if (s.len == 0) { return false; };
let i: i32 = 0;
for (i < s.len) {
if (s[i] < 32u8 || s[i] == 127u8) { return false; };
i += 1;
};
return true;
};
fn validrunid(s: str) bool = {
if (s.len != 16) { return false; };
let i: i32 = 0;
for (i < s.len) {
let c: u8 = s[i];
if (!((c >= '0': u8 && c <= '9': u8)
|| (c >= 'a': u8 && c <= 'f': u8))) { return false; };
i += 1;
};
return true;
};
fn validstage(s: stage) bool = { return s == stage.C || s == stage.WW; };
fn validphase(p: phase) bool = {
return p == phase.COMPILE || p == phase.BUILD || p == phase.RUN;
};
fn validverdict(v: verdict) bool = {
return v == verdict.PASS || v == verdict.FAIL || v == verdict.ERROR;
};
fn validtermination(t: termination) bool = {
return t == termination.EXIT || t == termination.SIGNAL
|| t == termination.TIMEOUT || t == termination.SETUP;
};
fn validexpected(c: *corpus, ids: []identity) bool = {
if (ids.len > nativecount) {
return protocolfail("selected count exceeds native matrix");
};
let i: i32 = 0;
for (i < ids.len) {
let id: *identity = &ids[i];
if (id.ordinal != i + 1) {
return protocolfail("expected ordinals are not consecutive");
};
if (id.fixtureindex < 0 || id.fixtureindex >= c.fixtures.len) {
return protocolfail("expected fixture index is invalid");
};
if (!validstage(id.stage)) {
return protocolfail("expected stage is invalid");
};
let j: i32 = 0;
for (j < i) {
if (ids[j].fixtureindex == id.fixtureindex
&& ids[j].stage == id.stage) {
return protocolfail("duplicate expected identity");
};
j += 1;
};
i += 1;
};
return true;
};
fn validcapture(r: *cellresult) bool = {
if (!safeprotocolfield(r.capture)) {
return protocolfail("unsafe or empty capture field");
};
if (r.verdict != verdict.PASS && r.capture == "-") {
return protocolfail("non-pass result has no retained capture");
};
return true;
};
fn validterminationcode(r: *cellresult) bool = {
if (r.termination == termination.EXIT) {
if (r.code < 0 || r.code > 255) {
return protocolfail("exit code is out of range");
};
return true;
};
if (r.termination == termination.SIGNAL) {
if (r.code < 1 || r.code > 64) {
return protocolfail("signal number is out of range");
};
return true;
};
if (r.termination == termination.TIMEOUT) {
if (r.code != 0) { return protocolfail("timeout code is not zero"); };
return true;
};
if (r.code >= 0 || r.code < -4095) {
return protocolfail("setup code is not a negative errno");
};
return true;
};
fn validresult(f: *fixture, r: *cellresult) bool = {
if (!safeprotocolfield(r.id)) {
return protocolfail("unsafe or empty result identity");
};
if (!validstage(r.stage) || !validphase(r.phase)
|| !validverdict(r.verdict) || !validtermination(r.termination)) {
return protocolfail("invalid result enum");
};
if (r.durationns < 0i64) { return protocolfail("negative duration"); };
if (!validcapture(r) || !validterminationcode(r)) { return false; };
if (r.verdict == verdict.PASS && r.termination != termination.EXIT) {
return protocolfail("pass is not a normal exit");
};
if (r.termination == termination.SETUP && r.verdict != verdict.ERROR) {
return protocolfail("setup termination is not an error");
};
if (r.verdict == verdict.FAIL && r.termination == termination.SETUP) {
return protocolfail("fail result has setup termination");
};
if (f.directive == directive.ERROR) {
if (r.phase != phase.COMPILE) {
return protocolfail("error fixture has non-compile phase");
};
if (r.verdict == verdict.PASS
&& (r.termination != termination.EXIT || r.code == 0)) {
return protocolfail("compile-fail pass is not a normal nonzero exit");
};
return true;
};
if (f.directive == directive.COMPILE) {
if (r.phase != phase.COMPILE) {
return protocolfail("compile fixture has non-compile phase");
};
if (r.verdict == verdict.PASS
&& (r.termination != termination.EXIT || r.code != 0)) {
return protocolfail("compile pass is not a normal exit zero");
};
if (r.verdict != verdict.PASS && r.termination == termination.EXIT
&& r.code == 0) {
return protocolfail("non-pass compile is a normal exit zero");
};
return true;
};
if (f.directive != directive.RUN && f.directive != directive.RUNEXIT) {
return protocolfail("invalid fixture directive");
};
if (r.phase == phase.COMPILE) {
return protocolfail("positive fixture has compile phase");
};
if (r.phase == phase.BUILD) {
if (r.verdict == verdict.PASS) {
return protocolfail("positive build cannot be a terminal pass");
};
if (r.termination == termination.EXIT && r.code == 0) {
return protocolfail("positive build stopped after normal exit zero");
};
return true;
};
let expected: i32 = 0;
if (f.directive == directive.RUNEXIT) { expected = f.exitcode; };
if (r.verdict == verdict.PASS
&& (r.termination != termination.EXIT || r.code != expected)) {
return protocolfail("runtime pass does not match expected exit");
};
if (r.verdict == verdict.FAIL && r.termination == termination.EXIT
&& r.code == expected) {
return protocolfail("runtime fail matches expected exit");
};
return true;
};
fn validrow(c: *corpus, expected: *identity, r: *cellresult) bool = {
if (r.ordinal != expected.ordinal) {
return protocolfail("missing, duplicate, or out-of-order ordinal");
};
let f: *fixture = &c.fixtures[expected.fixtureindex];
if (r.id != f.id || r.stage != expected.stage) {
return protocolfail("unexpected result identity");
};
return validresult(f, r);
};
fn putheader(b: *[]u8, runid: str, selected: i32) void = {
protocolput(b, "wwfix");
protocolintfield(b, protocolversion: i64);
protocolfield(b, runid);
protocolintfield(b, corpuscount: i64);
protocolintfield(b, nativecount: i64);
protocolfield(b, corpushash);
protocolintfield(b, selected: i64);
protocolnewline(b);
};
fn putrow(b: *[]u8, r: *cellresult) void = {
protocolput(b, "case");
protocolintfield(b, r.ordinal: i64);
protocolfield(b, r.id);
protocolfield(b, stageword(r.stage));
protocolfield(b, phaseword(r.phase));
protocolfield(b, verdictword(r.verdict));
protocolfield(b, terminationword(r.termination));
protocolintfield(b, r.code: i64);
protocolintfield(b, r.durationns);
protocolfield(b, r.capture);
protocolnewline(b);
};
fn putfooter(b: *[]u8, selected: i32, failures: i32) void = {
protocolput(b, "end");
protocolintfield(b, selected: i64);
protocolintfield(b, failures: i64);
protocolnewline(b);
};
fn writestream(fd: i32, runid: str, c: *corpus, ids: []identity,
results: []cellresult) bool = {
protocolerr = "";
if (!validrunid(runid)) { return protocolfail("invalid run ID"); };
if (!validexpected(c, ids)) { return false; };
if (results.len != ids.len) {
return protocolfail("selected result count mismatch");
};
let failures: i32 = 0;
let i: i32 = 0;
for (i < results.len) {
if (!validrow(c, &ids[i], &results[i])) { return false; };
if (results[i].verdict != verdict.PASS) { failures += 1; };
i += 1;
};
let b: []u8;
putheader(&b, runid, ids.len);
i = 0;
for (i < results.len) {
putrow(&b, &results[i]);
i += 1;
};
putfooter(&b, ids.len, failures);
return protocolwriteall(fd, b);
};
fn splitfields(line: str, out: *[]str) bool = {
let fields: []str;
let start: i32 = 0;
let i: i32 = 0;
for (i <= line.len) {
if (i == line.len || line[i] == '\t': u8) {
append(fields, line[start:i]);
start = i + 1;
};
i += 1;
};
*out = fields;
return true;
};
fn nextline(data: str, off: *i32, out: *str) bool = {
if (*off >= data.len) { return false; };
let end: i32 = *off;
for (end < data.len && data[end] != '\n': u8) { end += 1; };
if (end == data.len) { return false; };
*out = data[*off:end];
*off = end + 1;
return true;
};
fn parseu64field(s: str, out: *u64) bool = {
if (s.len == 0) { return protocolfail("empty integer"); };
if (s.len > 1 && s[0] == '0': u8) {
return protocolfail("non-canonical integer");
};
let n: u64 = 0u64;
let i: i32 = 0;
for (i < s.len) {
let c: u8 = s[i];
if (c < '0': u8 || c > '9': u8) {
return protocolfail("malformed integer");
};
let d: u64 = (c - '0': u8): u64;
if (n > 1844674407370955161u64
|| (n == 1844674407370955161u64 && d > 5u64)) {
return protocolfail("integer overflow");
};
n = n * 10u64 + d;
i += 1;
};
*out = n;
return true;
};
fn parsei32nonnegative(s: str, out: *i32) bool = {
let n: u64 = 0u64;
if (!parseu64field(s, &n)) { return false; };
if (n > 2147483647u64) { return protocolfail("i32 overflow"); };
*out = n: i32;
return true;
};
fn parsei64nonnegative(s: str, out: *i64) bool = {
let n: u64 = 0u64;
if (!parseu64field(s, &n)) { return false; };
if (n > 9223372036854775807u64) { return protocolfail("i64 overflow"); };
*out = n: i64;
return true;
};
fn parsei32field(s: str, out: *i32) bool = {
if (s.len == 0) { return protocolfail("empty integer"); };
if (s[0] != '-': u8) { return parsei32nonnegative(s, out); };
if (s.len == 1 || (s.len > 2 && s[1] == '0': u8)) {
return protocolfail("malformed negative integer");
};
let magnitude: str;
magnitude.ptr = s.ptr + 1u64;
magnitude.len = s.len - 1;
let n: u64 = 0u64;
if (!parseu64field(magnitude, &n)) { return false; };
if (n == 0u64 || n > 2147483648u64) {
return protocolfail("i32 overflow");
};
*out = -(n: i32);
return true;
};
fn parsestagefield(s: str, out: *stage) bool = {
if (s == "c") { *out = stage.C; return true; };
if (s == "ww") { *out = stage.WW; return true; };
return protocolfail("unknown stage");
};
fn parsephasefield(s: str, out: *phase) bool = {
if (s == "compile") { *out = phase.COMPILE; return true; };
if (s == "build") { *out = phase.BUILD; return true; };
if (s == "run") { *out = phase.RUN; return true; };
return protocolfail("unknown phase");
};
fn parseverdictfield(s: str, out: *verdict) bool = {
if (s == "pass") { *out = verdict.PASS; return true; };
if (s == "fail") { *out = verdict.FAIL; return true; };
if (s == "error") { *out = verdict.ERROR; return true; };
return protocolfail("unknown verdict");
};
fn parseterminationfield(s: str, out: *termination) bool = {
if (s == "exit") { *out = termination.EXIT; return true; };
if (s == "signal") { *out = termination.SIGNAL; return true; };
if (s == "timeout") { *out = termination.TIMEOUT; return true; };
if (s == "setup") { *out = termination.SETUP; return true; };
return protocolfail("unknown termination");
};
fn parseheader(line: str, selected: i32) bool = {
let fields: []str;
splitfields(line, &fields);
if (fields.len != 7) { return protocolfail("malformed header field count"); };
if (fields[0] != "wwfix") { return protocolfail("missing or malformed header"); };
let version: i32 = 0;
let observedcorpus: i32 = 0;
let observednative: i32 = 0;
let observedselected: i32 = 0;
if (!parsei32nonnegative(fields[1], &version)
|| !parsei32nonnegative(fields[3], &observedcorpus)
|| !parsei32nonnegative(fields[4], &observednative)
|| !parsei32nonnegative(fields[6], &observedselected)) { return false; };
if (version != protocolversion) { return protocolfail("wrong protocol version"); };
if (!validrunid(fields[2])) { return protocolfail("invalid run ID"); };
if (observedcorpus != corpuscount || observednative != nativecount
|| strings.compare(fields[5], corpushash) != 0) {
return protocolfail("wrong corpus identity");
};
if (observedselected != selected) {
return protocolfail("header selected count mismatch");
};
return true;
};
fn parserow(line: str, out: *cellresult) bool = {
let fields: []str;
splitfields(line, &fields);
if (fields.len != 10) { return protocolfail("malformed case field count"); };
if (fields[0] != "case") { return protocolfail("missing case record"); };
if (!parsei32nonnegative(fields[1], &out.ordinal)
|| !parsestagefield(fields[3], &out.stage)
|| !parsephasefield(fields[4], &out.phase)
|| !parseverdictfield(fields[5], &out.verdict)
|| !parseterminationfield(fields[6], &out.termination)
|| !parsei32field(fields[7], &out.code)
|| !parsei64nonnegative(fields[8], &out.durationns)) { return false; };
out.id = fields[2];
out.capture = fields[9];
return true;
};
fn parsefooter(line: str, selected: i32, failures: i32) bool = {
let fields: []str;
splitfields(line, &fields);
if (fields.len != 3 || fields[0] != "end") {
return protocolfail("missing or malformed footer");
};
let observedselected: i32 = 0;
let observedfailures: i32 = 0;
if (!parsei32nonnegative(fields[1], &observedselected)
|| !parsei32nonnegative(fields[2], &observedfailures)) { return false; };
if (observedselected != selected || observedfailures != failures) {
return protocolfail("inconsistent footer");
};
return true;
};
fn validatestream(data: str, c: *corpus, ids: []identity) bool = {
protocolerr = "";
if (data.len == 0) { return protocolfail("missing header"); };
if (data[data.len - 1] != '\n': u8) {
return protocolfail("incomplete final line");
};
let off: i32 = 0;
let line: str;
if (!nextline(data, &off, &line)) { return protocolfail("missing header"); };
if (!parseheader(line, ids.len)) { return false; };
if (!validexpected(c, ids)) { return false; };
let failures: i32 = 0;
let i: i32 = 0;
for (i < ids.len) {
if (!nextline(data, &off, &line)) {
return protocolfail("missing case record");
};
let r: cellresult;
if (!parserow(line, &r) || !validrow(c, &ids[i], &r)) { return false; };
if (r.verdict != verdict.PASS) { failures += 1; };
i += 1;
};
if (!nextline(data, &off, &line)) { return protocolfail("missing footer"); };
if (!parsefooter(line, ids.len, failures)) { return false; };
if (off != data.len) { return protocolfail("trailing records"); };
return true;
};
export fn validate(data: str, root: str, patterns: []str) bool = {
protocolerr = "";
let c: corpus;
if (!loadcorpus(root, &c)) {
protocolerr = strings.concat("cannot load corpus: ", error());
return false;
};
let ids: []identity;
if (!selectidentities(&c, patterns, &ids)) {
protocolerr = strings.concat("cannot select corpus: ", error());
return false;
};
return validatestream(data, &c, ids);
};

566
internal/wwfixture/run.ww Normal file
View File

@@ -0,0 +1,566 @@
package wwfixture;
import fmt;
import os;
import os.exec;
import strings;
import temp;
import time;
def fixturepoll: time.duration = 1000000i64: time.duration;
def fixturegrace: time.duration = 250000000i64: time.duration;
type cellstate = enum i32 {
QUEUED = 0,
COMPILE = 1,
BUILD = 2,
RUN = 3,
DONE = 4,
};
type fixturecell = struct {
fixtureindex: i32,
stage: stage,
state: cellstate,
dir: str,
began: time.instant,
deadline: time.instant,
result: cellresult,
};
fn rundecimal(value: i32) str = {
let digits: [11]u8;
let n: i32 = 0;
let v: i64 = value: i64;
let negative: bool = v < 0i64;
if (negative) { v = -v; };
if (v == 0i64) { digits[0] = '0': u8; n = 1; };
for (v > 0i64) {
digits[n] = '0': u8 + (v % 10i64): u8;
n += 1;
v /= 10i64;
};
let capacity: i32 = n;
if (negative) { capacity += 1; };
let out: []u8 = alloc([], capacity: u64)!;
if (negative) { append(out, '-': u8); };
let i: i32 = n;
for (i > 0) { i -= 1; append(out, digits[i]); };
return strings.frombytes(out);
};
fn numbered(ordinal: i32) str = {
let b: []u8 = alloc([], 4u64)!;
b.len = 4;
let v: i32 = ordinal;
let i: i32 = 3;
for (i >= 0) {
b[i] = '0': u8 + (v % 10): u8;
v /= 10;
i -= 1;
};
return strings.frombytes(b);
};
fn runid(path: str) str = {
let start: i32 = 0;
let i: i32 = 0;
for (i < path.len) {
if (path[i] == '/': u8) { start = i + 1; };
i += 1;
};
return strings.dup(path[start:path.len]);
};
fn makeenv(root: str, s: stage) []str = {
let env: []str = alloc([], 7u64)!;
append(env, "PATH=/usr/bin:/bin");
append(env, "LC_ALL=C");
append(env, strings.concat("WW_SRCLIB=", join(root, "lib")));
append(env, strings.concat("WW_LIB=", join(root, "out/lib")));
let suffix: str = "";
if (s == stage.WW) { suffix = "_ww"; };
append(env, strings.concat("WW_W6C=", join(root,
strings.concat("out/bin/w6c", suffix))));
append(env, strings.concat("WW_W6A=", join(root,
strings.concat("out/bin/w6a", suffix))));
append(env, strings.concat("WW_W6L=", join(root,
strings.concat("out/bin/w6l", suffix))));
return env;
};
fn initialresult(id: *identity, f: *fixture, dir: str) cellresult = {
let r: cellresult;
r.ordinal = id.ordinal;
r.id = f.id;
r.stage = id.stage;
r.phase = phase.COMPILE;
if (f.directive == directive.RUN || f.directive == directive.RUNEXIT) {
r.phase = phase.BUILD;
};
r.verdict = verdict.ERROR;
r.termination = termination.SETUP;
r.code = -5;
r.durationns = 0i64;
r.capture = dir;
return r;
};
fn procmessage(reason: str, p: *exec.result) str = {
return strings.concat(reason,
"\ntermination=", rundecimal(p.termination as i32),
" code=", rundecimal(p.code),
" errno=", rundecimal(p.errno),
" cleanup-errno=", rundecimal(p.cleanuperrno));
};
fn maptermination(p: *exec.result, r: *cellresult) void = {
if (p.termination == exec.termination.TIMEOUT) {
r.termination = termination.TIMEOUT;
r.code = 0;
return;
};
if (p.errno != 0 || p.cleanuperrno != 0
|| p.termination == exec.termination.ERROR) {
r.termination = termination.SETUP;
r.code = -p.errno;
if (r.code == 0) { r.code = -p.cleanuperrno; };
if (r.code == 0) { r.code = -5; };
return;
};
if (p.termination == exec.termination.EXIT) {
r.termination = termination.EXIT;
r.code = p.code;
return;
};
if (p.termination == exec.termination.SIGNAL) {
r.termination = termination.SIGNAL;
r.code = p.code;
return;
};
r.termination = termination.SETUP;
r.code = -5;
};
fn processerror(p: *exec.result) bool = {
return p.errno != 0 || p.cleanuperrno != 0
|| p.termination == exec.termination.ERROR;
};
fn finishmessage(c: *fixturecell, p: *exec.result, reason: str) bool = {
let message: str = procmessage(reason, p);
if (writeharness(c.dir, message)) { return true; };
c.result.verdict = verdict.ERROR;
return false;
};
fn startcommand(root: str, f: *fixture, c: *fixturecell,
h: *exec.process) void = {
let argv: []str;
let out: str;
let err: str;
if (c.state == cellstate.COMPILE) {
let frontend: str = join(root, "out/bin/w6c");
if (c.stage == stage.WW) { frontend = join(root, "out/bin/w6c_ww"); };
let compileargv: []str = alloc([], 4u64)!;
argv = compileargv;
append(argv, frontend); append(argv, "-o");
append(argv, join(c.dir, "case.s")); append(argv, f.source);
out = join(c.dir, "compile.stdout");
err = join(c.dir, "compile.stderr");
} else if (c.state == cellstate.BUILD) {
let driver: str = join(root, "out/bin/ww");
if (c.stage == stage.WW) { driver = join(root, "out/bin/ww_ww"); };
let buildargv: []str = alloc([], 5u64)!;
argv = buildargv;
append(argv, driver); append(argv, "build"); append(argv, "-o");
append(argv, join(c.dir, "program")); append(argv, f.source);
out = join(c.dir, "build.stdout");
err = join(c.dir, "build.stderr");
} else {
let program: str = join(c.dir, "program");
let runargv: []str = alloc([], 1u64)!;
argv = runargv;
append(argv, program);
out = join(c.dir, "run.stdout");
err = join(c.dir, "run.stderr");
};
let env: []str = makeenv(root, c.stage);
let cmd: exec.command;
cmd.path = argv[0];
cmd.argv = argv;
cmd.env = env;
cmd.dir = c.dir;
cmd.stdoutpath = out;
cmd.stderrpath = err;
cmd.deadline = c.deadline;
cmd.grace = fixturegrace;
exec.start(h, &cmd);
};
// Returns true only when the cell reached a terminal result. A successful
// positive build starts its program directly and keeps the scheduler slot.
fn collect(root: str, f: *fixture, c: *fixturecell,
h: *exec.process, captureok: *bool) bool = {
let p: *exec.result = &h.result;
c.result.durationns = time.diff(c.began,
time.now(time.clock.monotonic)): i64;
maptermination(p, &c.result);
if (c.state == cellstate.COMPILE) {
c.result.phase = phase.COMPILE;
if (processerror(p)) {
c.result.verdict = verdict.ERROR;
if (!finishmessage(c, p, "compiler process failed")) { *captureok = false; };
} else if (f.directive == directive.COMPILE) {
if (c.result.termination == termination.EXIT && c.result.code == 0) {
c.result.verdict = verdict.PASS;
} else {
c.result.verdict = verdict.FAIL;
if (!finishmessage(c, p,
"compiler did not exit normally with status 0")) {
*captureok = false;
};
};
} else if (c.result.termination == termination.EXIT && c.result.code != 0) {
let diagnostic: str;
let required: str = f.diagnostic;
if (c.stage == stage.WW && f.wwdiagnostic.len != 0) {
required = f.wwdiagnostic;
};
let path: str = join(c.dir, "compile.stderr");
if (!readfile(path, &diagnostic)) {
c.result.verdict = verdict.ERROR;
if (!finishmessage(c, p, "cannot read compiler diagnostics")) {
*captureok = false;
};
} else if (strings.contains(diagnostic, required)) {
c.result.verdict = verdict.PASS;
} else {
c.result.verdict = verdict.FAIL;
if (!finishmessage(c, p, "required diagnostic fragment was not observed")) {
*captureok = false;
};
};
} else {
c.result.verdict = verdict.FAIL;
if (!finishmessage(c, p, "compiler did not exit normally with a nonzero status")) {
*captureok = false;
};
};
c.state = cellstate.DONE;
return true;
};
if (c.state == cellstate.BUILD) {
c.result.phase = phase.BUILD;
if (!processerror(p) && c.result.termination == termination.EXIT
&& c.result.code == 0) {
c.state = cellstate.RUN;
startcommand(root, f, c, h);
return false;
};
if (processerror(p)) { c.result.verdict = verdict.ERROR; }
else { c.result.verdict = verdict.FAIL; };
if (!finishmessage(c, p, "positive fixture build did not exit normally with status 0")) {
*captureok = false;
};
c.state = cellstate.DONE;
return true;
};
c.result.phase = phase.RUN;
let expected: i32 = 0;
if (f.directive == directive.RUNEXIT) { expected = f.exitcode; };
if (processerror(p)) {
c.result.verdict = verdict.ERROR;
if (!finishmessage(c, p, "fixture program process failed")) { *captureok = false; };
} else if (c.result.termination == termination.EXIT
&& c.result.code == expected) {
c.result.verdict = verdict.PASS;
} else {
c.result.verdict = verdict.FAIL;
if (!finishmessage(c, p, "fixture program did not exit normally with the expected status")) {
*captureok = false;
};
};
c.state = cellstate.DONE;
return true;
};
fn finishinterrupted(c: *fixturecell, p: *exec.result,
signo: i32, captureok: *bool) void = {
c.result.phase = phase.COMPILE;
if (c.state == cellstate.BUILD) { c.result.phase = phase.BUILD; }
else if (c.state == cellstate.RUN) { c.result.phase = phase.RUN; };
c.result.durationns = time.diff(c.began,
time.now(time.clock.monotonic)): i64;
maptermination(p, &c.result);
c.result.verdict = verdict.ERROR;
let why: str = strings.concat("coordinator interrupted by signal ",
rundecimal(signo));
if (signo < 0) { why = "coordinator interrupt monitor failed"; };
if (!finishmessage(c, p, why)) { *captureok = false; };
c.state = cellstate.DONE;
};
fn finishqueued(c: *fixturecell, code: i32, reason: str,
captureok: *bool) void = {
c.result.verdict = verdict.ERROR;
c.result.termination = termination.SETUP;
c.result.code = -code;
if (c.result.code == 0) { c.result.code = -4; };
c.result.durationns = 0i64;
let p: exec.result;
p.termination = exec.termination.ERROR;
p.errno = code;
if (!finishmessage(c, &p, reason)) { *captureok = false; };
c.state = cellstate.DONE;
};
fn ensurecapture(dir: str, reason: str) bool = {
let rc: i32 = os.mkdir(dir, 448i32);
if (rc != 0 && rc != -17) { return false; };
return writeharness(dir, reason);
};
fn cleanuppassingcapture(c: *fixturecell) bool = {
let r: *cellresult = &c.result;
if (removeall(c.dir)) {
r.capture = "-";
return true;
};
r.verdict = verdict.ERROR;
r.termination = termination.SETUP;
r.code = -5;
return ensurecapture(c.dir, "passing capture cleanup failed");
};
fn cleanupcaptures(opts: *options, cells: []fixturecell,
runroot: str) bool = {
let ok: bool = true;
let kept: bool = false;
let i: i32 = 0;
for (i < cells.len) {
let r: *cellresult = &cells[i].result;
if (r.verdict == verdict.PASS && !opts.keep) {
if (r.capture != "-" && !cleanuppassingcapture(&cells[i])) {
ok = false;
};
if (r.capture != "-") { kept = true; };
} else { kept = true; };
i += 1;
};
if (!kept) {
if (os.rmdir(runroot) != 0) {
ok = false;
if (cells.len > 0) {
let c: *fixturecell = &cells[0];
c.result.verdict = verdict.ERROR;
c.result.termination = termination.SETUP;
c.result.code = -5;
c.result.capture = c.dir;
ensurecapture(c.dir, "run directory cleanup failed");
};
};
};
return ok;
};
fn preparecells(opts: *options, c: *corpus, ids: []identity,
runroot: str, outcells: *[]fixturecell,
outhandles: *[]exec.process) bool = {
let cells: []fixturecell = alloc([], ids.len: u64)!;
let handles: []exec.process = alloc([], ids.len: u64)!;
let i: i32 = 0;
for (i < ids.len) {
let f: *fixture = &c.fixtures[ids[i].fixtureindex];
let cell: fixturecell;
cell.fixtureindex = ids[i].fixtureindex;
cell.stage = ids[i].stage;
cell.state = cellstate.QUEUED;
cell.dir = join(runroot, numbered(ids[i].ordinal));
cell.result = initialresult(&ids[i], f, cell.dir);
append(cells, cell);
let handle: exec.process;
append(handles, handle);
i += 1;
};
*outcells = cells;
*outhandles = handles;
let ok: bool = true;
i = 0;
for (i < cells.len) {
if (os.mkdir(cells[i].dir, 448i32) != 0) {
ok = false;
i += 1;
continue;
};
i += 1;
};
if (!ok) { fmt.errorln("wwfixture: cannot create numbered capture directory"); };
return ok;
};
fn cancelall(handles: []exec.process) bool = {
let i: i32 = 0;
for (i < handles.len) { exec.cancel(&handles[i]); i += 1; };
let pending: bool = true;
for (pending) {
pending = false;
i = 0;
for (i < handles.len) {
if (!exec.poll(&handles[i])) { pending = true; };
i += 1;
};
if (pending) { time.sleep(fixturepoll, time.clock.monotonic); };
};
let ok: bool = true;
i = 0;
for (i < handles.len) {
if (handles[i].pid > 0 && (handles[i].result.errno != 0
|| handles[i].result.cleanuperrno != 0)) { ok = false; };
i += 1;
};
return ok;
};
fn runfixtures(opts: *options, c: *corpus, ids: []identity) int = {
let runroot: str = strings.dup(temp.dir());
let rid: str = runid(runroot);
let cells: []fixturecell;
let handles: []exec.process;
let prepared: bool = preparecells(opts, c, ids, runroot, &cells, &handles);
let watch: exec.interrupt;
let captureok: bool = true;
let watchok: bool = false;
let interrupted: i32 = 0;
let active: i32 = 0;
let complete: i32 = 0;
let next: i32 = 0;
if (!prepared) {
let rootcaptureok: bool = writeharness(runroot,
"cannot create one or more numbered capture directories");
let i: i32 = 0;
for (i < cells.len) {
let cellcaptureok: bool = true;
finishqueued(&cells[i], 5,
"fixture setup stopped before process launch", &cellcaptureok);
if (!cellcaptureok) {
cells[i].result.capture = runroot;
if (!rootcaptureok) { captureok = false; };
};
i += 1;
};
complete = cells.len;
} else if (cells.len > 0) {
watchok = exec.interruptopen(&watch);
};
if (prepared && cells.len > 0 && !watchok) {
let code: i32 = watch.errno;
let i: i32 = 0;
for (i < cells.len) {
finishqueued(&cells[i], code,
"cannot establish coordinator interrupt monitor", &captureok);
i += 1;
};
complete = cells.len;
};
for (complete < cells.len) {
let signo: i32 = exec.interruptpoll(&watch);
if (signo != 0) {
interrupted = signo;
let cleanupok: bool = cancelall(handles);
if (!cleanupok) { captureok = false; };
let i: i32 = 0;
for (i < cells.len) {
if (cells[i].state == cellstate.DONE) { i += 1; continue; };
if (cells[i].state == cellstate.QUEUED) {
let ec: i32 = 4;
if (signo < 0) { ec = -signo; };
finishqueued(&cells[i], ec,
"coordinator stopped before cell launch", &captureok);
} else {
finishinterrupted(&cells[i], &handles[i].result,
signo, &captureok);
};
i += 1;
};
complete = cells.len;
break;
};
for (next < cells.len && active < opts.jobs) {
let current: *fixturecell = &cells[next];
let f: *fixture = &c.fixtures[current.fixtureindex];
current.state = cellstate.COMPILE;
if (f.directive == directive.RUN || f.directive == directive.RUNEXIT) {
current.state = cellstate.BUILD;
};
current.began = time.now(time.clock.monotonic);
current.deadline = time.add(current.began,
(opts.timeoutms * (time.millisecond: i64)): time.duration);
startcommand(c.root, f, current, &handles[next]);
active += 1;
next += 1;
};
let i: i32 = 0;
for (i < next) {
if (cells[i].state == cellstate.DONE
|| cells[i].state == cellstate.QUEUED) { i += 1; continue; };
if (exec.poll(&handles[i])) {
let f: *fixture = &c.fixtures[cells[i].fixtureindex];
if (collect(c.root, f, &cells[i], &handles[i], &captureok)) {
if (cells[i].result.verdict == verdict.PASS && !opts.keep
&& !cleanuppassingcapture(&cells[i])) {
captureok = false;
};
active -= 1;
complete += 1;
};
};
i += 1;
};
if (active > 0) { time.sleep(fixturepoll, time.clock.monotonic); };
};
if (watchok && !exec.interruptclose(&watch)) {
captureok = false;
if (cells.len > 0) {
let c0: *fixturecell = &cells[0];
if (!writefile(join(c0.dir, "coordinator.txt"),
"cannot close coordinator interrupt monitor\n")) {
captureok = false;
};
if (c0.result.verdict == verdict.PASS) {
c0.result.verdict = verdict.ERROR;
c0.result.termination = termination.SETUP;
c0.result.code = -watch.errno;
if (c0.result.code == 0) { c0.result.code = -5; };
};
};
};
if (!cleanupcaptures(opts, cells, runroot)) { captureok = false; };
let results: []cellresult = alloc([], cells.len: u64)!;
let failures: i32 = 0;
let i: i32 = 0;
for (i < cells.len) {
append(results, cells[i].result);
if (cells[i].result.verdict != verdict.PASS) { failures += 1; };
i += 1;
};
if (!writestream(os.STDOUT_FILENO, rid, c, ids, results)) {
let retained: bool = true;
let rc: i32 = os.mkdir(runroot, 448i32);
if (rc != 0 && rc != -17) { retained = false; };
if (retained && !writefile(join(runroot, "publication.txt"),
strings.concat("result publication failed: ", protocolerror(), "\n"))) {
retained = false;
};
if (retained) {
fmt.errorln(strings.concat("wwfixture: ", protocolerror(),
"; capture: ", runroot));
} else {
fmt.errorln(strings.concat("wwfixture: ", protocolerror(),
"; cannot retain publication capture"));
};
return 1;
};
if (!captureok || interrupted != 0 || failures != 0) { return 1; };
return 0;
};

View File

@@ -0,0 +1,98 @@
package wwfixture;
def protocolversion: i32 = 1;
def corpuscount: i32 = 1224;
def errorcount: i32 = 314;
def compilecount: i32 = 12;
def runcount: i32 = 136;
def runexitcount: i32 = 762;
def nativecount: i32 = 2448;
def corpushash: str = "8b522e42cbce8b389e33e917229dede8045d85f1af0c54630cfef83c4ef242be";
type directive = enum i32 {
ERROR = 0,
RUN = 1,
RUNEXIT = 2,
COMPILE = 3,
};
type stage = enum i32 {
C = 0,
WW = 1,
};
type phase = enum i32 {
COMPILE = 0,
BUILD = 1,
RUN = 2,
};
type verdict = enum i32 {
PASS = 0,
FAIL = 1,
ERROR = 2,
};
type termination = enum i32 {
EXIT = 0,
SIGNAL = 1,
TIMEOUT = 2,
SETUP = 3,
};
type fixture = struct {
name: str,
id: str,
source: str,
directive: directive,
diagnostic: str,
wwdiagnostic: str,
exitcode: i32,
};
type corpus = struct {
root: str,
fixtures: []fixture,
};
type identity = struct {
ordinal: i32,
fixtureindex: i32,
stage: stage,
};
type cellresult = struct {
ordinal: i32,
id: str,
stage: stage,
phase: phase,
verdict: verdict,
termination: termination,
code: i32,
durationns: i64,
capture: str,
};
fn stageword(s: stage) str = {
if (s == stage.C) { return "c"; };
return "ww";
};
fn phaseword(p: phase) str = {
if (p == phase.COMPILE) { return "compile"; };
if (p == phase.BUILD) { return "build"; };
return "run";
};
fn verdictword(v: verdict) str = {
if (v == verdict.PASS) { return "pass"; };
if (v == verdict.FAIL) { return "fail"; };
return "error";
};
fn terminationword(t: termination) str = {
if (t == termination.EXIT) { return "exit"; };
if (t == termination.SIGNAL) { return "signal"; };
if (t == termination.TIMEOUT) { return "timeout"; };
return "setup";
};