Files
ww/internal/wwfixture/run.ww
Hojun-Cho aa0f77b891 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.
2026-08-07 23:21:04 +09:00

567 lines
16 KiB
Plaintext

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;
};