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