package driver_test; // `ww` driver CLI observers. Ports of the retired native carriers // test/wcc/949_driver_flagargs.c and 949_missingpkg.c; the retired // second-positional single-file-only assertion now belongs to package-root // selection, while the remaining driver assertions stay here. // // flagargs (#15 B2) — `ww -V` exits 0 with stdout exactly // "ww \n" and empty stderr; 14 flag-misuse rows each exit // 2 with the row's stderr fragment; and on every row plus -V the // wwstage twin `ww_ww` matches the cstage `ww` byte-exactly on exit // code, stdout and stderr (rule 10 on the driver surface). The // version oracle is parsed from cmd/wcc/ww.h at run time, preserving // the carrier's exact-output ownership without a C toolchain // dependency. // // missingpkg (#16 ENFORCE-driver) — `ww build` over a source whose // only import cannot be located exits nonzero with "cannot find // package nosuchpkg" on stderr (the driver fatal, emitted by // cmd/ww/main.c, not w6c); the ww_ww twin of this leg is owned by the // retained bootstrap carrier test/wcc/993_ww_ww.c. The inline branch // pins the skip arm: a single-file multi-package unit whose import is // satisfied by an inline `package aa` builds and runs to exit 7 (the // fatal must not over-fire on the inline-package pattern). import os; import os.exec; import strings; import testenv; import time; fn fail(label: str, why: str) void = { let m: str = strings.concat("driver FAIL: ", label, " -- ", why, "\n"); os.write(2, m.ptr, m.len: u64); assert(false); }; fn tmo() time.duration = { return (30i64 * (time.second: i64)): time.duration; }; // Byte-offset borrow: strings.sub is rune-indexed and ww.h holds only // ASCII around the define, but the borrow keeps the cursor math exact. fn bslice(s: str, start: i32, end: i32) str = { assert(start <= end && end <= s.len); let r: str; r.ptr = s.ptr + (start: u64); r.len = end - start; return r; }; // The compile-time constant the C carrier took from #include "ww.h". fn wwversion() str = { let h: str = testenv.readfile(strings.concat(testenv.repo(), "/cmd/wcc/ww.h")); let at: i32 = testenv.pos(h, "#define WW_VERSION"); assert(at >= 0); let i: i32 = at; for (h[i] != '"') { i += 1; }; let j: i32 = i + 1; for (h[j] != '"') { j += 1; }; return bslice(h, i + 1, j); }; fn rundrv(root: str, name: str, drv: str, argv: []str, out: *testenv.commandout) void = { let av: []str = alloc([], (argv.len + 1): u64)!; append(av, testenv.driver(drv)); let i: i32 = 0; for (i < argv.len) { append(av, argv[i]); i += 1; }; testenv.runcommand(root, root, name, av, tmo(), out); assert(out.termination == exec.termination.EXIT); }; fn runrootargv(dir: str, root: str, name: str, drv: str, out: *testenv.commandout) void = { let av: []str = ["/ww", "build", "rootprobe"]; let env: []str = ["PATH=/usr/bin:/bin", "LC_ALL=C"]; let c: exec.command; c.path = testenv.driver(drv); c.argv = av; c.env = env; c.dir = dir; c.stdoutpath = strings.concat(root, "/", name, ".stdout"); c.stderrpath = strings.concat(root, "/", name, ".stderr"); c.deadline = time.add(time.now(time.clock.monotonic), tmo()); c.grace = (100i64 * (time.millisecond: i64)): time.duration; let r: exec.result; exec.run(&c, &r); assert(r.errno == 0 && r.cleanuperrno == 0); out.termination = r.termination; out.code = r.code; out.stdout = testenv.readfile(c.stdoutpath); out.stderr = testenv.readfile(c.stderrpath); }; @test fn version() void = { let td: str = testenv.fresh(); let want: str = strings.concat("ww ", wwversion(), "\n"); let co: testenv.commandout; let wo: testenv.commandout; let vtail: []str = ["-V"]; rundrv(td, "version_c", "ww", vtail, &co); rundrv(td, "version_w", "ww_ww", vtail, &wo); if (co.code != 0 || !testenv.same(co.stdout, want) || co.stderr.len != 0) { fail("version", "ww -V rc/stdout/stderr drifted from WW_VERSION"); }; if (wo.code != co.code || !testenv.same(wo.stdout, co.stdout) || !testenv.same(wo.stderr, co.stderr)) { fail("version", "ww_ww -V != ww -V (twin parity)"); }; testenv.clean(td); }; @test fn help() void = { let td: str = testenv.fresh(); let tail: []str = ["-h"]; let co: testenv.commandout; let wo: testenv.commandout; rundrv(td, "help_c", "ww", tail, &co); rundrv(td, "help_w", "ww_ww", tail, &wo); if (co.code != 0 || wo.code != 0 || !testenv.same(co.stdout, wo.stdout) || co.stderr.len != 0 || wo.stderr.len != 0) { fail("help", "driver help is not byte-identical on stdout"); }; if (testenv.has(co.stdout, "foo/foo.ww") || testenv.has(co.stdout, "fmt ")) { fail("help", "driver help advertises a retired path or command"); }; let fmttail: []str = ["fmt"]; rundrv(td, "fmt_c", "ww", fmttail, &co); rundrv(td, "fmt_w", "ww_ww", fmttail, &wo); if (co.code != 2 || wo.code != co.code || !testenv.same(co.stdout, wo.stdout) || !testenv.same(co.stderr, wo.stderr) || !testenv.has(co.stderr, "unknown subcommand: fmt")) { fail("help", "retired fmt command is not an identical unknown command"); }; testenv.clean(td); }; @test fn rootargv0() void = { let td: str = testenv.fresh(); let lib: str = strings.concat(td, "/lib"); let a: str = strings.concat(td, "/a"); let wd: str = strings.concat(a, "/b"); assert(os.mkdir(lib, 448i32) == 0); assert(os.mkdir(a, 448i32) == 0); assert(os.mkdir(wd, 448i32) == 0); testenv.writefile(strings.concat(lib, "/rootprobe.ww"), "package main;\nexport fn main() i32 = { return 0; };\n"); let co: testenv.commandout; let wo: testenv.commandout; runrootargv(wd, td, "rootargv_c", "ww", &co); runrootargv(wd, td, "rootargv_w", "ww_ww", &wo); if (co.code == 0 || wo.code == 0 || !testenv.has(co.stderr, "cannot find module") || !testenv.has(wo.stderr, "cannot find module")) { fail("rootargv0", "argv0 /ww did not resolve selfdir as /"); }; testenv.clean(td); }; // a3 == "" means a two-token argv tail; no row passes a literal "". @test fn flagargs() void = { let a1: []str = ["build", "build", "build", "build", "build", "run", "run", "run", "test", "test", "test", "test", "test", "test"]; let a2: []str = ["-o", "-I", "-L", "-l", "-zz", "-o", "-l", "-zz", "-l", "-zz", "-I", "-o", "-o", "-run"]; let a3: []str = ["", "", "", "", "", "", "", "", "", "", "", "", "x", ""]; let subs: []str = [ "ww build: -o needs an argument", "ww build: -I needs an argument", "ww build: -L needs an argument", "ww build: -l needs an argument", "ww build: unknown flag", "ww run: -o needs an argument", "ww run: -l needs an argument", "ww run: unknown flag", "ww test: unknown flag", "ww test: unknown flag", "ww test: -I needs an argument", "ww test: -o needs an argument", "ww test: -o needs -c for a package target", "ww test: -run needs an argument"]; let i: i32 = 0; for (i < subs.len) { let td: str = testenv.fresh(); let tail: []str = alloc([], 3u64)!; append(tail, a1[i]); append(tail, a2[i]); if (a3[i].len != 0) { append(tail, a3[i]); }; let co: testenv.commandout; let wo: testenv.commandout; // fixed capture names: each row owns a fresh td, and the row // fragment holds '/' bytes that may not name a capture file rundrv(td, "row_c", "ww", tail, &co); rundrv(td, "row_w", "ww_ww", tail, &wo); if (co.code != 2) { fail(subs[i], "ww rc != 2"); }; if (!testenv.has(co.stderr, subs[i])) { fail(subs[i], strings.concat("ww stderr missing fragment; got: ", co.stderr)); }; if (wo.code != co.code || !testenv.same(wo.stderr, co.stderr) || !testenv.same(wo.stdout, co.stdout)) { fail(subs[i], "ww_ww != ww (twin parity on rc/stderr/stdout)"); }; testenv.clean(td); i += 1; }; }; @test fn missingpkg_miss() void = { let td: str = testenv.fresh(); let src: str = strings.concat(td, "/miss.ww"); testenv.writefile(src, strings.concat( "package main;\n", "import nosuchpkg;\n", "export fn main() i32 = { return 0; };\n")); let co: testenv.commandout; // -o needs a writable stem: the output-derived .sepwork follows -o, // so /dev/null would yield an uncreatable /dev/null.sepwork and // mask the missing-package fatal. let av: []str = [testenv.driver("ww"), "build", "-o", strings.concat(td, "/miss.out"), src]; testenv.runcommand(td, td, "miss", av, tmo(), &co); assert(co.termination == exec.termination.EXIT); if (co.code == 0) { fail("missingpkg_miss", "ww accepted a missing import"); }; if (!testenv.has(co.stderr, "cannot find package nosuchpkg")) { fail("missingpkg_miss", "no 'cannot find package nosuchpkg' on stderr"); }; testenv.clean(td); }; @test fn missingpkg_inline() void = { let td: str = testenv.fresh(); let src: str = strings.concat(td, "/inline.ww"); testenv.writefile(src, strings.concat( "package main;\n", "package aa;\n", "export fn getv() i32 = { return 7; };\n", "package main;\n", "import aa;\n", "export fn main() i32 = { return aa.getv(); };\n")); let out: str = strings.concat(td, "/inline"); let co: testenv.commandout; let av: []str = [testenv.driver("ww"), "build", "-o", out, src]; testenv.runcommand(td, td, "inline_build", av, tmo(), &co); assert(co.termination == exec.termination.EXIT); if (co.code != 0) { fail("missingpkg_inline", "inline-package build failed (inline-skip branch regressed)"); }; let ro: testenv.commandout; let rav: []str = [out]; testenv.runcommand(td, td, "inline_run", rav, tmo(), &ro); assert(ro.termination == exec.termination.EXIT); if (ro.code != 7) { fail("missingpkg_inline", "built binary exit != 7"); }; testenv.clean(td); };