test: retire owned observers 989_sepdotpath + 989_syntaxexport
This commit is contained in:
@@ -1,339 +0,0 @@
|
||||
/*
|
||||
* 989_sepdotpath_run — sep-build of a DOTTED-path (multi-component) package
|
||||
* (#57). Commit-6 broad-soak prereq: the real toolchain sep-builds packages
|
||||
* like `encoding.utf8`, whose body must mangle on the full dotted import path
|
||||
* (`encoding.utf8.X`), not the leaf `package` clause (`utf8.X`).
|
||||
*
|
||||
* Root cause it guards: the sep producer splices a package's own body under
|
||||
* `//ww:module-reset` (so `-c` keeps imported==0). Pre-#57 that reset carried
|
||||
* NO path, so the body's `package <leaf>;` clause set curmod to the LEAF —
|
||||
* the DEFINER mangled `b.val` while every importer (spliced under
|
||||
* `//ww:module a.b`) referenced `a.b.val` → unresolved at link. #57 threads
|
||||
* the dotted path through the reset (`//ww:module-reset a.b`), demoting the
|
||||
* leaf clause to an assertion. Single-COMPONENT packages were always clean
|
||||
* (leaf == dotted path) — that invariant is the regression guard below.
|
||||
*
|
||||
* Graph (smallest that mixes both kinds): root -> { a.b (dotted), c (single) }.
|
||||
*
|
||||
* Asserts (fresh output stems under an invocation-owned root; retained
|
||||
* `<stem>.sepwork` is inspected, then removed by final carrier cleanup):
|
||||
* 1. Build + run, BOTH stages → exit EXPECT_EXIT (the dotted-package symbol
|
||||
* resolves + links; the program runs). Pre-#57 the link failed.
|
||||
* 2. cs==ww (rule 10): per-package `.s`/`.wwi`/`.unit.ww` AND the final
|
||||
* binary are byte-identical between `ww` and `ww_ww`.
|
||||
* 3. NON-VACUITY (a) — definer==importer on the DOTTED form: the producer's
|
||||
* `a.b.s` DEFINES `a.b.val` and the root's `__root.s` REFERENCES
|
||||
* `a.b.val`. Pre-#57 the definer emitted the leaf `b.val` → the strings
|
||||
* mismatched; this row would fail (and assert 1's link would fail).
|
||||
* The reset directive in `a.b.unit.ww` carries the dotted path.
|
||||
* 4. NON-VACUITY (b) — single-component regression guard: package `c`
|
||||
* sep-builds in the SAME graph, defines `c.cval` (leaf == dotted), and
|
||||
* cs==ww (covered by assert 2). Cross-commit byte-id of single-component
|
||||
* output is additionally pinned by 989_sepbuild_run + the 990-997 gates.
|
||||
*
|
||||
* All intermediates are `-o`-redirected to /tmp for isolation. Models
|
||||
* 989_sepbuild_run.c conventions; 989 prefix per the sep-gate precedent.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define EXPECT_EXIT 37
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 0 if `needle` occurs in the file at `path`, 1 if absent, -1 on read err. */
|
||||
static int
|
||||
file_contains(const char *path, const char *needle)
|
||||
{
|
||||
char *b = NULL;
|
||||
size_t n = 0;
|
||||
if (slurp(path, &b, &n) < 0) return -1;
|
||||
int found = (strstr(b, needle) != NULL);
|
||||
free(b);
|
||||
return found ? 0 : 1;
|
||||
}
|
||||
|
||||
static int
|
||||
write_file(const char *path, const char *body)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(body, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = absbin();
|
||||
if (!bin) return 1;
|
||||
char td[64], cmd[8192], p[1024];
|
||||
int fail = 0, have_lib = 0, have_a = 0, have_ab = 0, have_c = 0;
|
||||
|
||||
snprintf(td, sizeof td, "/tmp/wwsepdot_%d", getpid());
|
||||
if (mkdir(td, 0755) != 0) {
|
||||
fprintf(stderr, "sepdotpath FAIL: cannot acquire %s\n", td);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* lib/a/b — a 2-level dotted package (clause `package b;`, dir a/b). */
|
||||
snprintf(p, sizeof p, "%s/lib", td);
|
||||
if (mkdir(p, 0755) != 0) {
|
||||
fprintf(stderr, "sepdotpath FAIL: cannot create %s\n", p);
|
||||
fail++; goto out;
|
||||
}
|
||||
have_lib = 1;
|
||||
snprintf(p, sizeof p, "%s/lib/a", td);
|
||||
if (mkdir(p, 0755) != 0) {
|
||||
fprintf(stderr, "sepdotpath FAIL: cannot create %s\n", p);
|
||||
fail++; goto out;
|
||||
}
|
||||
have_a = 1;
|
||||
snprintf(p, sizeof p, "%s/lib/a/b", td);
|
||||
if (mkdir(p, 0755) != 0) {
|
||||
fprintf(stderr, "sepdotpath FAIL: cannot create %s\n", p);
|
||||
fail++; goto out;
|
||||
}
|
||||
have_ab = 1;
|
||||
snprintf(p, sizeof p, "%s/lib/c", td);
|
||||
if (mkdir(p, 0755) != 0) {
|
||||
fprintf(stderr, "sepdotpath FAIL: cannot create %s\n", p);
|
||||
fail++; goto out;
|
||||
}
|
||||
have_c = 1;
|
||||
|
||||
snprintf(p, sizeof p, "%s/lib/a/b/mod.ww", td);
|
||||
if (write_file(p, "package b;\nexport fn val() i32 = { return 42; };\n"))
|
||||
{ fail++; goto out; }
|
||||
snprintf(p, sizeof p, "%s/lib/c/mod.ww", td);
|
||||
if (write_file(p, "package c;\nexport fn cval() i32 = { return 5; };\n"))
|
||||
{ fail++; goto out; }
|
||||
|
||||
char rootww[1024];
|
||||
snprintf(rootww, sizeof rootww, "%s/root.ww", td);
|
||||
if (write_file(rootww,
|
||||
"package main;\n"
|
||||
"import a.b;\n"
|
||||
"import c;\n"
|
||||
"fn main() i32 = { return b.val() - c.cval(); };\n"))
|
||||
{ fail++; goto out; }
|
||||
|
||||
/* Two driver stages and their scratch dirs (paths rebuilt from the
|
||||
* small fixed `td` + stage tag → provably non-truncating snprintfs). */
|
||||
struct { const char *drv, *tag; char prog[1024]; }
|
||||
stg[] = { { "ww", "cs", {0} }, { "ww_ww", "ww", {0} } };
|
||||
|
||||
for (int s = 0; s < 2; s++) {
|
||||
snprintf(stg[s].prog, sizeof stg[s].prog, "%s/prog.%s", td, stg[s].tag);
|
||||
/* Each direct build produces the `.s`/`.unit.ww` artifacts this
|
||||
* gate inspects. */
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 240 %s/%s build "
|
||||
"-I %s/lib -o %s %s >/dev/null 2>&1",
|
||||
bin, stg[s].drv, td, stg[s].prog, rootww);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "sepdotpath FAIL: %s build\n", stg[s].drv);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
int rc = runwait(stg[s].prog);
|
||||
if (rc != EXPECT_EXIT) {
|
||||
fprintf(stderr, "sepdotpath FAIL: %s prog exit=%d expected %d\n",
|
||||
stg[s].drv, rc, EXPECT_EXIT);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* The discovered package set materialized (dotted + single + root). */
|
||||
const char *pkgs[] = { "a.b", "c", "__root" };
|
||||
for (int i = 0; i < 3; i++) {
|
||||
/* #69: the root is compiled WITHOUT `-I`, so it produces no `.wwi`;
|
||||
* assert its `.s` materialized instead. Deps still emit a `.wwi`. */
|
||||
int is_root = (strcmp(pkgs[i], "__root") == 0);
|
||||
snprintf(p, sizeof p, "%s/prog.cs.sepwork/%s%s", td, pkgs[i],
|
||||
is_root ? ".s" : ".wwi");
|
||||
if (access(p, 0) != 0) {
|
||||
fprintf(stderr, "sepdotpath FAIL: missing %s%s (discovery)\n",
|
||||
pkgs[i], is_root ? ".s" : ".wwi");
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* cs==ww (rule 10): per-package .s/.wwi/.unit.ww + final binary. The
|
||||
* root has no `.wwi` post-#69, so it is excluded from the .wwi compare. */
|
||||
for (int i = 0; i < 3; i++) {
|
||||
const char *suf[] = { ".s", ".wwi", ".unit.ww" };
|
||||
for (int k = 0; k < 3; k++) {
|
||||
if (strcmp(pkgs[i], "__root") == 0 && strcmp(suf[k], ".wwi") == 0)
|
||||
continue;
|
||||
char a[1024], b[1024];
|
||||
snprintf(a, sizeof a, "%s/prog.%s.sepwork/%s%s",
|
||||
td, stg[0].tag, pkgs[i], suf[k]);
|
||||
snprintf(b, sizeof b, "%s/prog.%s.sepwork/%s%s",
|
||||
td, stg[1].tag, pkgs[i], suf[k]);
|
||||
if (files_eq(a, b) != 0) {
|
||||
fprintf(stderr, "sepdotpath FAIL: cs!=ww for %s%s (rule 10)\n",
|
||||
pkgs[i], suf[k]);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (files_eq(stg[0].prog, stg[1].prog) != 0) {
|
||||
fprintf(stderr, "sepdotpath FAIL: cs exe != ww exe (rule 10)\n");
|
||||
fail++;
|
||||
}
|
||||
|
||||
/* NON-VACUITY (a): definer == importer on the DOTTED qualification.
|
||||
* Pre-#57 the definer emitted the leaf `b.val` while the importer
|
||||
* referenced `a.b.val` → mismatch + unresolved link. */
|
||||
{
|
||||
char def_s[1024], ref_s[1024], unit[1024];
|
||||
snprintf(def_s, sizeof def_s, "%s/prog.cs.sepwork/a.b.s", td);
|
||||
snprintf(ref_s, sizeof ref_s, "%s/prog.cs.sepwork/__root.s", td);
|
||||
snprintf(unit, sizeof unit, "%s/prog.cs.sepwork/a.b.unit.ww", td);
|
||||
if (file_contains(def_s, "TEXT a.b.val") != 0) {
|
||||
fprintf(stderr, "sepdotpath FAIL: definer a.b.s lacks dotted "
|
||||
"`TEXT a.b.val` (leaf-clause regression)\n");
|
||||
fail++;
|
||||
}
|
||||
if (file_contains(ref_s, "a.b.val") != 0) {
|
||||
fprintf(stderr, "sepdotpath FAIL: importer __root.s lacks "
|
||||
"reference to a.b.val\n");
|
||||
fail++;
|
||||
}
|
||||
if (file_contains(unit, "//ww:module-reset a.b") != 0) {
|
||||
fprintf(stderr, "sepdotpath FAIL: a.b.unit.ww reset directive "
|
||||
"does not carry the dotted path\n");
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* NON-VACUITY (b): single-component `c` sep-builds in the SAME graph and
|
||||
* defines its leaf==dotted symbol `c.cval` (curmod resolves to the same
|
||||
* string with or without #57 → byte-id preserved). cs==ww already
|
||||
* asserted above; cross-commit byte-id pinned by 989_sepbuild + 990-997. */
|
||||
{
|
||||
char c_s[1024];
|
||||
snprintf(c_s, sizeof c_s, "%s/prog.cs.sepwork/c.s", td);
|
||||
if (file_contains(c_s, "TEXT c.cval") != 0) {
|
||||
fprintf(stderr, "sepdotpath FAIL: single-component c.s lacks "
|
||||
"`TEXT c.cval` (regression)\n");
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"rm -rf -- '%s/prog.cs.sepwork' '%s/prog.ww.sepwork'", td, td);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "sepdotpath FAIL: cannot clean .sepwork trees\n");
|
||||
fail++;
|
||||
}
|
||||
{
|
||||
char path[1200];
|
||||
snprintf(path, sizeof path, "%s/root.ww", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepdotpath FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/prog.cs", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepdotpath FAIL: cannot remove %s\n", path); fail++; }
|
||||
snprintf(path, sizeof path, "%s/prog.ww", td);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepdotpath FAIL: cannot remove %s\n", path); fail++; }
|
||||
}
|
||||
if (have_ab) {
|
||||
snprintf(p, sizeof p, "%s/lib/a/b", td);
|
||||
{
|
||||
char path[1200];
|
||||
snprintf(path, sizeof path, "%s/mod.ww", p);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepdotpath FAIL: cannot remove %s\n", path); fail++; }
|
||||
}
|
||||
if (rmdir(p) != 0 && errno != ENOENT) { fprintf(stderr, "sepdotpath FAIL: cannot remove %s\n", p); fail++; }
|
||||
}
|
||||
if (have_a) {
|
||||
snprintf(p, sizeof p, "%s/lib/a", td);
|
||||
if (rmdir(p) != 0 && errno != ENOENT) { fprintf(stderr, "sepdotpath FAIL: cannot remove %s\n", p); fail++; }
|
||||
}
|
||||
if (have_c) {
|
||||
snprintf(p, sizeof p, "%s/lib/c", td);
|
||||
{
|
||||
char path[1200];
|
||||
snprintf(path, sizeof path, "%s/mod.ww", p);
|
||||
if (unlink(path) != 0 && errno != ENOENT) { fprintf(stderr, "sepdotpath FAIL: cannot remove %s\n", path); fail++; }
|
||||
}
|
||||
if (rmdir(p) != 0 && errno != ENOENT) { fprintf(stderr, "sepdotpath FAIL: cannot remove %s\n", p); fail++; }
|
||||
}
|
||||
if (have_lib) {
|
||||
snprintf(p, sizeof p, "%s/lib", td);
|
||||
if (rmdir(p) != 0 && errno != ENOENT) { fprintf(stderr, "sepdotpath FAIL: cannot remove %s\n", p); fail++; }
|
||||
}
|
||||
if (rmdir(td) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "sepdotpath FAIL: cannot remove %s\n", td);
|
||||
fail++;
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr, "sepdotpath: %d check(s) failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("sepdotpath: dotted `a.b` + single `c` via build_one_sep — "
|
||||
"build+run (exit %d) + cs==ww per-pkg .s/.wwi/.unit + final binary "
|
||||
"+ definer==importer on a.b.val + single-component c.cval intact\n",
|
||||
EXPECT_EXIT);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,306 +0,0 @@
|
||||
/*
|
||||
* 989_syntaxexport_run — BUG-A residual at the surviving syntax->wcc
|
||||
* package boundary (#72), post the #74 frontend consolidation.
|
||||
*
|
||||
* Root cause it guards: `lib/ww/syntax/` exports the fns wcc calls, and
|
||||
* those fns' signatures name frontend types (tok, tinfo, tctx, ...). When
|
||||
* the separate-compilation producer emits syntax's `.wwi`,
|
||||
* check_exported_type recurses every exported decl — and, for an exported
|
||||
* struct, its FIELDS too (the
|
||||
* harec STORAGE_STRUCT precedent, wwi.c wwi_check_type N_TSTRUCT). Any
|
||||
* referenced type that is itself unexported aborts the producer with
|
||||
* "exported declaration references unexported type". Pre-fix, syntax's
|
||||
* surface left 16 such types unexported, so syntax's `.wwi` never produced
|
||||
* -> every wcc sep-build was blocked. Fix: `export` those 16 type decls
|
||||
* (10 fn-signature-direct + 6 surfaced by the struct-field recursion:
|
||||
* tykind/tfield/tparam/ttupleelem via tinfo, tctx via typesinit, and
|
||||
* tinfocacheent via tctx). Exporting a TYPE emits no code -> the bug is
|
||||
* byte-id-neutral; this gate proves the PRODUCER residual is closed.
|
||||
*
|
||||
* The consumer here uses QUALIFIED refs (`syntax.tokname`, `syntax.tkind`)
|
||||
* to isolate the producer fix from the separate unqualified-ref question
|
||||
* (#75). It happens to link + run end-to-end, which also shows the
|
||||
* qualified-ref path resolves across the sep boundary.
|
||||
*
|
||||
* Asserts (direct builds compile every package):
|
||||
* 1. Build + run, BOTH stages -> exit EXPECT_EXIT (`syntax.tokname(
|
||||
* TK_INT)` == "int", len 3). Pre-fix the syntax producer exited 1, so
|
||||
* the build never produced a binary.
|
||||
* 2. syntax's `.wwi` is produced and carries the 16 `export type` decls.
|
||||
* 3. cs==ww (rule 10): syntax's `.wwi`/`.s`/`.unit.ww` AND the final
|
||||
* binary are byte-identical between `ww` and `ww_ww`.
|
||||
* 4. NON-VACUITY (the bug, isolated, BOTH compilers): replay the producer
|
||||
* `w6c -c -I` on the produced `syntax.unit.ww` -> exits 0 (exports
|
||||
* present). On a copy with `export ` stripped from the type decls ->
|
||||
* MUST exit non-zero (the export-check fires). Proven for w6c + w6c_ww.
|
||||
*
|
||||
* All intermediates are `-o`-redirected to /tmp; it only reads
|
||||
* the in-tree `lib/ww/syntax`. Models 989_seproot_export_run.c.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define EXPECT_EXIT 3
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 0 if `needle` occurs in the file at `path`, 1 if absent, -1 on read err. */
|
||||
static int
|
||||
file_contains(const char *path, const char *needle)
|
||||
{
|
||||
char *b = NULL;
|
||||
size_t n = 0;
|
||||
if (slurp(path, &b, &n) < 0) return -1;
|
||||
int found = (strstr(b, needle) != NULL);
|
||||
free(b);
|
||||
return found ? 0 : 1;
|
||||
}
|
||||
|
||||
static int
|
||||
write_file(const char *path, const char *body)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
if (fputs(body, f) == EOF) { fclose(f); return -1; }
|
||||
return fclose(f);
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = absbin();
|
||||
if (!bin) return 1;
|
||||
char root[1024];
|
||||
if (getcwd(root, sizeof root) == NULL) return 1;
|
||||
|
||||
char td[] = "/tmp/wwsynexp_XXXXXX";
|
||||
char cmd[8192];
|
||||
int fail = 0, cleanup_fail = 0;
|
||||
|
||||
if (mkdtemp(td) == NULL) return 1;
|
||||
|
||||
/* Root: QUALIFIED refs into the real syntax package — `syntax.tokname`
|
||||
* over `syntax.tkind`. The producer must serialize syntax's `.wwi`
|
||||
* (exported fns over frontend types) WITHOUT the unexported-type abort. */
|
||||
char rootww[1024];
|
||||
snprintf(rootww, sizeof rootww, "%s/root.ww", td);
|
||||
if (write_file(rootww,
|
||||
"package main;\n"
|
||||
"import syntax;\n"
|
||||
"fn main() i32 = {\n"
|
||||
"\tlet s: str = syntax.tokname(syntax.tkind.TK_INT);\n"
|
||||
"\treturn s.len: i32;\n"
|
||||
"};\n"))
|
||||
{ fail++; goto out; }
|
||||
|
||||
struct { const char *drv, *tag; char prog[1024]; }
|
||||
stg[] = { { "ww", "cs", {0} }, { "ww_ww", "ww", {0} } };
|
||||
|
||||
for (int s = 0; s < 2; s++) {
|
||||
snprintf(stg[s].prog, sizeof stg[s].prog, "%s/prog.%s", td, stg[s].tag);
|
||||
/* Direct builds produce the `.wwi`/`.s`/`.unit.ww` artifacts this
|
||||
* gate inspects. */
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"timeout 240 %s/%s build "
|
||||
"-I %s/lib/ww -o %s %s >%s/build.%s.log 2>&1",
|
||||
bin, stg[s].drv, root, stg[s].prog, rootww,
|
||||
td, stg[s].tag);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "syntaxexport FAIL: %s build (pre-fix: syntax "
|
||||
"producer rejects export-fn-over-unexported-type)\n", stg[s].drv);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
/* the producer abort must not appear in the build log. */
|
||||
char log[1024], wwi[1024];
|
||||
snprintf(log, sizeof log, "%s/build.%s.log", td, stg[s].tag);
|
||||
if (file_contains(log, "unexported type") == 0) {
|
||||
fprintf(stderr, "syntaxexport FAIL: %s build log shows "
|
||||
"'unexported type' (producer residual not closed)\n", stg[s].drv);
|
||||
fail++;
|
||||
}
|
||||
snprintf(wwi, sizeof wwi, "%s/prog.%s.sepwork/syntax.wwi", td, stg[s].tag);
|
||||
if (access(wwi, 0) != 0) {
|
||||
fprintf(stderr, "syntaxexport FAIL: %s produced no syntax.wwi\n",
|
||||
stg[s].drv);
|
||||
fail++;
|
||||
}
|
||||
int rc = runwait(stg[s].prog);
|
||||
if (rc != EXPECT_EXIT) {
|
||||
fprintf(stderr, "syntaxexport FAIL: %s prog exit=%d expected %d\n",
|
||||
stg[s].drv, rc, EXPECT_EXIT);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* syntax's `.wwi` carries the now-exported frontend types — spot-check
|
||||
* a representative set spanning each surfacing path (fn-direct + the
|
||||
* struct-field and tctx-field cascades). */
|
||||
{
|
||||
char wwi[1024];
|
||||
snprintf(wwi, sizeof wwi, "%s/prog.cs.sepwork/syntax.wwi", td);
|
||||
const char *want[] = {
|
||||
"export type tok ", "export type tkind ", "export type node ",
|
||||
"export type nkind ", "export type tinfo ", "export type tfield ",
|
||||
"export type tctx ", "export type tinfocacheent ",
|
||||
};
|
||||
for (int i = 0; i < (int)(sizeof want / sizeof want[0]); i++)
|
||||
if (file_contains(wwi, want[i]) != 0) {
|
||||
fprintf(stderr, "syntaxexport FAIL: syntax.wwi lacks '%s'\n",
|
||||
want[i]);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* cs==ww (rule 10): syntax's per-package artefacts + the final binary. */
|
||||
{
|
||||
const char *suf[] = { ".wwi", ".s", ".unit.ww" };
|
||||
for (int k = 0; k < 3; k++) {
|
||||
char a[1024], b[1024];
|
||||
snprintf(a, sizeof a, "%s/prog.cs.sepwork/syntax%s", td, suf[k]);
|
||||
snprintf(b, sizeof b, "%s/prog.ww.sepwork/syntax%s", td, suf[k]);
|
||||
if (files_eq(a, b) != 0) {
|
||||
fprintf(stderr, "syntaxexport FAIL: cs!=ww for syntax%s "
|
||||
"(rule 10)\n", suf[k]);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (files_eq(stg[0].prog, stg[1].prog) != 0) {
|
||||
fprintf(stderr, "syntaxexport FAIL: cs exe != ww exe (rule 10)\n");
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
/* NON-VACUITY (the bug, isolated): replay the producer on the produced
|
||||
* `syntax.unit.ww`. With the exports present `w6c -c -I` exits 0; with
|
||||
* `export ` stripped from the type decls it MUST exit non-zero (the
|
||||
* export-check fires). Proven for BOTH compilers. */
|
||||
{
|
||||
char unit[1024], ne[1024];
|
||||
snprintf(unit, sizeof unit, "%s/prog.cs.sepwork/syntax.unit.ww", td);
|
||||
snprintf(ne, sizeof ne, "%s/syntax.noexport.ww", td);
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"sed 's/export type /type /g' %s > %s", unit, ne);
|
||||
if (runwait(cmd) != 0) { fail++; goto out; }
|
||||
|
||||
struct { const char *comp; } cc[] = { { "w6c" }, { "w6c_ww" } };
|
||||
for (int j = 0; j < 2; j++) {
|
||||
char wwi[1024], asmf[1024];
|
||||
snprintf(wwi, sizeof wwi, "%s/nv.%s.wwi", td, cc[j].comp);
|
||||
snprintf(asmf, sizeof asmf, "%s/nv.%s.s", td, cc[j].comp);
|
||||
snprintf(cmd, sizeof cmd, "%s/%s -c -I %s -o %s %s >/dev/null 2>&1",
|
||||
bin, cc[j].comp, wwi, asmf, unit);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "syntaxexport FAIL: %s -c -I rejected the "
|
||||
"EXPORTED unit (post-fix producer must succeed)\n",
|
||||
cc[j].comp);
|
||||
fail++;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/%s -c -I %s -o %s %s >/dev/null 2>&1",
|
||||
bin, cc[j].comp, wwi, asmf, ne);
|
||||
if (runwait(cmd) == 0) {
|
||||
fprintf(stderr, "syntaxexport FAIL: %s -c -I accepted the "
|
||||
"UNEXPORTED-type unit (bug not reproduced -> vacuous "
|
||||
"gate)\n", cc[j].comp);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
for (int s = 0; s < 2; s++) {
|
||||
const char *tag = s == 0 ? "cs" : "ww";
|
||||
char path[1200];
|
||||
snprintf(path, sizeof path, "%s/prog.%s.sepwork", td, tag);
|
||||
snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path);
|
||||
if (runwait(cmd) != 0) cleanup_fail = 1;
|
||||
snprintf(path, sizeof path, "%s/prog.%s", td, tag);
|
||||
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
|
||||
snprintf(path, sizeof path, "%s/build.%s.log", td, tag);
|
||||
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
|
||||
}
|
||||
{
|
||||
const char *files[] = {
|
||||
"root.ww", "syntax.noexport.ww",
|
||||
"nv.w6c.wwi", "nv.w6c.s",
|
||||
"nv.w6c_ww.wwi", "nv.w6c_ww.s",
|
||||
};
|
||||
for (size_t i = 0; i < sizeof files / sizeof files[0]; i++) {
|
||||
char path[1200];
|
||||
snprintf(path, sizeof path, "%s/%s", td, files[i]);
|
||||
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
|
||||
}
|
||||
}
|
||||
if (rmdir(td) != 0) cleanup_fail = 1;
|
||||
if (cleanup_fail) {
|
||||
fprintf(stderr, "syntaxexport FAIL: cleanup incomplete under %s\n", td);
|
||||
fail++;
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr, "syntaxexport: %d check(s) failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("syntaxexport: syntax->wcc boundary sep-PRODUCES clean (16 export "
|
||||
"type decls) + qualified-ref consumer build+run (exit %d) + cs==ww "
|
||||
"syntax.wwi/.s/.unit.ww/binary + non-vacuity (export strip rejects, "
|
||||
"both compilers)\n", EXPECT_EXIT);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user