/* * Explicit bootstrap gate: the C-built `ww` and the ww-built `ww_ww` * orchestrate the same w6c/w6a/w6l, so the resulting binaries must be * byte-identical for every program in the corpus. The corpus includes * a multi-import build (selfhost/cmd/wwdump/main.ww) exercising `use` * resolution, the -I search path, and the libwwrt.a link. Each build * runs from a per-driver scratch directory so the intermediate .s/.o * files don't collide. */ #include #include #include #include #include #include #include #include "wwtestpkg.h" 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_eq(const char *a, const char *b) { FILE *fa = fopen(a, "rb"); FILE *fb = fopen(b, "rb"); if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } int rc = 0; int any = 0; for (;;) { int ca = fgetc(fa); int cb = fgetc(fb); if (ca != cb) { rc = -1; break; } /* a produced executable can never be legitimately empty. */ if (ca == EOF) { if (!any) rc = -1; break; } any = 1; } fclose(fa); fclose(fb); return rc; } /* `incs` may be a colon-separated list of include dirs (the ww driver * accepts -I path1:path2:path3). */ static int build_via(const char *bin, const char *driver, const char *src, const char *workdir, const char *incs, const char *out_basename) { char cmd[4096]; /* -o / keeps the binary where the diff expects it AND * routes the .s/.o intermediates into the workdir (T3) — no longer * next to the source, so concurrent driver builds never collide. */ if (incs && incs[0]) { snprintf(cmd, sizeof cmd, "cd %s && timeout 180 %s/%s build -o %s/%s -I %s %s 2>/dev/null", workdir, bin, driver, workdir, out_basename, incs, src); } else { snprintf(cmd, sizeof cmd, "cd %s && timeout 180 %s/%s build -o %s/%s %s 2>/dev/null", workdir, bin, driver, workdir, out_basename, src); } return runwait(cmd); } static int diff_one(const char *bin, const char *cwd, const char *label, const char *src, const char *out_basename, const char *incs) { char dc[64], dw[64]; snprintf(dc, sizeof dc, "/tmp/ww_d_%d_c", getpid()); snprintf(dw, sizeof dw, "/tmp/ww_d_%d_w", getpid()); char cmd[256]; int rc = -1; snprintf(cmd, sizeof cmd, "rm -rf %s %s && mkdir -p %s %s", dc, dw, dc, dw); if (runwait(cmd) != 0) goto cleanup; if (build_via(bin, "ww", src, dc, incs, out_basename) != 0) { fprintf(stderr, "ww_ww FAIL: C ww errored on %s\n", label); goto cleanup; } if (build_via(bin, "ww_ww", src, dw, incs, out_basename) != 0) { fprintf(stderr, "ww_ww FAIL: ww ww errored on %s\n", label); goto cleanup; } char co[1024], wo[1024]; snprintf(co, sizeof co, "%s/%s", dc, out_basename); snprintf(wo, sizeof wo, "%s/%s", dw, out_basename); if (slurp_eq(co, wo) != 0) fprintf(stderr, "ww_ww FAIL: %s — driver outputs differ\n", label); else rc = 0; cleanup: /* `ww build` retains the caller-owned .sepwork tree inside the * workdir; rm -rf of the exact pid-keyed pair covers it, and is * safe even after a partial mkdir. */ snprintf(cmd, sizeof cmd, "rm -rf %s %s", dc, dw); if (runwait(cmd) != 0 && rc == 0) rc = -1; (void)cwd; return rc; } /* #16: `run` must report the child's real exit code, not collapse * every non-zero exit to 1. Returns -1 on staging/spawn failure. */ static int run_exit_code(const char *bin, const char *driver, int code) { char src[64]; snprintf(src, sizeof src, "/tmp/ww_rc_%d_%d.ww", getpid(), code); FILE *f = fopen(src, "w"); if (!f) return -1; fprintf(f, "package main;\nexport fn main() i32 = {\n\treturn %d;\n};\n", code); if (fclose(f) != 0) { perror(src); if (unlink(src) != 0 && errno != ENOENT) perror(src); return -1; } char cmd[256]; snprintf(cmd, sizeof cmd, "%s/%s run %s 2>/dev/null", bin, driver, src); int rc = runwait(cmd); if (unlink(src) != 0 && errno != ENOENT) { perror(src); if (rc == code) rc = -1; } return rc; } /* Pins the build-step caller contract: a failing build must still * report failure (non-zero). The process mechanism's exact exit-code * return must not regress this, since the w6c/w6a/w6l callers only * test `!= 0`. */ static int run_build_fail(const char *bin, const char *driver) { char src[64]; snprintf(src, sizeof src, "/tmp/ww_badbuild_%d.ww", getpid()); FILE *f = fopen(src, "w"); if (!f) { perror(src); return -1; } wwtest_fputs("export fn main() i32 = {\n\treturn 1\n", f); /* no ; no } */ if (fclose(f) != 0) { perror(src); if (unlink(src) != 0 && errno != ENOENT) perror(src); return -1; } char cmd[256]; snprintf(cmd, sizeof cmd, "%s/%s run %s 2>/dev/null", bin, driver, src); int rc = runwait(cmd); if (unlink(src) != 0 && errno != ENOENT) { perror(src); /* cleanup failure maps onto the -1 sentinel the caller rejects; * a real rc==0 (driver failed to fail) keeps its diagnostic. */ if (rc != 0) rc = -1; } return rc; } /* #16 ENFORCE-driver: an unresolvable import is a hard error, not a silent * skip — the driver must exit nonzero AND print "ww: cannot find package * ". Both drivers must agree (rule-10 wording). Returns 0 if the * driver loud-fails as expected, 1 otherwise. (The ww_ww half is why this * lives in 993, the rule-14-safe wwstage-driver range.) */ static int run_missing_pkg(const char *bin, const char *driver) { char tmpdir[80], src[160], errf[160], outbin[160], rmcmd[200]; snprintf(tmpdir, sizeof tmpdir, "/tmp/ww_mp_%s_%d", driver, getpid()); if (mkdir(tmpdir, 0755) != 0) { perror(tmpdir); return 1; } snprintf(src, sizeof src, "%s/mp.ww", tmpdir); snprintf(errf, sizeof errf, "%s/mp.err", tmpdir); snprintf(outbin, sizeof outbin, "%s/mp", tmpdir); snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); FILE *f = fopen(src, "w"); if (!f) { perror(src); if (runwait(rmcmd) != 0) fprintf(stderr, "ww_ww: cleanup of %s failed\n", tmpdir); return 1; } wwtest_fputs("import nosuchpkg;\nexport fn main() i32 = { return 0; };\n", f); fclose(f); /* #8: the sep scratch is mkdir'd before the import-resolution failure, * so a no-o build leaks /tmp/ww_mp_*.sepwork. -o routes it under tmpdir; * rm -rf clears it. The missing-pkg error wording is -o-independent. */ char cmd[512]; snprintf(cmd, sizeof cmd, "%s/%s build -o %s %s 2>%s", bin, driver, outbin, src, errf); int rc = runwait(cmd); int found = 0; FILE *e = fopen(errf, "rb"); if (e) { char buf[4096]; size_t n = fread(buf, 1, sizeof buf - 1, e); fclose(e); buf[n] = '\0'; found = strstr(buf, "cannot find package nosuchpkg") != NULL; } int cleanfail = runwait(rmcmd) != 0; if (rc == 0 || !found) { fprintf(stderr, "ww_ww FAIL: %s missing-pkg rc=%d found=%d " "(want nonzero + 'cannot find package')\n", driver, rc, found); return 1; } if (cleanfail) { fprintf(stderr, "ww_ww: cleanup of %s failed\n", tmpdir); return 1; } return 0; } int main(void) { const char *bin = absbin(); if (!bin) return 1; char cwd[1024]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; /* Stage a tiny program in a place both drivers can reach. * pid-keyed so concurrent runs of this test (or shards of it) don't * race on the staging file. */ char hello_src[64], hello_out[64]; snprintf(hello_src, sizeof hello_src, "/tmp/ww_d_hello_%d.ww", getpid()); snprintf(hello_out, sizeof hello_out, "ww_d_hello_%d", getpid()); { FILE *f = fopen(hello_src, "w"); if (!f) return 1; wwtest_fputs("import os;\n\n" "export fn main() i32 = {\n" "\tos.write(1, \"hi\\n\".ptr, 3u64);\n" "\treturn 0;\n" "};\n", f); if (fclose(f) != 0) { perror(hello_src); if (unlink(hello_src) != 0 && errno != ENOENT) perror(hello_src); return 1; } } struct { const char *label; const char *src; /* may be relative to cwd */ const char *out; /* basename of expected output */ const char *incs; /* colon-separated -I list, may be NULL */ } cases[] = { { "hello", NULL, NULL, "" }, /* filled in below */ { "wwdump", NULL, "main", NULL }, /* filled in below */ { NULL, NULL, NULL, NULL }, }; cases[0].src = hello_src; cases[0].out = hello_out; /* wwdump case: absolute paths so the driver finds the imports * regardless of the per-driver workdir. */ static char wwdump_src[2048], wwdump_incs[4096]; snprintf(wwdump_src, sizeof wwdump_src, "%s/selfhost/cmd/wwdump/main.ww", cwd); snprintf(wwdump_incs, sizeof wwdump_incs, "%s/lib/ww:%s/lib/encoding/utf8:%s/selfhost/cmd/wcc", cwd, cwd, cwd); cases[1].src = wwdump_src; cases[1].incs = wwdump_incs; int fail = 0; int n = 0; int cleanfail = 0; for (int i = 0; cases[i].label; i++) { if (diff_one(bin, cwd, cases[i].label, cases[i].src, cases[i].out, cases[i].incs) != 0) fail++; n++; } if (unlink(hello_src) != 0 && errno != ENOENT) { perror(hello_src); cleanfail = 1; } /* #16: `ww_ww run` must propagate the child program's real exit code. * Table-driven; the non-zero rows fail under the old collapse-to-1. * 255 is the single-byte boundary (WEXITSTATUS max). The C ww driver * is the reference (do_run -> WEXITSTATUS); assert ww_ww matches it * row-for-row. */ static const int exit_codes[] = { 0, 7, 42, 255 }; int nrc = (int)(sizeof exit_codes / sizeof exit_codes[0]); int rcfail = 0; for (int i = 0; i < nrc; i++) { int want = exit_codes[i]; int cww = run_exit_code(bin, "ww", want); int wwww = run_exit_code(bin, "ww_ww", want); if (cww != want) { fprintf(stderr, "ww_ww FAIL: C ww run returned %d, " "want %d\n", cww, want); rcfail++; } if (wwww != want) { fprintf(stderr, "ww_ww FAIL: ww_ww run returned %d, " "want %d (exit-code propagation, #16)\n", wwww, want); rcfail++; } } /* Build-step caller contract: a failing build must still report * failure (non-zero), and both drivers must agree. */ int cbad = run_build_fail(bin, "ww"); int wbad = run_build_fail(bin, "ww_ww"); /* <= 0 rejects the -1 staging/cleanup sentinel: equal sentinels must * not pass as build-fail parity when neither driver ever ran. */ if (cbad <= 0 || wbad <= 0 || cbad != wbad) { fprintf(stderr, "ww_ww FAIL: build-fail exit C ww=%d ww_ww=%d " "(want equal and non-zero)\n", cbad, wbad); rcfail++; } /* #16 ENFORCE-driver: a missing import package loud-fails (nonzero + * "cannot find package") on BOTH drivers — identical wording. */ rcfail += run_missing_pkg(bin, "ww"); rcfail += run_missing_pkg(bin, "ww_ww"); if (fail || rcfail || cleanfail) { fprintf(stderr, "ww_ww: %d/%d diff(s) failed, %d exit-code " "row(s) failed\n", fail, n, rcfail); return 1; } printf("ww_ww: byte-identical to C ww on %d corpus builds " "(single-file + selfhost/wwdump multi-import); " "run exit-code propagated on %d rows + build-fail parity (#16)\n", n, nrc); return 0; }