Every surviving carrier now owns its artifacts: checked mkdir/mkdtemp/ fopen acquisition, one all-exit cleanup funnel per carrier, ENOENT- tolerant checked unlinks, exact-path deletion (rm -rf only for an owned pid-keyed dir or a .sepwork beneath one), and cleanup failure fails a passing carrier without overwriting its diagnostic. In the same pass the carriers adapt to the driver contract this branch lands: --sep and WW_PKGCACHE are gone, -S and the /tmp/ww_run_<pid> scratch contract are asserted, and rows whose runtime or reject coverage moved to test/wcc/data fixtures or test/lang @test owners are trimmed to the byte/artifact/diagnostic observations only they can make. Repair and adaptation ride together because most files interleave both in the same hunks; splitting would manufacture intermediate carrier states that never existed and cannot run against either driver.
276 lines
9.2 KiB
C
276 lines
9.2 KiB
C
/*
|
|
* 989_sepstructdef_run — sep-build of a dep package that EXPORTS aggregate-
|
|
* initializer defs: `export def info: s = s{a=7,b=11};` (N_STRUCTLIT) and
|
|
* `export def arr: [3]i32 = [1,2,3];` (N_ARRLIT) — BUG-2 (#70).
|
|
*
|
|
* Root cause it guards: the `.wwi` producer's const-expr emitter handled
|
|
* scalar/N_BIN/N_UN/N_DOT but NOT N_STRUCTLIT(15)/N_ARRLIT(16). An exported
|
|
* aggregate-init def aborted the producer with "unhandled const-expr node
|
|
* kind 15", so the dep never produced a `.wwi` and the sep-build failed.
|
|
*
|
|
* Fix (producer, both stages): an aggregate-init def is a DATA-global (#52),
|
|
* so the producer emits a value-LESS prototype `export def X: T;` — the
|
|
* defining package's own `.o` emits the struct/array DATA; the importer
|
|
* registers the def by TYPE only (cgen's emit_defs/emit_lets already skip a
|
|
* NULL-rhs def, no dup DATA) and reads resolve to external refs the linker
|
|
* fills. Consuming the prototype also required the def parser (both stages)
|
|
* to accept the bodyless form, mirroring the existing fn-prototype arm.
|
|
*
|
|
* Graph (smallest reproducing shape): root -> { d (one real dep package) }.
|
|
* d exports an aggregate STRUCT def (info), an aggregate ARRAY def (arr),
|
|
* and three reader fns; root sums them at RUNTIME.
|
|
*
|
|
* Asserts (direct builds compile every package):
|
|
* 1. Build + LINK + run, BOTH stages -> exit EXPECT_EXIT. The link+run is
|
|
* the external-DATA-ref proof: the importer's CALL d.geta / d.getelem /
|
|
* d.getb and d's own LEAQ d.info/d.arr(SB) refs resolve against the dep
|
|
* `.o`. Pre-fix the dep producer aborted ("node kind 15") -> no binary.
|
|
* 2. cs==ww (rule 10): per-package `.s`/`.wwi`/`.unit.ww` for {d,__root}
|
|
* AND the final binary are byte-identical between the two drivers.
|
|
* 3. The dep `.wwi` carries the value-LESS prototypes `export def info: s;`
|
|
* and `export def arr: [3]i32;` (the fix's surface), and NOT the old
|
|
* value-serialized `export def info: s = ` form (NON-VACUITY: proves the
|
|
* producer took the new aggregate arm, not the scalar arm).
|
|
*
|
|
* All intermediates are `-o`-redirected to /tmp for isolation. Models
|
|
* 989_seproot_export_run.c conventions; 989 prefix per the sep-gate precedent.
|
|
*/
|
|
#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 20 /* info.a(7) + arr[1](2) + info.b(11) */
|
|
|
|
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 td[] = "/tmp/wwsepstructdef_XXXXXX";
|
|
char cmd[8192], p[1024];
|
|
int fail = 0, cleanup_fail = 0, have_lib = 0, have_d = 0;
|
|
|
|
if (mkdtemp(td) == NULL) return 1;
|
|
|
|
/* lib/d — the dep package exporting aggregate-init defs + reader fns.
|
|
* The struct type must be EXPORTED so the prototype names a visible
|
|
* type (an exported def over an unexported type is a separate loud
|
|
* reject, check_exported_type). */
|
|
snprintf(p, sizeof p, "%s/lib", td);
|
|
if (mkdir(p, 0755) != 0) { fail++; goto out; }
|
|
have_lib = 1;
|
|
snprintf(p, sizeof p, "%s/lib/d", td);
|
|
if (mkdir(p, 0755) != 0) { fail++; goto out; }
|
|
have_d = 1;
|
|
snprintf(p, sizeof p, "%s/lib/d/mod.ww", td);
|
|
if (write_file(p,
|
|
"package d;\n"
|
|
"export type s = struct { a: i32, b: i32 };\n"
|
|
"export def info: s = s { a = 7, b = 11 };\n"
|
|
"export def arr: [3]i32 = [1, 2, 3];\n"
|
|
"export fn geta() i32 = { return info.a; };\n"
|
|
"export fn getb() i32 = { return info.b; };\n"
|
|
"export fn getelem(i: i32) i32 = { return arr[i]; };\n"))
|
|
{ fail++; goto out; }
|
|
|
|
char rootww[1024];
|
|
snprintf(rootww, sizeof rootww, "%s/root.ww", td);
|
|
if (write_file(rootww,
|
|
"package main;\n"
|
|
"import d;\n"
|
|
"fn main() i32 = {\n"
|
|
"\treturn d.geta() + d.getelem(1) + d.getb();\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);
|
|
/* Each direct build compiles every package, so the
|
|
* `.s`/`.wwi`/`.unit.ww` this gate inspects are always produced. */
|
|
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, "sepstructdef FAIL: %s build (pre-fix: the "
|
|
"aggregate-init def aborts the producer with \"node kind 15\")\n",
|
|
stg[s].drv);
|
|
fail++;
|
|
continue;
|
|
}
|
|
int rc = runwait(stg[s].prog);
|
|
if (rc != EXPECT_EXIT) {
|
|
fprintf(stderr, "sepstructdef FAIL: %s prog exit=%d expected %d "
|
|
"(external aggregate-DATA ref did not resolve)\n",
|
|
stg[s].drv, rc, EXPECT_EXIT);
|
|
fail++;
|
|
}
|
|
}
|
|
|
|
/* cs==ww (rule 10): per-package .s/.wwi/.unit.ww + final binary. */
|
|
const char *pkgs[] = { "d", "__root" };
|
|
for (int i = 0; i < 2; i++) {
|
|
const char *suf[] = { ".s", ".wwi", ".unit.ww" };
|
|
for (int k = 0; k < 3; k++) {
|
|
/* the root's `.wwi` is intentionally not produced (BUG-1 fix). */
|
|
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, "sepstructdef 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, "sepstructdef FAIL: cs exe != ww exe (rule 10)\n");
|
|
fail++;
|
|
}
|
|
|
|
/* The dep `.wwi` carries the value-LESS aggregate prototypes — and NOT
|
|
* the old value-serialized form (NON-VACUITY: the producer took the new
|
|
* aggregate arm, not the scalar `= <init>` arm). */
|
|
{
|
|
char wwi[1024];
|
|
snprintf(wwi, sizeof wwi, "%s/prog.cs.sepwork/d.wwi", td);
|
|
if (file_contains(wwi, "export def info: s;\n") != 0) {
|
|
fprintf(stderr, "sepstructdef FAIL: d.wwi lacks value-less "
|
|
"`export def info: s;` (struct-lit def prototype)\n");
|
|
fail++;
|
|
}
|
|
if (file_contains(wwi, "export def arr: [3]i32;\n") != 0) {
|
|
fprintf(stderr, "sepstructdef FAIL: d.wwi lacks value-less "
|
|
"`export def arr: [3]i32;` (array-lit def prototype)\n");
|
|
fail++;
|
|
}
|
|
if (file_contains(wwi, "export def info: s = ") == 0) {
|
|
fprintf(stderr, "sepstructdef FAIL: d.wwi serialized the aggregate "
|
|
"value (`export def info: s = ...`) -> vacuous gate\n");
|
|
fail++;
|
|
}
|
|
}
|
|
|
|
out:
|
|
for (int s = 0; s < 2; s++) {
|
|
const char *tag = s == 0 ? "cs" : "ww";
|
|
snprintf(p, sizeof p, "%s/prog.%s.sepwork", td, tag);
|
|
snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", p);
|
|
if (runwait(cmd) != 0) cleanup_fail = 1;
|
|
snprintf(p, sizeof p, "%s/prog.%s", td, tag);
|
|
if (unlink(p) != 0 && errno != ENOENT) cleanup_fail = 1;
|
|
}
|
|
snprintf(p, sizeof p, "%s/root.ww", td);
|
|
if (unlink(p) != 0 && errno != ENOENT) cleanup_fail = 1;
|
|
if (have_d) {
|
|
snprintf(p, sizeof p, "%s/lib/d/mod.ww", td);
|
|
if (unlink(p) != 0 && errno != ENOENT) cleanup_fail = 1;
|
|
snprintf(p, sizeof p, "%s/lib/d", td);
|
|
if (rmdir(p) != 0 && errno != ENOENT) cleanup_fail = 1;
|
|
}
|
|
if (have_lib) {
|
|
snprintf(p, sizeof p, "%s/lib", td);
|
|
if (rmdir(p) != 0 && errno != ENOENT) cleanup_fail = 1;
|
|
}
|
|
if (rmdir(td) != 0) cleanup_fail = 1;
|
|
if (cleanup_fail) {
|
|
fprintf(stderr, "sepstructdef FAIL: cleanup incomplete under %s\n", td);
|
|
fail++;
|
|
}
|
|
if (fail) {
|
|
fprintf(stderr, "sepstructdef: %d check(s) failed\n", fail);
|
|
return 1;
|
|
}
|
|
printf("sepstructdef: exported struct-lit + array-lit defs via "
|
|
"build_one_sep — build+link+run (exit %d) + cs==ww per-pkg + binary "
|
|
"+ value-less `.wwi` prototypes (both stages)\n", EXPECT_EXIT);
|
|
return 0;
|
|
}
|