From 200f51ca94ba09a7955994c2120b355410ec2aeb Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Thu, 18 Jun 2026 12:09:25 +0900 Subject: [PATCH] ww: resolve self-named import to the dir-package, not a sibling file (M4 E3, #98) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver searchpath is srcd-first (srcd = the entry file's directory). A co-located black-box test lib//test.ww makes srcd=lib/, so resolving `import ` hit the sibling-FILE branch lib//.ww and folded it inline into the consumer unit under the wrong module tag ("package does not match import path ") — 7 lib-run tests fail under separate compilation. The combined amalgamator tolerated the co-location; only sep surfaced it. Resolve a package directory-first: walk ALL searchpath entries for a directory match, and only fall back to a file match if no directory exists anywhere. A dir-package now beats a same-named sibling file (fixes the self-named shadow), while a leaf package with no directory (e.g. lib/encoding/hex) still resolves via its file. This realizes the driver's "a module is the directory" intent; the originally-specced per-directory suppression was rejected because it broke leaf packages (rob-pike). Both stages (cmd/ww/main.c + selfhost twin). The dir-beats-earlier-file precedence change is latent and loud-failing (#101). Move-set: ww + ww_ww (driver) only; w6c_ww/wwdump_ww/w6a_ww/w6l_ww HOLD. Gate: test/wcc/989_coloimport_sep.c (table-driven, both stages). --- Makefile | 12 ++ cmd/ww/main.c | 69 ++++--- selfhost/cmd/ww/main.combined.ww | 102 +++++----- selfhost/cmd/ww/main.ww | 102 +++++----- test/wcc/989_coloimport_sep.c | 236 ++++++++++++++++++++++ test/wcc/data/colo98/gadget/gadget.ww | 7 + test/wcc/data/colo98/widget/widget.ww | 5 + test/wcc/data/colo98/widget/widgettest.ww | 7 + 8 files changed, 421 insertions(+), 119 deletions(-) create mode 100644 test/wcc/989_coloimport_sep.c create mode 100644 test/wcc/data/colo98/gadget/gadget.ww create mode 100644 test/wcc/data/colo98/widget/widget.ww create mode 100644 test/wcc/data/colo98/widget/widgettest.ww diff --git a/Makefile b/Makefile index 7f8fa844..fdf24e80 100644 --- a/Makefile +++ b/Makefile @@ -572,6 +572,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_pkgcache_run \ $(BIN)/test_c6soak_run \ $(BIN)/test_septest_run \ + $(BIN)/test_coloimport_sep \ $(BIN)/test_floatlit_run \ $(BIN)/test_checked_run \ $(BIN)/test_floatarr_run \ @@ -3258,6 +3259,17 @@ $(BIN)/test_septest_run: test/wcc/989_septest_run.c $(BIN)/ww $(BIN)/ww_ww \ $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +# 989_coloimport_sep — #98 E3 flip-blocker: a co-located `_test` entry must +# not let its srcd shadow a self-named transitive `import ` into the +# sibling file. Drives BOTH driver stages on a co-located fixture, asserting +# run-exit 0 (pre-fix the importer's fold makes w6c reject = the non-vacuity +# teeth), the dir-package registers its own .unit.ww/.wwi, and cs==ww per-pkg +# .s/.wwi (rule 10). Needs both driver + compiler + asm + linker stages. +$(BIN)/test_coloimport_sep: test/wcc/989_coloimport_sep.c $(BIN)/ww $(BIN)/ww_ww \ + $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ + $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_floatlit_run: test/wcc/989_floatlit_run.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< diff --git a/cmd/ww/main.c b/cmd/ww/main.c index a61c225b..046b52ee 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -119,21 +119,24 @@ import_path_form(const char *name, char *out, size_t outsz) out[i] = '\0'; } -/* try // as a directory, then /.ww as a file. - * Sets *is_dir on hit. Symmetric with wwstage locatein for byte-id - * driver output (rule 10). The legacy //.ww form - * was dropped in task #22 — directory-as-module enumeration replaces - * it, mirroring ref/hare/hare/module/srcs.ha (Hare has no fallback - * matching `foo/foo.ha`; a module IS the directory). */ +/* try // as a directory (want_dir), else /.ww as + * a file. Sets *is_dir on hit. Symmetric with wwstage locatein for + * byte-id driver output (rule 10). The legacy //.ww + * form was dropped in task #22 — directory-as-module enumeration + * replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no + * fallback matching `foo/foo.ha`; a module IS the directory). */ static int locate_import_in(const char *dir, const char *path_form, char *out, - size_t outsz, int *is_dir) + size_t outsz, int *is_dir, int want_dir) { struct stat st; - snprintf(out, outsz, "%s/%s", dir, path_form); - if (stat(out, &st) == 0 && S_ISDIR(st.st_mode)) { - *is_dir = 1; - return 1; + if (want_dir) { + snprintf(out, outsz, "%s/%s", dir, path_form); + if (stat(out, &st) == 0 && S_ISDIR(st.st_mode)) { + *is_dir = 1; + return 1; + } + return 0; } snprintf(out, outsz, "%s/%s.ww", dir, path_form); if (access(out, 0) == 0) { @@ -144,25 +147,41 @@ locate_import_in(const char *dir, const char *path_form, char *out, } /* Walk a colon-separated dirlist trying to resolve `path_form`. Returns - * 1 on the first hit and writes the concrete path + dir/file marker. */ + * 1 on the first hit and writes the concrete path + dir/file marker. + * + * #98: "a module IS the directory" — a directory-package on ANY entry + * wins over a same-named sibling FILE on an EARLIER entry. The driver + * builds the searchpath srcd-first; a co-located `lib//test.ww` + * entry makes srcd = lib/, so a self-named `import ` would + * else file-hit the sibling lib//.ww and (under --sep) fold + * inline under the wrong module-reset → "package does not match + * import path ". Two passes — directories first, files only + * if no directory matches anywhere — let lib// resolve as the dir + * while a genuine leaf package with no directory (e.g. lib/encoding/hex + * imported bare as `hex`, reachable only via its file in srcd) still + * resolves in the file pass. Latent: a dir-package now beats an + * earlier-entry same-named sibling FILE — loud-failing, none in the + * corpus; tracked as #101. */ static int locate_import(const char *dirs, const char *path_form, char *out, size_t outsz, int *is_dir) { - const char *p = dirs; - while (*p) { - const char *e = strchr(p, ':'); - size_t n = e ? (size_t)(e - p) : strlen(p); - if (n > 0 && n < outsz) { - char dir[1024]; - if (n >= sizeof dir) n = sizeof dir - 1; - memcpy(dir, p, n); - dir[n] = '\0'; - if (locate_import_in(dir, path_form, out, outsz, - is_dir)) return 1; + for (int want_dir = 1; want_dir >= 0; want_dir--) { + const char *p = dirs; + while (*p) { + const char *e = strchr(p, ':'); + size_t n = e ? (size_t)(e - p) : strlen(p); + if (n > 0 && n < outsz) { + char dir[1024]; + if (n >= sizeof dir) n = sizeof dir - 1; + memcpy(dir, p, n); + dir[n] = '\0'; + if (locate_import_in(dir, path_form, out, + outsz, is_dir, want_dir)) return 1; + } + if (!e) break; + p = e + 1; } - if (!e) break; - p = e + 1; } return 0; } diff --git a/selfhost/cmd/ww/main.combined.ww b/selfhost/cmd/ww/main.combined.ww index 0973c617..f4a9e756 100644 --- a/selfhost/cmd/ww/main.combined.ww +++ b/selfhost/cmd/ww/main.combined.ww @@ -3112,14 +3112,14 @@ fn importpathform(name: *u8, namelen: u64) *u8 = { return buf.ptr; }; -// Try // as a directory, then /.ww as a file. -// Sets *isdir on hit. Symmetric with cstage locate_import_in for -// byte-id driver output (rule 10). The legacy //.ww +// Try // as a directory (wantdir != 0), else /.ww +// as a file. Sets *isdir on hit. Symmetric with cstage locate_import_in +// for byte-id driver output (rule 10). The legacy //.ww // form was dropped in task #22 — directory-as-module enumeration // replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no // `foo/foo.ha` fallback; a module IS the directory). fn locatein(dir: *u8, dirlen: u64, - pathform: *u8, pflen: u64, isdir: *i32) *u8 = { + pathform: *u8, pflen: u64, isdir: *i32, wantdir: i32) *u8 = { let buf: []u8 = alloc([], (os.PATH_MAX: u64))!; let off: u64 = 0u64; let i: u64 = 0u64; @@ -3129,63 +3129,71 @@ fn locatein(dir: *u8, dirlen: u64, i = 0u64; for (i < pflen) { buf[off + i] = pathform[i]; i += 1u64; }; off += pflen; + if (wantdir != 0i32) { + buf[off] = 0u8; + let fi: os.filestat; + let r: (void | os.oserror) = os.stat(&fi, pathstr(buf.ptr)); + match (r) { + case void => { + let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT + if (t == os.mode.DIR: u32) { + *isdir = 1; + return buf.ptr; + }; + }; + case let e: os.oserror => void; + }; + return nil; + }; + buf[off] = 46u8; off += 1u64; // '.' + buf[off] = 119u8; off += 1u64; // 'w' + buf[off] = 119u8; off += 1u64; // 'w' buf[off] = 0u8; - let fi: os.filestat; - let r: (void | os.oserror) = os.stat(&fi, pathstr(buf.ptr)); - let isdirhit: bool = false; - match (r) { - case void => { - let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT - if (t == os.mode.DIR: u32) { isdirhit = true; }; - }; - case let e: os.oserror => void; - }; - if (isdirhit) { - *isdir = 1; - return buf.ptr; - }; - - let buf2: []u8 = alloc([], (os.PATH_MAX: u64))!; - off = 0u64; - i = 0u64; - for (i < dirlen) { buf2[off + i] = dir[i]; i += 1u64; }; - off += dirlen; - buf2[off] = 47u8; off += 1u64; - i = 0u64; - for (i < pflen) { buf2[off + i] = pathform[i]; i += 1u64; }; - off += pflen; - buf2[off] = 46u8; off += 1u64; // '.' - buf2[off] = 119u8; off += 1u64; // 'w' - buf2[off] = 119u8; off += 1u64; // 'w' - buf2[off] = 0u8; - if (os.access(pathstr(buf2.ptr), 0i32) == 0) { + if (os.access(pathstr(buf.ptr), 0i32) == 0) { *isdir = 0; - return buf2.ptr; + return buf.ptr; }; return nil; }; // Walk a colon-separated dirlist, return first hit or nil. Sets // *isdir on hit. +// +// #98: "a module IS the directory" — a directory-package on ANY entry +// wins over a same-named sibling FILE on an EARLIER entry. The driver +// builds the searchpath srcd-first; a co-located `lib//test.ww` +// entry makes srcd = lib/, so a self-named `import ` would +// else file-hit the sibling lib//.ww and (under --sep) fold +// inline under the wrong module-reset. Two passes — directories first, +// files only if no directory matches anywhere — let lib// resolve +// as the dir while a genuine leaf package with no directory (e.g. +// lib/encoding/hex imported bare as `hex`, reachable only via its file +// in srcd) still resolves in the file pass. Latent: a dir-package now +// beats an earlier-entry same-named sibling FILE — loud-failing, none +// in the corpus; tracked as #101. fn locateimport(dirs: *u8, name: *u8, namelen: u64, isdir: *i32) *u8 = { let pathform: *u8 = importpathform(name, namelen); let pflen: u64 = cstrlen(pathform); let total: u64 = cstrlen(dirs); - let p: u64 = 0u64; - for (p < total) { - let q: u64 = p; - for (q < total) { - if (dirs[q] == ':') { break; }; - q += 1u64; + let wantdir: i32 = 1i32; + for (wantdir >= 0i32) { + let p: u64 = 0u64; + for (p < total) { + let q: u64 = p; + for (q < total) { + if (dirs[q] == ':') { break; }; + q += 1u64; + }; + let seglen: u64 = q - p; + if (seglen > 0u64) { + let hit: *u8 = locatein(dirs + p, seglen, + pathform, pflen, isdir, wantdir); + if (hit != nil) { return hit; }; + }; + p = q + 1u64; }; - let seglen: u64 = q - p; - if (seglen > 0u64) { - let hit: *u8 = locatein(dirs + p, seglen, - pathform, pflen, isdir); - if (hit != nil) { return hit; }; - }; - p = q + 1u64; + wantdir -= 1i32; }; return nil; }; diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index 4554dd10..0eecdda1 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -238,14 +238,14 @@ fn importpathform(name: *u8, namelen: u64) *u8 = { return buf.ptr; }; -// Try // as a directory, then /.ww as a file. -// Sets *isdir on hit. Symmetric with cstage locate_import_in for -// byte-id driver output (rule 10). The legacy //.ww +// Try // as a directory (wantdir != 0), else /.ww +// as a file. Sets *isdir on hit. Symmetric with cstage locate_import_in +// for byte-id driver output (rule 10). The legacy //.ww // form was dropped in task #22 — directory-as-module enumeration // replaces it, mirroring ref/hare/hare/module/srcs.ha (Hare has no // `foo/foo.ha` fallback; a module IS the directory). fn locatein(dir: *u8, dirlen: u64, - pathform: *u8, pflen: u64, isdir: *i32) *u8 = { + pathform: *u8, pflen: u64, isdir: *i32, wantdir: i32) *u8 = { let buf: []u8 = alloc([], (os.PATH_MAX: u64))!; let off: u64 = 0u64; let i: u64 = 0u64; @@ -255,63 +255,71 @@ fn locatein(dir: *u8, dirlen: u64, i = 0u64; for (i < pflen) { buf[off + i] = pathform[i]; i += 1u64; }; off += pflen; + if (wantdir != 0i32) { + buf[off] = 0u8; + let fi: os.filestat; + let r: (void | os.oserror) = os.stat(&fi, pathstr(buf.ptr)); + match (r) { + case void => { + let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT + if (t == os.mode.DIR: u32) { + *isdir = 1; + return buf.ptr; + }; + }; + case let e: os.oserror => void; + }; + return nil; + }; + buf[off] = 46u8; off += 1u64; // '.' + buf[off] = 119u8; off += 1u64; // 'w' + buf[off] = 119u8; off += 1u64; // 'w' buf[off] = 0u8; - let fi: os.filestat; - let r: (void | os.oserror) = os.stat(&fi, pathstr(buf.ptr)); - let isdirhit: bool = false; - match (r) { - case void => { - let t: u32 = (fi.mode: u32) & 61440u32; // S_IFMT - if (t == os.mode.DIR: u32) { isdirhit = true; }; - }; - case let e: os.oserror => void; - }; - if (isdirhit) { - *isdir = 1; - return buf.ptr; - }; - - let buf2: []u8 = alloc([], (os.PATH_MAX: u64))!; - off = 0u64; - i = 0u64; - for (i < dirlen) { buf2[off + i] = dir[i]; i += 1u64; }; - off += dirlen; - buf2[off] = 47u8; off += 1u64; - i = 0u64; - for (i < pflen) { buf2[off + i] = pathform[i]; i += 1u64; }; - off += pflen; - buf2[off] = 46u8; off += 1u64; // '.' - buf2[off] = 119u8; off += 1u64; // 'w' - buf2[off] = 119u8; off += 1u64; // 'w' - buf2[off] = 0u8; - if (os.access(pathstr(buf2.ptr), 0i32) == 0) { + if (os.access(pathstr(buf.ptr), 0i32) == 0) { *isdir = 0; - return buf2.ptr; + return buf.ptr; }; return nil; }; // Walk a colon-separated dirlist, return first hit or nil. Sets // *isdir on hit. +// +// #98: "a module IS the directory" — a directory-package on ANY entry +// wins over a same-named sibling FILE on an EARLIER entry. The driver +// builds the searchpath srcd-first; a co-located `lib//test.ww` +// entry makes srcd = lib/, so a self-named `import ` would +// else file-hit the sibling lib//.ww and (under --sep) fold +// inline under the wrong module-reset. Two passes — directories first, +// files only if no directory matches anywhere — let lib// resolve +// as the dir while a genuine leaf package with no directory (e.g. +// lib/encoding/hex imported bare as `hex`, reachable only via its file +// in srcd) still resolves in the file pass. Latent: a dir-package now +// beats an earlier-entry same-named sibling FILE — loud-failing, none +// in the corpus; tracked as #101. fn locateimport(dirs: *u8, name: *u8, namelen: u64, isdir: *i32) *u8 = { let pathform: *u8 = importpathform(name, namelen); let pflen: u64 = cstrlen(pathform); let total: u64 = cstrlen(dirs); - let p: u64 = 0u64; - for (p < total) { - let q: u64 = p; - for (q < total) { - if (dirs[q] == ':') { break; }; - q += 1u64; + let wantdir: i32 = 1i32; + for (wantdir >= 0i32) { + let p: u64 = 0u64; + for (p < total) { + let q: u64 = p; + for (q < total) { + if (dirs[q] == ':') { break; }; + q += 1u64; + }; + let seglen: u64 = q - p; + if (seglen > 0u64) { + let hit: *u8 = locatein(dirs + p, seglen, + pathform, pflen, isdir, wantdir); + if (hit != nil) { return hit; }; + }; + p = q + 1u64; }; - let seglen: u64 = q - p; - if (seglen > 0u64) { - let hit: *u8 = locatein(dirs + p, seglen, - pathform, pflen, isdir); - if (hit != nil) { return hit; }; - }; - p = q + 1u64; + wantdir -= 1i32; }; return nil; }; diff --git a/test/wcc/989_coloimport_sep.c b/test/wcc/989_coloimport_sep.c new file mode 100644 index 00000000..9133d4fc --- /dev/null +++ b/test/wcc/989_coloimport_sep.c @@ -0,0 +1,236 @@ +/* + * 989_coloimport_sep — #98 E3 flip-blocker: a co-located `_test` entry must + * not shadow the dir-package it imports under `ww test --sep`. + * + * The driver builds the sep searchpath srcd-first (srcd = the entry file's + * own directory). When the entry is a co-located black-box test + * `lib//test.ww`, srcd = `lib/`, so a transitive `import + * ` (reached from another package in the closure) hits the sibling + * FILE `lib//.ww` (is_dir=0) FIRST — treated as an intra-package + * split, folded INLINE into the importer's unit under that importer's + * module-reset → w6c rejects `package does not match import path + * `. locate_import / locateimport now walk the searchpath in + * TWO passes — pass 1 seeks a DIRECTORY on any entry, pass 2 (only if no + * directory matches anywhere) seeks a FILE — so the dir-package `/` + * on a later entry beats the same-named sibling FILE on the srcd entry, + * while a genuine leaf package with no directory still resolves in the + * file pass. This gate stands for the 7 lib-run tests + * (904/966/967/972/974/976/977) the bug blocked under sep. + * + * Fixture (test/wcc/data/colo98/, the exact srcd-shadow shape, minimised): + * widget/widget.ww package widget — the sibling primary file + * that shadows the dir. + * widget/widgettest.ww package widget_test, import gadget — the + * co-located test ENTRY; + * srcd = .../widget. + * gadget/gadget.ww package gadget, import widget — the NON-root + * importer compiled with -I, + * whose self-named `import + * widget` triggers the fold. + * `-I test/wcc/data/colo98` puts widget's parent on the fallback path, + * mirroring how `lib` holds the real `lib//` dirs. + * + * Asserts (both driver stages, COLD per-stage WW_PKGCACHE): + * 1. RUN-EXIT 0 — the @test resolves, builds, links, runs. Pre-fix the + * non-root importer's fold makes w6c reject → non-zero; this is the + * non-vacuity teeth (verified: revert the two-pass walker → reddens + * with `package widget does not match import path gadget`). + * 2. REGISTERED — widget.unit.ww + widget.wwi appear in .sepwork: the + * import resolved to the DIR-package, not the sibling file. Pre-fix + * widget is never registered (folds into the importer), so both are + * absent — the "right module tag" signal. + * 3. cs==ww (rule 10) — per-package .s/.wwi byte-identical across the + * cstage `ww` and wwstage `ww_ww` sep-drivers, proving the `locatein` + * port is symmetric with `locate_import_in`. + * + * Light wwstage-driver test (CLAUDE.md rule 14): every intermediate is + * `-o`-redirected to /tmp, so it is phase-1 parallel-safe. Models + * 989_septest_run conventions; 989 prefix per the sep-gate precedent. + */ +#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 const char * +absbin(void) +{ + const char *b = getenv("BIN"); + if (!b) b = "out/bin"; + if (b[0] == '/') return b; + static char buf[2048]; + char cwd[1024]; + if (getcwd(cwd, sizeof cwd) == NULL) return NULL; + snprintf(buf, sizeof buf, "%s/%s", cwd, b); + return buf; +} + +static int +slurp(const char *path, char **outbuf, size_t *outlen) +{ + FILE *f = fopen(path, "rb"); + if (!f) return -1; + fseek(f, 0, SEEK_END); + long n = ftell(f); + fseek(f, 0, SEEK_SET); + if (n < 0) { fclose(f); return -1; } + char *b = malloc((size_t)n + 1); + if (!b) { fclose(f); return -1; } + if (fread(b, 1, (size_t)n, f) != (size_t)n) { free(b); fclose(f); return -1; } + b[n] = '\0'; + fclose(f); + *outbuf = b; + *outlen = (size_t)n; + return 0; +} + +static int +files_eq(const char *a, const char *b) +{ + char *ba = NULL, *bb = NULL; + size_t na = 0, nb = 0; + if (slurp(a, &ba, &na) < 0 || slurp(b, &bb, &nb) < 0) { + free(ba); free(bb); + return -1; + } + int eq = (na == nb && memcmp(ba, bb, na) == 0); + free(ba); free(bb); + return eq ? 0 : 1; +} + +/* cs==ww over a sep build's per-package output: every .s/.wwi the cstage + * driver produced in `csdir` must be byte-identical to the wwstage + * driver's same-named file in `wwdir`. Returns the count of mismatches. */ +static int +cmp_sepwork(const char *csdir, const char *wwdir, const char *label) +{ + DIR *d = opendir(csdir); + if (!d) { + fprintf(stderr, "coloimport FAIL: %s — no cs sepwork %s\n", label, csdir); + return 1; + } + int bad = 0, seen = 0; + struct dirent *ent; + while ((ent = readdir(d)) != NULL) { + const char *nm = ent->d_name; + size_t nl = strlen(nm); + int is_s = (nl > 2 && strcmp(nm + nl - 2, ".s") == 0); + int is_wwi = (nl > 4 && strcmp(nm + nl - 4, ".wwi") == 0); + if (!is_s && !is_wwi) continue; + seen++; + char a[2048], b[2048]; + snprintf(a, sizeof a, "%s/%s", csdir, nm); + snprintf(b, sizeof b, "%s/%s", wwdir, nm); + if (files_eq(a, b) != 0) { + fprintf(stderr, "coloimport FAIL: %s — cs!=ww for %s (rule 10)\n", + label, nm); + bad++; + } + } + closedir(d); + if (seen == 0) { + fprintf(stderr, "coloimport FAIL: %s — no .s/.wwi in %s\n", label, csdir); + bad++; + } + return bad; +} + +struct tcase { + const char *label; + const char *entry; /* the co-located `_test` entry built under --sep */ + const char *incdir; /* -I dir (parent that holds the dir-package) */ + const char *regpkg; /* the dir-package that must register its own unit */ +}; + +static struct tcase cases[] = { + /* the transitive self-named-import shadow — the exact lib-run shape. */ + { "widget", "test/wcc/data/colo98/widget/widgettest.ww", + "test/wcc/data/colo98", "widget" }, + { NULL, NULL, NULL, NULL }, +}; + +int +main(void) +{ + const char *bin = absbin(); + if (!bin) return 1; + + char td[64], cmd[8192]; + int fail = 0; + snprintf(td, sizeof td, "/tmp/wwcoloimport_%d", getpid()); + snprintf(cmd, sizeof cmd, "rm -rf %s", td); + runwait(cmd); + mkdir(td, 0755); + + for (int i = 0; cases[i].label; i++) { + struct tcase *t = &cases[i]; + + struct { const char *drv, *tag; int rc; } + stg[] = { { "ww", "cs", -1 }, { "ww_ww", "ww", -1 } }; + for (int s = 0; s < 2; s++) { + char prog[1024]; + snprintf(prog, sizeof prog, "%s/%s.%s.bin", td, t->label, stg[s].tag); + /* COLD per-(case,stage) cache: every package compiles fresh so + * the per-pkg .s/.wwi this gate inspects are produced. */ + snprintf(cmd, sizeof cmd, + "WW_PKGCACHE='%s/cache.%s.%s' %s/%s test --sep -I %s -o %s %s " + ">/dev/null 2>&1", + td, t->label, stg[s].tag, bin, stg[s].drv, t->incdir, prog, + t->entry); + stg[s].rc = runwait(cmd); + } + + /* 1. RUN-EXIT 0 on BOTH stages (pre-fix: w6c rejects the importer). */ + if (stg[0].rc != 0 || stg[1].rc != 0) { + fprintf(stderr, "coloimport FAIL: %s exits cs=%d ww=%d (expected 0)\n", + t->label, stg[0].rc, stg[1].rc); + fail++; + continue; + } + + char csdir[1024], wwdir[1024]; + snprintf(csdir, sizeof csdir, "%s/%s.cs.bin.sepwork", td, t->label); + snprintf(wwdir, sizeof wwdir, "%s/%s.ww.bin.sepwork", td, t->label); + + /* 2. REGISTERED: the dir-package owns its unit + interface (pre-fix + * it folds into the importer and these are absent). */ + char u[2048], w[2048]; + snprintf(u, sizeof u, "%s/%s.unit.ww", csdir, t->regpkg); + snprintf(w, sizeof w, "%s/%s.wwi", csdir, t->regpkg); + if (access(u, 0) != 0) { + fprintf(stderr, "coloimport FAIL: %s — %s missing (import folded " + "to sibling file, not the dir-package)\n", t->label, u); + fail++; + } + if (access(w, 0) != 0) { + fprintf(stderr, "coloimport FAIL: %s — %s missing\n", t->label, w); + fail++; + } + + /* 3. cs==ww (rule 10): per-package .s/.wwi byte-identical. */ + fail += cmp_sepwork(csdir, wwdir, t->label); + } + + snprintf(cmd, sizeof cmd, "rm -rf %s", td); + runwait(cmd); + if (fail) { + fprintf(stderr, "coloimport: %d check(s) failed\n", fail); + return 1; + } + printf("coloimport: co-located `_test` entry resolves `import ` to the " + "dir-package (not the sibling file) under `ww test --sep` on both driver " + "stages + cs==ww per-pkg .s/.wwi\n"); + return 0; +} diff --git a/test/wcc/data/colo98/gadget/gadget.ww b/test/wcc/data/colo98/gadget/gadget.ww new file mode 100644 index 00000000..8f4d0f53 --- /dev/null +++ b/test/wcc/data/colo98/gadget/gadget.ww @@ -0,0 +1,7 @@ +package gadget; + +import widget; + +export fn frob(x: i32) i32 = { + return widget.bump(x) + 1; +}; diff --git a/test/wcc/data/colo98/widget/widget.ww b/test/wcc/data/colo98/widget/widget.ww new file mode 100644 index 00000000..bc7201ab --- /dev/null +++ b/test/wcc/data/colo98/widget/widget.ww @@ -0,0 +1,5 @@ +package widget; + +export fn bump(x: i32) i32 = { + return x + 1; +}; diff --git a/test/wcc/data/colo98/widget/widgettest.ww b/test/wcc/data/colo98/widget/widgettest.ww new file mode 100644 index 00000000..a21678c2 --- /dev/null +++ b/test/wcc/data/colo98/widget/widgettest.ww @@ -0,0 +1,7 @@ +package widget_test; + +import gadget; + +@test fn t_frob() void = { + if (gadget.frob(40) != 42) { abort(); }; +};