diff --git a/Makefile b/Makefile index e7814e77..520418d7 100644 --- a/Makefile +++ b/Makefile @@ -382,7 +382,8 @@ LIBENV_WW_TARGETS = $(LIBENV_WW_TESTS:%=wwtest/%) # they run under test-compiler. XMOD_WW_TESTS = test/xmod/collide_test.ww test/xmod/m1_test.ww \ test/xmod/label_test.ww test/xmod/typecheck_test.ww \ - test/xmod/enumcap_test.ww test/xmod/modreset_test.ww + test/xmod/enumcap_test.ww test/xmod/modreset_test.ww \ + test/xmod/direnum_test.ww XMOD_WW_TARGETS = $(XMOD_WW_TESTS:%=wwtest/%) # Ww-native asm-window observers: single-file ww tests under test/asm/ diff --git a/test/wcc/737_direnum.c b/test/wcc/737_direnum.c deleted file mode 100644 index cd5ddee0..00000000 --- a/test/wcc/737_direnum.c +++ /dev/null @@ -1,510 +0,0 @@ -/* - * 737_direnum — driver-level sentinel for directory-package loading. - * Existing imported-directory fixtures plus a private dynamic table pin: - * - * ok — multi-file dir is concatenated by both stages; the entry - * reads cross-pkg bare-leaf fns from sibling files. Build - * must succeed for both C-built `ww` and ww-built `ww_ww`. - * bad — multi-file dirs with conflicting `package ;` clauses; - * root — direct multi-file directory builds, deterministic source order, - * missing/invalid clauses, and root conflicts; - * leaf — an imported directory with one consistent but wrong package name. - * - * Asm-presence isn't checked separately — 968_utf8_run, 966_strings_ - * run, 995_self_rebuild already exercise dir-enum end-to-end at - * binary level. This file pins the cstage/wwstage symmetric error - * path so a regression on either driver fails loud. - */ -#include -#include -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return 1; -} - -static int -stderr_contains(const char *path, const char *needle) -{ - FILE *f = fopen(path, "rb"); - if (!f) return 0; - char buf[4096]; - size_t n = fread(buf, 1, sizeof buf - 1, f); - fclose(f); - buf[n] = '\0'; - return strstr(buf, needle) != NULL; -} - -static int -write_file(const char *path, const char *body) -{ - FILE *f = fopen(path, "wb"); - if (!f) return -1; - int failed = fputs(body, f) == EOF; - if (fclose(f) != 0) failed = 1; - return failed ? -1 : 0; -} - -static int -files_equal(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"), *fb = fopen(b, "rb"); - if (!fa || !fb) { - if (fa) fclose(fa); - if (fb) fclose(fb); - return 0; - } - int ca, cb; - do { - ca = fgetc(fa); - cb = fgetc(fb); - } while (ca == cb && ca != EOF); - fclose(fa); - fclose(fb); - return ca == cb; -} - -static int -file_orders(const char *path, const char *first, const char *second) -{ - FILE *f = fopen(path, "rb"); - if (!f) return 0; - char buf[16384]; - size_t n = fread(buf, 1, sizeof buf - 1, f); - fclose(f); - buf[n] = '\0'; - char *a = strstr(buf, first); - char *b = strstr(buf, second); - return a != NULL && b != NULL && a < b; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char ww[1280], ww_ww[1280]; - snprintf(ww, sizeof ww, "%s/ww", bin); - snprintf(ww_ww, sizeof ww_ww, "%s/ww_ww", bin); - - int total = 0, fail = 0; - int have_owned = 0, have_rootok = 0, have_rootbad = 0; - int have_importbad = 0, have_wanted = 0; - int have_missing = 0, have_invalid = 0; - char owner[128]; - snprintf(owner, sizeof owner, "/tmp/direnum_owner_%d", getpid()); - if (mkdir(owner, 0755) != 0) { - fprintf(stderr, "737[setup]: cannot acquire temp directory: %s\n", - strerror(errno)); - return 1; - } - char errp[512]; - - /* ok: cross-pkg bare-leaf via dir-enum, both stages. `ww run` owns - * /tmp/ww_run_/{main,main.sepwork} and removes both itself, so no - * per-invocation build directory is needed here. */ - { - const char *src = "test/wcc/data/direnum/entry.ww"; - char cmd[2048]; - - snprintf(cmd, sizeof cmd, "%s run %s >/dev/null 2>&1", ww, src); - total++; - if (runwait(cmd) != 0) { - fprintf(stderr, "737[ok-cstage]: ww run %s failed\n", src); - fail++; - } - - if (access(ww_ww, X_OK) == 0) { - snprintf(cmd, sizeof cmd, "%s run %s >/dev/null 2>&1", - ww_ww, src); - total++; - if (runwait(cmd) != 0) { - fprintf(stderr, "737[ok-wwstage]: ww_ww run %s failed\n", src); - fail++; - } - } - } - - /* bad: the package loader validates the complete stored source list - * conflict-first, before checking the import-path leaf. Both stages must - * surface the same directory-level conflict. */ - { - const char *src = "test/wcc/data/direnum/bad_entry.ww"; - - /* Build INPUT stays in-repo. Output, diagnostic, and the retained - * exact .sepwork tree stay beneath this invocation's root. */ - char out[512]; - char cmd[2048]; - - snprintf(out, sizeof out, "%s/bad-c", owner); - snprintf(errp, sizeof errp, "%s/bad-c.err", owner); - snprintf(cmd, sizeof cmd, "%s build -o '%s' %s 2>'%s' >/dev/null", - ww, out, src, errp); - int rc = runwait(cmd); - total++; - if (rc == 0) { - fprintf(stderr, "737[bad-cstage]: expected build failure, succeeded\n"); - fail++; - } else if (!stderr_contains(errp, "conflicting package names")) { - fprintf(stderr, "737[bad-cstage]: stderr missing 'conflicting package names'\n"); - fail++; - } - - if (access(ww_ww, X_OK) == 0) { - snprintf(out, sizeof out, "%s/bad-w", owner); - snprintf(errp, sizeof errp, "%s/bad-w.err", owner); - snprintf(cmd, sizeof cmd, - "%s build -o '%s' %s 2>'%s' >/dev/null", - ww_ww, out, src, errp); - rc = runwait(cmd); - total++; - if (rc == 0) { - fprintf(stderr, "737[bad-wwstage]: expected build failure, succeeded\n"); - fail++; - } else if (!stderr_contains(errp, "conflicting package names")) { - fprintf(stderr, "737[bad-wwstage]: stderr missing 'conflicting package names'\n"); - fail++; - } - } - } - - /* bad-deep: same conflict, but one clause sits behind a >2048-byte - * comment header. The loader's one whole-source header/import read must - * still validate it in both stages. */ - { - const char *src = "test/wcc/data/direnum/bad_deep_entry.ww"; - - /* Same owned-root layout as the shallow conflict above. */ - char out[512]; - char cmd[2048]; - - snprintf(out, sizeof out, "%s/baddeep-c", owner); - snprintf(errp, sizeof errp, "%s/baddeep-c.err", owner); - snprintf(cmd, sizeof cmd, "%s build -o '%s' %s 2>'%s' >/dev/null", - ww, out, src, errp); - int rc = runwait(cmd); - total++; - if (rc == 0) { - fprintf(stderr, "737[bad-deep-cstage]: expected build failure, succeeded\n"); - fail++; - } else if (!stderr_contains(errp, "conflicting package names")) { - fprintf(stderr, "737[bad-deep-cstage]: stderr missing 'conflicting package names'\n"); - fail++; - } - - if (access(ww_ww, X_OK) == 0) { - snprintf(out, sizeof out, "%s/baddeep-w", owner); - snprintf(errp, sizeof errp, "%s/baddeep-w.err", owner); - snprintf(cmd, sizeof cmd, - "%s build -o '%s' %s 2>'%s' >/dev/null", - ww_ww, out, src, errp); - rc = runwait(cmd); - total++; - if (rc == 0) { - fprintf(stderr, "737[bad-deep-wwstage]: expected build failure, succeeded\n"); - fail++; - } else if (!stderr_contains(errp, "conflicting package names")) { - fprintf(stderr, "737[bad-deep-wwstage]: stderr missing 'conflicting package names'\n"); - fail++; - } - } - } - - /* Authoritative Package rows. All inputs, outputs, and retained build - * scratch live beneath the invocation-owned root and are removed below. */ - { - char td[512], rootok[512], rootbad[512], importbad[512]; - char wanted[512], missing[512], invalid[512]; - char path[1024], cmd[4096]; - snprintf(td, sizeof td, "%s/owned", owner); - snprintf(rootok, sizeof rootok, "%s/rootok", td); - snprintf(rootbad, sizeof rootbad, "%s/rootbad", td); - snprintf(importbad, sizeof importbad, "%s/importbad", td); - snprintf(wanted, sizeof wanted, "%s/wanted", importbad); - snprintf(missing, sizeof missing, "%s/missing", td); - snprintf(invalid, sizeof invalid, "%s/invalid", td); - - int setup = 0; - if (mkdir(td, 0755) != 0) setup = 1; - else have_owned = 1; - if (!setup && mkdir(rootok, 0755) != 0) setup = 1; - else if (!setup) have_rootok = 1; - if (!setup && mkdir(rootbad, 0755) != 0) setup = 1; - else if (!setup) have_rootbad = 1; - if (!setup && mkdir(importbad, 0755) != 0) setup = 1; - else if (!setup) have_importbad = 1; - if (!setup && mkdir(wanted, 0755) != 0) setup = 1; - else if (!setup) have_wanted = 1; - if (!setup && mkdir(missing, 0755) != 0) setup = 1; - else if (!setup) have_missing = 1; - if (!setup && mkdir(invalid, 0755) != 0) setup = 1; - else if (!setup) have_invalid = 1; - if (!setup) snprintf(path, sizeof path, "%s/z.ww", rootok); - if (!setup) setup = write_file(path, - "package rootok;\n// ORDER-Z\n" - "fn value() i32 = { return 17; };\n"); - if (!setup) snprintf(path, sizeof path, "%s/a.ww", rootok); - if (!setup) setup = write_file(path, - "// leading comment is part of the loader grammar\n" - "package rootok;\n// ORDER-A\n" - "fn main() i32 = { return value(); };\n"); - if (!setup) snprintf(path, sizeof path, "%s/a.ww", rootbad); - if (!setup) setup = write_file(path, - "package rootbad;\nfn main() i32 = { return 0; };\n"); - if (!setup) snprintf(path, sizeof path, "%s/b.ww", rootbad); - if (!setup) setup = write_file(path, - "package other;\nfn spare() i32 = { return 0; };\n"); - if (!setup) snprintf(path, sizeof path, "%s/entry.ww", importbad); - if (!setup) setup = write_file(path, - "package main;\nimport wanted;\n" - "fn main() i32 = { return wanted.value(); };\n"); - if (!setup) snprintf(path, sizeof path, "%s/a.ww", wanted); - if (!setup) setup = write_file(path, - "package other;\nexport fn value() i32 = { return 0; };\n"); - if (!setup) snprintf(path, sizeof path, "%s/a.ww", missing); - if (!setup) setup = write_file(path, - "fn main() i32 = { return 0; };\n"); - if (!setup) snprintf(path, sizeof path, "%s/a.ww", invalid); - if (!setup) setup = write_file(path, - "package 7bad;\nfn main() i32 = { return 0; };\n"); - - if (setup) { - fprintf(stderr, "737[owned-setup]: cannot create private fixtures\n"); - fail++; - total++; - } else { - const char *drivers[] = { ww, ww_ww }; - const char *stages[] = { "cstage", "wwstage" }; - char units[2][512]; - int built[2] = {0, 0}; - for (int s = 0; s < 2; s++) { - if (access(drivers[s], X_OK) != 0) continue; - char out[512], err[512]; - snprintf(out, sizeof out, "%s/root-%d", td, s); - snprintf(err, sizeof err, "%s/root-%d.err", td, s); - snprintf(cmd, sizeof cmd, - "%s build -o '%s' '%s' " - ">/dev/null 2>'%s'", drivers[s], out, - rootok, err); - int rc = runwait(cmd); - total++; - if (rc != 0 || runwait(out) != 17) { - fprintf(stderr, - "737[root-%s]: build/run failed (build=%d)\n", - stages[s], rc); - fail++; - continue; - } - snprintf(units[s], sizeof units[s], - "%s.sepwork/__root.unit.ww", out); - if (!file_orders(units[s], "ORDER-A", "ORDER-Z")) { - fprintf(stderr, - "737[root-%s]: stored sources are not byte-sorted\n", - stages[s]); - fail++; - } else { - built[s] = 1; - } - } - if (built[0] && built[1]) { - total++; - if (!files_equal(units[0], units[1])) { - fprintf(stderr, - "737[root-parity]: C/WW units differ\n"); - fail++; - } - } - - struct rejectrow { - const char *tag; - const char *target; - const char *needle; - } rows[] = { - { "root-conflict", rootbad, "conflicting package names" }, - { "import-leaf", NULL, "does not match import path" }, - { "root-missing", missing, "invalid or missing package clause" }, - { "root-invalid", invalid, "invalid or missing package clause" }, - }; - char entry[512]; - snprintf(entry, sizeof entry, "%s/entry.ww", importbad); - rows[1].target = entry; - for (int r = 0; r < 4; r++) { - char errors[2][512]; - int ran[2] = {0, 0}; - for (int s = 0; s < 2; s++) { - if (access(drivers[s], X_OK) != 0) continue; - char out[512]; - snprintf(out, sizeof out, "%s/%s-%d", td, - rows[r].tag, s); - snprintf(errors[s], sizeof errors[s], "%s/%s-%d.err", - td, rows[r].tag, s); - snprintf(cmd, sizeof cmd, - "%s build -o '%s' '%s' " - ">/dev/null 2>'%s'", drivers[s], out, - rows[r].target, errors[s]); - int rc = runwait(cmd); - total++; - ran[s] = 1; - if (rc == 0 - || !stderr_contains(errors[s], rows[r].needle)) { - fprintf(stderr, - "737[%s-%s]: reject/diagnostic mismatch\n", - rows[r].tag, stages[s]); - fail++; - } - } - if (ran[0] && ran[1]) { - total++; - if (!files_equal(errors[0], errors[1])) { - fprintf(stderr, - "737[%s-parity]: C/WW diagnostics differ\n", - rows[r].tag); - fail++; - } - } - } - } - } - - int cleanup_fail = 0; - char path[1024], cmd[2048]; - const char *top_stems[] = { "bad-c", "bad-w", "baddeep-c", "baddeep-w" }; - const char *suffixes[] = { "", ".err" }; - for (size_t i = 0; i < sizeof top_stems / sizeof top_stems[0]; i++) { - snprintf(path, sizeof path, "%s/%s.sepwork", owner, top_stems[i]); - snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path); - if (runwait(cmd) != 0) { - fprintf(stderr, "737[cleanup]: remove %s failed\n", path); - cleanup_fail = 1; - } - } - const char *reject_tags[] = { - "root-conflict", "import-leaf", "root-missing", "root-invalid" - }; - if (have_owned) { - for (int s = 0; s < 2; s++) { - snprintf(path, sizeof path, "%s/owned/root-%d.sepwork", owner, s); - snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path); - if (runwait(cmd) != 0) { - fprintf(stderr, "737[cleanup]: remove %s failed\n", path); - cleanup_fail = 1; - } - for (size_t r = 0; r < sizeof reject_tags / sizeof reject_tags[0]; r++) { - snprintf(path, sizeof path, "%s/owned/%s-%d.sepwork", - owner, reject_tags[r], s); - snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path); - if (runwait(cmd) != 0) { - fprintf(stderr, "737[cleanup]: remove %s failed\n", path); - cleanup_fail = 1; - } - } - } - } - - for (size_t i = 0; i < sizeof top_stems / sizeof top_stems[0]; i++) { - for (size_t k = 0; k < sizeof suffixes / sizeof suffixes[0]; k++) { - snprintf(path, sizeof path, "%s/%s%s", owner, - top_stems[i], suffixes[k]); - if (unlink(path) != 0 && errno != ENOENT) { - fprintf(stderr, "737[cleanup]: unlink %s: %s\n", - path, strerror(errno)); - cleanup_fail = 1; - } - } - } - if (have_owned) { - for (int s = 0; s < 2; s++) { - for (size_t k = 0; k < sizeof suffixes / sizeof suffixes[0]; k++) { - snprintf(path, sizeof path, "%s/owned/root-%d%s", - owner, s, suffixes[k]); - if (unlink(path) != 0 && errno != ENOENT) { - fprintf(stderr, "737[cleanup]: unlink %s: %s\n", - path, strerror(errno)); - cleanup_fail = 1; - } - } - for (size_t r = 0; r < sizeof reject_tags / sizeof reject_tags[0]; r++) { - for (size_t k = 0; k < sizeof suffixes / sizeof suffixes[0]; k++) { - snprintf(path, sizeof path, "%s/owned/%s-%d%s", - owner, reject_tags[r], s, suffixes[k]); - if (unlink(path) != 0 && errno != ENOENT) { - fprintf(stderr, "737[cleanup]: unlink %s: %s\n", - path, strerror(errno)); - cleanup_fail = 1; - } - } - } - } - } - - struct owned_path { - const char *name; - int owned; - } fixture_files[] = { - { "owned/rootok/z.ww", have_rootok }, - { "owned/rootok/a.ww", have_rootok }, - { "owned/rootbad/a.ww", have_rootbad }, - { "owned/rootbad/b.ww", have_rootbad }, - { "owned/importbad/entry.ww", have_importbad }, - { "owned/importbad/wanted/a.ww", have_wanted }, - { "owned/missing/a.ww", have_missing }, - { "owned/invalid/a.ww", have_invalid }, - }; - for (size_t i = 0; i < sizeof fixture_files / sizeof fixture_files[0]; i++) { - if (!fixture_files[i].owned) continue; - snprintf(path, sizeof path, "%s/%s", owner, fixture_files[i].name); - if (unlink(path) != 0 && errno != ENOENT) { - fprintf(stderr, "737[cleanup]: unlink %s: %s\n", - path, strerror(errno)); - cleanup_fail = 1; - } - } - struct owned_path fixture_dirs[] = { - { "owned/importbad/wanted", have_wanted }, - { "owned/rootok", have_rootok }, - { "owned/rootbad", have_rootbad }, - { "owned/importbad", have_importbad }, - { "owned/missing", have_missing }, - { "owned/invalid", have_invalid }, - { "owned", have_owned }, - }; - for (size_t i = 0; i < sizeof fixture_dirs / sizeof fixture_dirs[0]; i++) { - if (!fixture_dirs[i].owned) continue; - snprintf(path, sizeof path, "%s/%s", owner, fixture_dirs[i].name); - if (rmdir(path) != 0 && errno != ENOENT) { - fprintf(stderr, "737[cleanup]: rmdir %s: %s\n", - path, strerror(errno)); - cleanup_fail = 1; - } - } - if (rmdir(owner) != 0) { - fprintf(stderr, "737[cleanup]: rmdir %s: %s\n", - owner, strerror(errno)); - cleanup_fail = 1; - } - if (cleanup_fail) fail++; - - if (fail) { - fprintf(stderr, "737_direnum: %d/%d fixtures failed\n", fail, total); - return 1; - } - printf("737_direnum: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/xmod/direnum_test.ww b/test/xmod/direnum_test.ww new file mode 100644 index 00000000..88bca2dd --- /dev/null +++ b/test/xmod/direnum_test.ww @@ -0,0 +1,200 @@ +package direnum_test; + +// Directory-package loader observers on both driver stages. Port of +// the retired native carrier test/wcc/737_direnum.c; every assertion +// preserved, the bad/bad-deep rows strengthened with the stderr +// parity leg the C carrier reserved for the reject table. +// +// okrun — the in-repo tree test/wcc/data/direnum/ (entry imports +// multi-file dir ok/ with a.ww+b.ww both package ok, cross-pkg +// bare-leaf calls): `ww run` exits 0 on BOTH stages. +// +// conflict — bad_entry.ww imports a dir with conflicting `package` +// clauses; bad_deep_entry.ww the same with one clause behind a +// >2048-byte comment header (the loader's whole-source header read). +// Both stages must FAIL with "conflicting package names", and the +// two stages' diagnostics must be byte-identical. +// +// rootorder — a private rootok/ dir built DIRECTLY (z.ww ORDER-Z +// value()=17 vs a.ww ORDER-A leading comment + main): build exit 0, +// the binary exits 17, and the driver's .sepwork/__root.unit.ww +// stores ORDER-A strictly before ORDER-Z (byte-sorted deterministic +// source order); the two stages' units are byte-identical. +// +// rejects — the authoritative loader-reject table over private trees: +// root-conflict ("conflicting package names"), import-leaf ("does +// not match import path"), root-missing and root-invalid ("invalid +// or missing package clause"); per row both stages fail, carry the +// needle, and their captured stderr is byte-identical. +// +// Dropped C machinery, not assertions: the ww_ww-absent skip gate +// (the Make target declares both drivers) and the unlink/rmdir +// accounting (testenv.clean asserts the removal). + +import os; +import os.exec; +import strings; +import testenv; +import time; + +fn fail(label: str, why: str) void = { + let m: str = strings.concat("direnum FAIL: ", label, " -- ", why, + "\n"); + os.write(2, m.ptr, m.len: u64); + assert(false); +}; + +fn tmo() time.duration = { + return (240i64 * (time.second: i64)): time.duration; +}; + +// -1 encodes an abnormal (non-EXIT) termination, never a valid code. +fn runcode(dir: str, name: str, argv: []str) i32 = { + let co: testenv.commandout; + testenv.runcommand(dir, dir, name, argv, tmo(), &co); + if (co.termination != exec.termination.EXIT) { return -1; }; + return co.code; +}; + +fn datasrc(name: str) str = { + return strings.concat(testenv.repo(), "/test/wcc/data/direnum/", + name); +}; + +@test fn okrun() void = { + let td: str = testenv.fresh(); + let src: str = datasrc("entry.ww"); + let drvs: []str = ["ww", "ww_ww"]; + let s: i32 = 0; + for (s < 2) { + let av: []str = [testenv.driver(drvs[s]), "run", src]; + if (runcode(td, strings.concat("run_", drvs[s]), av) != 0) { + fail("okrun", strings.concat(drvs[s], " run failed on the ", + "multi-file imported dir (cross-pkg bare-leaf calls)")); + }; + s += 1; + }; + testenv.clean(td); +}; + +// A build expected to FAIL with `needle` on both stages, diagnostics +// byte-identical across stages. +fn rejectpair(label: str, td: str, target: str, needle: str) void = { + let drvs: []str = ["ww", "ww_ww"]; + let errs: []str = ["", ""]; + let s: i32 = 0; + for (s < 2) { + let out: str = strings.concat(td, "/", label, "-", drvs[s]); + let co: testenv.commandout; + let av: []str = [testenv.driver(drvs[s]), "build", "-o", out, + target]; + testenv.runcommand(td, td, + strings.concat(label, "_", drvs[s]), av, tmo(), &co); + if (co.termination != exec.termination.EXIT || co.code == 0) { + fail(label, strings.concat(drvs[s], + " expected build failure, succeeded")); + }; + if (!testenv.has(co.stderr, needle)) { + fail(label, strings.concat(drvs[s], " stderr missing '", + needle, "'")); + }; + errs[s] = co.stderr; + s += 1; + }; + if (!testenv.same(errs[0], errs[1])) { + fail(label, "C/WW diagnostics differ"); + }; +}; + +@test fn conflict() void = { + let td: str = testenv.fresh(); + rejectpair("bad", td, datasrc("bad_entry.ww"), + "conflicting package names"); + rejectpair("bad-deep", td, datasrc("bad_deep_entry.ww"), + "conflicting package names"); + testenv.clean(td); +}; + +@test fn rootorder() void = { + let td: str = testenv.fresh(); + let rootok: str = strings.concat(td, "/rootok"); + assert(os.mkdir(rootok, 493) == 0); + testenv.writefile(strings.concat(rootok, "/z.ww"), strings.concat( + "package rootok;\n// ORDER-Z\n", + "fn value() i32 = { return 17; };\n")); + testenv.writefile(strings.concat(rootok, "/a.ww"), strings.concat( + "// leading comment is part of the loader grammar\n", + "package rootok;\n// ORDER-A\n", + "fn main() i32 = { return value(); };\n")); + let drvs: []str = ["ww", "ww_ww"]; + let units: []str = ["", ""]; + let s: i32 = 0; + for (s < 2) { + let out: str = strings.concat(td, "/root-", drvs[s]); + let av: []str = [testenv.driver(drvs[s]), "build", "-o", out, + rootok]; + if (runcode(td, strings.concat("build_", drvs[s]), av) != 0) { + fail("rootorder", strings.concat(drvs[s], + " direct dir build failed")); + }; + let rav: []str = [out]; + if (runcode(td, strings.concat("run_", drvs[s]), rav) != 17) { + fail("rootorder", strings.concat(drvs[s], " exit != 17")); + }; + units[s] = testenv.readfile(strings.concat(out, + ".sepwork/__root.unit.ww")); + let a: i32 = testenv.pos(units[s], "ORDER-A"); + let z: i32 = testenv.pos(units[s], "ORDER-Z"); + if (a < 0 || z < 0 || a >= z) { + fail("rootorder", strings.concat(drvs[s], + " stored sources are not byte-sorted")); + }; + s += 1; + }; + if (!testenv.same(units[0], units[1])) { + fail("rootorder", "C/WW __root.unit.ww differ"); + }; + testenv.clean(td); +}; + +@test fn rejects() void = { + let td: str = testenv.fresh(); + let rootbad: str = strings.concat(td, "/rootbad"); + let importbad: str = strings.concat(td, "/importbad"); + let wanted: str = strings.concat(importbad, "/wanted"); + let missing: str = strings.concat(td, "/missing"); + let invalid: str = strings.concat(td, "/invalid"); + assert(os.mkdir(rootbad, 493) == 0); + assert(os.mkdir(importbad, 493) == 0); + assert(os.mkdir(wanted, 493) == 0); + assert(os.mkdir(missing, 493) == 0); + assert(os.mkdir(invalid, 493) == 0); + testenv.writefile(strings.concat(rootbad, "/a.ww"), + "package rootbad;\nfn main() i32 = { return 0; };\n"); + testenv.writefile(strings.concat(rootbad, "/b.ww"), + "package other;\nfn spare() i32 = { return 0; };\n"); + testenv.writefile(strings.concat(importbad, "/entry.ww"), + strings.concat("package main;\nimport wanted;\n", + "fn main() i32 = { return wanted.value(); };\n")); + testenv.writefile(strings.concat(wanted, "/a.ww"), + "package other;\nexport fn value() i32 = { return 0; };\n"); + testenv.writefile(strings.concat(missing, "/a.ww"), + "fn main() i32 = { return 0; };\n"); + testenv.writefile(strings.concat(invalid, "/a.ww"), + "package 7bad;\nfn main() i32 = { return 0; };\n"); + + let tags: []str = ["root-conflict", "import-leaf", "root-missing", + "root-invalid"]; + let targets: []str = [rootbad, strings.concat(importbad, + "/entry.ww"), missing, invalid]; + let needles: []str = ["conflicting package names", + "does not match import path", + "invalid or missing package clause", + "invalid or missing package clause"]; + let r: i32 = 0; + for (r < tags.len) { + rejectpair(tags[r], td, targets[r], needles[r]); + r += 1; + }; + testenv.clean(td); +};