Files
ww/test/wwfixture/process/main.ww
Hojun-Cho f28843f2f5 ww test: preserve combined product output order
Go 1.26.5 maps each test binary's stdout and stderr to the same product writer. Teach the captured executor to share one open file description for byte-equal output paths, then make directory test products emit that one ordered capture on coordinator stdout. Build captures and inherited-stdio routes remain unchanged.\n\nKeep the executor mechanism, package policy, native Cstage/WWstage proof, and normative contract together so every commit preserves the observable test-output behavior.
2026-08-20 18:29:09 +09:00

427 lines
13 KiB
Plaintext

package main;
import os;
import os.exec;
import strings;
import temp;
import time;
fn eq(a: str, b: str) bool = {
if (a.len != b.len) { return false; };
let i: i32 = 0;
for (i < a.len) {
if (a[i] != b[i]) { return false; };
i += 1;
};
return true;
};
fn writeexact(fd: i32, s: str) bool = {
match (os.writeall(fd, s.ptr, s.len: u64)) {
case let n: i64 => return n == s.len: i64;
case let e: os.oserror => return false;
};
};
fn readfile(path: str) str = {
let fd: i32 = os.open(path, os.flag.RDONLY, 0i32);
assert(!(fd < 0));
let sr: (i64 | os.oserror) = os.filesize(fd);
let n: i64 = -1i64;
match (sr) {
case let v: i64 => n = v;
case let e: os.oserror => abort("filesize failed");
};
assert(!(n < 0i64));
let b: []u8 = alloc([], (n + 1i64): u64)!;
b.len = (n + 1i64): i32;
let rr: (i64 | os.oserror) = os.readall(fd, b.ptr, n: u64);
assert(!(os.close(fd) != 0));
let got: i64 = -1i64;
match (rr) {
case let v: i64 => got = v;
case let e: os.oserror => abort("read failed");
};
assert(got == n);
let ni: i32 = n: i32;
b[ni] = 0u8;
let out: str;
out.ptr = b.ptr;
out.len = n: i32;
return out;
};
fn waitchild(pid: i32) i32 = {
let status: i32 = 0;
for (true) {
let got: i32 = os.wait4(pid, &status, 0i32, nil: *void);
if (got == pid) { return status; };
if (got != -4) { return -1; };
};
return -1;
};
fn childmode(args: []str) void = {
let mode: str = args[2];
if (eq(mode, "exit0")) { os.exit(0); };
if (eq(mode, "exit7")) { os.exit(7); };
if (eq(mode, "exit127")) { os.exit(127); };
if (eq(mode, "route")) {
if (args.len != 5 || !eq(args[3], "token")) { os.exit(129); };
match (os.getenv("ROUTE")) {
case let value: str => if (!eq(value, "value")) { os.exit(130); };
case void => os.exit(131);
};
let cwdbytes: [4096]u8;
let n: i64 = os.getcwd(&cwdbytes[0], size([4096]u8));
if (n <= 1i64 || n > size([4096]u8): i64) { os.exit(132); };
let cwd: str;
cwd.ptr = &cwdbytes[0];
cwd.len = (n - 1i64): i32;
if (!eq(cwd, args[4])) { os.exit(133); };
if (!writeexact(os.STDOUT_FILENO, "route stdout\n")) { os.exit(134); };
if (!writeexact(os.STDERR_FILENO, "route stderr\n")) { os.exit(135); };
os.exit(0);
};
if (eq(mode, "merge")) {
if (!writeexact(os.STDOUT_FILENO, "out 1\n")) { os.exit(146); };
if (!writeexact(os.STDERR_FILENO, "err 1\n")) { os.exit(147); };
if (!writeexact(os.STDOUT_FILENO, "out 2\n")) { os.exit(148); };
if (!writeexact(os.STDERR_FILENO, "err 2\n")) { os.exit(149); };
os.exit(0);
};
if (eq(mode, "signal")) {
os.kill(os.getpid(), os.SIGKILL);
os.exit(120);
};
if (eq(mode, "hold")) {
for (true) { time.sleep(time.second, time.clock.monotonic); };
};
if (eq(mode, "resistdesc")) {
let mask: u64 = 1u64 << ((os.SIGTERM - 1): u64);
if (os.sigprocmask(os.SIG_BLOCK, &mask, nil: *u64) != 0) {
os.exit(121);
};
let pid: i32 = os.fork();
if (pid < 0) { os.exit(122); };
if (pid > 0 && args.len == 4) {
let readyfd: i32 = os.open(args[3],
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 384);
if (readyfd < 0) { os.exit(142); };
let me: u32 = os.getpid(): u32;
let ready: [4]u8;
ready[0] = me: u8;
ready[1] = (me >> 8u32): u8;
ready[2] = (me >> 16u32): u8;
ready[3] = (me >> 24u32): u8;
if (os.write(readyfd, &ready[0], 4u64) != 4i64) {
os.exit(143);
};
if (os.close(readyfd) != 0) { os.exit(144); };
};
for (true) { time.sleep(time.second, time.clock.monotonic); };
};
if (eq(mode, "exitdesc")) {
let mask: u64 = 1u64 << ((os.SIGTERM - 1): u64);
if (os.sigprocmask(os.SIG_BLOCK, &mask, nil: *u64) != 0) {
os.exit(124);
};
let ready: [2]i32;
if (os.pipe(&ready) != 0) { os.exit(125); };
let pid: i32 = os.fork();
if (pid < 0) { os.exit(126); };
let b: [1]u8 = [1u8];
if (pid == 0) {
os.close(ready[0]);
if (os.write(ready[1], &b[0], 1u64) != 1i64) { os.exit(127); };
os.close(ready[1]);
for (true) { time.sleep(time.second, time.clock.monotonic); };
};
os.close(ready[1]);
if (os.read(ready[0], &b[0], 1u64) != 1i64) { os.exit(128); };
os.close(ready[0]);
os.exit(0);
};
os.exit(123);
};
fn fillcommand(c: *exec.command, self: str, mode: str, root: str,
name: str, lifetime: time.duration, grace: time.duration) void = {
let av: []str = alloc([], 3u64)!;
append(av, self);
append(av, "child");
append(av, mode);
let env: []str = alloc([], 2u64)!;
append(env, "PATH=/usr/bin:/bin");
append(env, "LC_ALL=C");
c.path = self;
c.argv = av;
c.env = env;
c.dir = root;
c.stdoutpath = strings.concat(root, "/", name, ".out");
c.stderrpath = strings.concat(root, "/", name, ".err");
c.deadline = time.add(time.now(time.clock.monotonic), lifetime);
c.grace = grace;
};
fn waitdone(p: *exec.process) void = {
for (!exec.poll(p)) {
time.sleep(time.millisecond, time.clock.monotonic);
};
};
fn cancelall(ps: []exec.process) bool = {
let i: i32 = 0;
for (i < ps.len) { exec.cancel(&ps[i]); i += 1; };
let pending: bool = true;
for (pending) {
pending = false;
i = 0;
for (i < ps.len) {
if (!exec.poll(&ps[i])) { pending = true; };
i += 1;
};
if (pending) { time.sleep(time.millisecond, time.clock.monotonic); };
};
let ok: bool = true;
i = 0;
for (i < ps.len) {
if (ps[i].pid > 0 && (ps[i].result.errno != 0
|| ps[i].result.cleanuperrno != 0)) { ok = false; };
i += 1;
};
return ok;
};
fn main() void = {
let args: []str = os.args();
if (args.len >= 3 && eq(args[1], "child")) {
childmode(args);
};
assert(!(args.len == 0));
let self: str = strings.dup(args[0]);
if (self.len == 0 || self[0] != '/': u8) {
let cwdbuf: [4096]u8;
let cwdn: i64 = os.getcwd(&cwdbuf[0], size([4096]u8));
assert(!(cwdn <= 1i64 || cwdn > size([4096]u8): i64));
let cwd: str;
cwd.ptr = &cwdbuf[0];
cwd.len = (cwdn - 1i64): i32;
self = strings.concat(cwd, "/", self);
};
let root: str = strings.dup(temp.dir());
let p: exec.process;
let c: exec.command;
let r: exec.result;
let grace: time.duration =
(20i64 * (time.millisecond: i64)): time.duration;
fillcommand(&c, self, "exit0", root, "exit0",
time.second, grace);
exec.run(&c, &r);
assert(r.termination == exec.termination.EXIT && r.code == 0);
assert(r.errno == 0 && r.cleanuperrno == 0);
fillcommand(&c, self, "exitdesc", root, "exitdesc",
time.second, grace);
let descbefore: time.instant = time.now(time.clock.monotonic);
exec.start(&p, &c);
waitdone(&p);
let descafter: time.instant = time.now(time.clock.monotonic);
assert(p.result.termination == exec.termination.EXIT
&& p.result.code == 0);
assert(!((time.diff(descbefore, descafter): i64) < grace: i64));
assert(!(os.kill(-p.pid, 0) != -3));
fillcommand(&c, self, "exit7", root, "exit7",
time.second, grace);
exec.run(&c, &r);
assert(r.termination == exec.termination.EXIT && r.code == 7);
fillcommand(&c, self, "exit127", root, "exit127",
time.second, grace);
exec.run(&c, &r);
assert(r.termination == exec.termination.EXIT && r.code == 127);
assert(r.errno == 0);
fillcommand(&c, self, "route", root, "route",
time.second, grace);
let routeargv: []str = c.argv;
append(routeargv, "token");
append(routeargv, root);
c.argv = routeargv;
let routeenv: []str = c.env;
append(routeenv, "ROUTE=value");
c.env = routeenv;
exec.run(&c, &r);
assert(r.termination == exec.termination.EXIT && r.code == 0);
assert(eq(readfile(c.stdoutpath), "route stdout\n"));
assert(eq(readfile(c.stderrpath), "route stderr\n"));
fillcommand(&c, self, "merge", root, "merge",
time.second, grace);
let mergeerr: str = c.stderrpath;
c.stderrpath = c.stdoutpath;
exec.run(&c, &r);
assert(r.termination == exec.termination.EXIT && r.code == 0);
assert(r.errno == 0 && r.cleanuperrno == 0);
assert(eq(readfile(c.stdoutpath), "out 1\nerr 1\nout 2\nerr 2\n"));
assert(!os.exists(mergeerr));
fillcommand(&c, self, "signal", root, "signal",
time.second, grace);
exec.run(&c, &r);
assert(r.termination == exec.termination.SIGNAL);
assert(!(r.code != os.SIGKILL));
fillcommand(&c, "/no/such/exec-program", "exit0", root, "execfail",
time.second, grace);
exec.run(&c, &r);
assert(r.termination == exec.termination.ERROR && r.errno == 2);
fillcommand(&c, "/no/such/exec-program", "exit0", root, "pastfail",
time.second, grace);
c.deadline = time.add(time.now(time.clock.monotonic),
(-1i64 * (time.second: i64)): time.duration);
exec.run(&c, &r);
assert(r.termination == exec.termination.ERROR && r.errno == 2);
fillcommand(&c, self, "exit0", root, "chdirfail",
time.second, grace);
c.dir = strings.concat(root, "/missing-directory");
exec.run(&c, &r);
assert(r.termination == exec.termination.ERROR && r.errno == 2);
fillcommand(&c, self, "resistdesc", root, "timeout",
(300i64 * (time.millisecond: i64)): time.duration, grace);
let until: i64 = time.diff(time.now(time.clock.monotonic),
c.deadline): i64;
assert(!(until < 250i64 * (time.millisecond: i64)));
let timeoutbefore: time.instant = time.now(time.clock.monotonic);
exec.start(&p, &c);
waitdone(&p);
let timeoutafter: time.instant = time.now(time.clock.monotonic);
assert(p.result.termination == exec.termination.TIMEOUT);
assert(!((time.diff(timeoutbefore, timeoutafter): i64)
< 250i64 * (time.millisecond: i64)));
assert(!(os.kill(-p.pid, 0) != -3));
let exists: str = strings.concat(root, "/exists.out");
let fd: i32 = os.open(exists,
os.flag.WRONLY | os.flag.CREATE | os.flag.EXCL, 384);
assert(!(fd < 0));
assert(!(os.close(fd) != 0));
fillcommand(&c, self, "exit0", root, "unused",
time.second, grace);
c.stdoutpath = exists;
exec.run(&c, &r);
assert(r.termination == exec.termination.ERROR && r.errno == 17);
let readyfile: str = strings.concat(root, "/runinterrupt.ready");
let coordinator: i32 = os.fork();
assert(!(coordinator < 0));
if (coordinator == 0) {
let ic: exec.command;
let ir: exec.result;
fillcommand(&ic, self, "resistdesc", root, "runinterrupt",
(10i64 * (time.second: i64)): time.duration, grace);
let iav: []str = ic.argv;
append(iav, readyfile);
ic.argv = iav;
exec.run(&ic, &ir);
os.exit(145);
};
let readywait: i32 = 0;
for (os.access(readyfile, 0i32) != 0 && readywait < 2000) {
time.sleep(time.millisecond, time.clock.monotonic);
readywait += 1;
};
assert(!(os.access(readyfile, 0i32) != 0));
let readybytes: str = readfile(readyfile);
assert(readybytes.len == 4);
let runpid: i32 = (readybytes[0]: u32
| ((readybytes[1]: u32) << 8u32)
| ((readybytes[2]: u32) << 16u32)
| ((readybytes[3]: u32) << 24u32)): i32;
assert(!(os.kill(coordinator, os.SIGINT) != 0));
let coordinatorstatus: i32 = waitchild(coordinator);
assert(coordinatorstatus >= 0 && os.wifsignaled(coordinatorstatus)
&& os.wtermsig(coordinatorstatus) == os.SIGINT);
assert(!(os.kill(-runpid, 0) != -3));
let closedprobe: i32 = os.fork();
assert(!(closedprobe < 0));
if (closedprobe == 0) {
if (os.close(os.STDOUT_FILENO) != 0) { os.exit(137); };
if (os.close(os.STDERR_FILENO) != 0) { os.exit(138); };
let cc: exec.command;
let cr: exec.result;
fillcommand(&cc, self, "route", root, "closed",
time.second, grace);
let cav: []str = cc.argv;
append(cav, "token");
append(cav, root);
cc.argv = cav;
let cev: []str = cc.env;
append(cev, "ROUTE=value");
cc.env = cev;
exec.run(&cc, &cr);
if (cr.termination != exec.termination.EXIT || cr.code != 0
|| cr.errno != 0 || cr.cleanuperrno != 0) { os.exit(139); };
if (!eq(readfile(cc.stdoutpath), "route stdout\n")) { os.exit(140); };
if (!eq(readfile(cc.stderrpath), "route stderr\n")) { os.exit(141); };
fillcommand(&cc, self, "merge", root, "closedmerge",
time.second, grace);
let closedmergeerr: str = cc.stderrpath;
cc.stderrpath = cc.stdoutpath;
exec.run(&cc, &cr);
if (cr.termination != exec.termination.EXIT || cr.code != 0
|| cr.errno != 0 || cr.cleanuperrno != 0) { os.exit(150); };
if (!eq(readfile(cc.stdoutpath),
"out 1\nerr 1\nout 2\nerr 2\n")) { os.exit(151); };
if (os.exists(closedmergeerr)) { os.exit(152); };
os.exit(0);
};
let closedstatus: i32 = waitchild(closedprobe);
assert(closedstatus >= 0 && os.wifexited(closedstatus)
&& os.wexitstatus(closedstatus) == 0);
let watch: exec.interrupt;
assert(exec.interruptopen(&watch));
let handles: []exec.process = alloc([], 2u64)!;
let zero: exec.process;
append(handles, zero);
append(handles, zero);
fillcommand(&c, self, "hold", root, "interrupt",
(10i64 * (time.second: i64)): time.duration, grace);
exec.start(&handles[0], &c);
assert(!(os.kill(os.getpid(), os.SIGINT) != 0));
let signo: i32 = 0;
for (signo == 0) {
signo = exec.interruptpoll(&watch);
};
assert(!(signo != os.SIGINT));
assert(cancelall(handles));
assert(handles[0].result.termination == exec.termination.SIGNAL);
assert(exec.poll(&handles[1]));
assert(exec.interruptclose(&watch));
let captures: []str = [
"exit0.out", "exit0.err", "exitdesc.out", "exitdesc.err",
"exit7.out", "exit7.err", "exit127.out", "exit127.err",
"route.out", "route.err", "merge.out", "signal.out", "signal.err",
"execfail.out", "execfail.err", "pastfail.out", "pastfail.err",
"chdirfail.out", "chdirfail.err", "timeout.out", "timeout.err",
"exists.out", "runinterrupt.out", "runinterrupt.err",
"runinterrupt.ready",
"closed.out", "closed.err", "closedmerge.out",
"interrupt.out", "interrupt.err",
];
let i: i32 = 0;
for (i < captures.len) {
assert(!(os.remove(strings.concat(root, "/", captures[i])) != 0));
i += 1;
};
assert(!(os.rmdir(root) != 0));
};