Files
ww/test/wcc/989_wwispread_sep.c
Hojun-Cho a95a7a316b test/wcc: carrier ownership repair and driver-contract adaptation
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.
2026-08-07 23:21:04 +09:00

197 lines
6.3 KiB
C

/*
* 989_wwispread_sep — #95 .wwi producer must preserve the `...` union-spread
* marker when serializing a tagged-union type under `ww build`.
*
* A spread variant `(...inner | str)` carries Node.op == TK_ELLIPSIS on the
* variant node (parse.c:315-324). The N_TTAGGED serializer loop must re-emit
* the `...` prefix purely syntactically (cstage wwi.c, selfhost wwi.ww) —
* mirroring Hare's unparse (ref/hare/hare/unparse/type.ha:290-300), which
* leaves flattening to the consumer's type-store. Before the fix the marker
* was dropped: liba.wwi read back as `(inner | str)`, a consumer never
* flattened inner's arms, and a bare i32/i64 member was rejected — so fmt /
* log / getopt failed under the sep flip. (The old combined.ww path never
* round-trips through .wwi, so it accepted; the bug was latent until the flip.)
*
* TABLE-DRIVEN over two spread shapes (2-arm `inner` and 3-arm `three`). For
* each shape and each driver stage (cs `ww`, ww `ww_ww`) the test:
* (a) builds the consumer via `ww build` and runs it — clean compile
* + exit 0 proves the imported alias's arms flattened into the spread
* type (the consumer assigns BARE members the spread must expose);
* (b) greps the produced <pkg>.wwi and asserts the spread marker survived;
* (c) cs==ww (rule 10): the .wwi from `ww` vs `ww_ww` must be byte-identical.
*
* NON-VACUITY: reverting the wwi.c/.ww edit reddens BOTH legs — the consumer
* build fails with `init i32 not assignable to declared outer` AND the marker
* grep finds nothing (verified by hand at fix time).
*
* Every build's intermediates are `-o`-redirected to a private /tmp directory.
* Models 989_declns_sep 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>
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`, else 1 (or -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;
}
struct shape {
const char *name; /* label */
const char *main; /* consumer main.ww (imports the spread package) */
const char *wwi; /* produced interface basename for the imported pkg */
const char *marker; /* the spread marker that must survive serialization */
};
static const struct shape shapes[] = {
{ "twoarm", "test/wcc/data/wwispread/main.ww", "liba.wwi", "...inner" },
{ "threearm", "test/wcc/data/wwispread3/main.ww", "libb.wwi", "...three" },
};
static const struct { const char *drv; const char *tag; } drivers[] = {
{ "ww", "cs" },
{ "ww_ww", "ww" },
};
int
main(void)
{
const char *bin = absbin();
if (!bin) { fprintf(stderr, "wwispread FAIL: getcwd\n"); return 1; }
char td[] = "/tmp/wwwwispread_XXXXXX";
char cmd[4096];
if (mkdtemp(td) == NULL) return 1;
int fail = 0, cleanup_fail = 0;
for (size_t s = 0; s < sizeof shapes / sizeof shapes[0]; s++) {
char wwi[2][256] = {{0}};
for (size_t d = 0; d < sizeof drivers / sizeof drivers[0]; d++) {
char prog[160];
snprintf(prog, sizeof prog, "%s/%s.%s", td, shapes[s].name,
drivers[d].tag);
snprintf(cmd, sizeof cmd, "%s/%s build -o %s %s 2>/dev/null",
bin, drivers[d].drv, prog, shapes[s].main);
if (runwait(cmd) != 0) {
fprintf(stderr, "wwispread FAIL: %s build %s "
"(spread member not assignable → marker dropped?)\n",
drivers[d].drv, shapes[s].name);
fail++;
continue;
}
int got = runwait(prog);
if (got != 0) {
fprintf(stderr, "wwispread FAIL: %s %s run exit=%d want=0\n",
drivers[d].drv, shapes[s].name, got);
fail++;
}
snprintf(wwi[d], sizeof wwi[d], "%s/%s.%s.sepwork/%s", td,
shapes[s].name, drivers[d].tag, shapes[s].wwi);
if (file_contains(wwi[d], shapes[s].marker) != 0) {
fprintf(stderr, "wwispread FAIL: %s %s — %s missing `%s` "
"(spread marker dropped in serializer)\n", drivers[d].drv,
shapes[s].name, shapes[s].wwi, shapes[s].marker);
fail++;
}
}
/* cs==ww (rule 10): both stages emit the same interface text. */
if (files_eq(wwi[0], wwi[1]) != 0) {
fprintf(stderr, "wwispread FAIL: %s cs!=ww for %s (rule 10)\n",
shapes[s].name, shapes[s].wwi);
fail++;
}
}
for (size_t s = 0; s < sizeof shapes / sizeof shapes[0]; s++) {
for (size_t d = 0; d < sizeof drivers / sizeof drivers[0]; d++) {
char path[1200];
snprintf(path, sizeof path, "%s/%s.%s.sepwork", td,
shapes[s].name, drivers[d].tag);
snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path);
if (runwait(cmd) != 0) cleanup_fail = 1;
snprintf(path, sizeof path, "%s/%s.%s", td,
shapes[s].name, drivers[d].tag);
if (unlink(path) != 0 && errno != ENOENT) cleanup_fail = 1;
}
}
if (rmdir(td) != 0) cleanup_fail = 1;
if (cleanup_fail) {
fprintf(stderr, "wwispread FAIL: cleanup incomplete under %s\n", td);
fail++;
}
if (fail) {
fprintf(stderr, "wwispread: %d check(s) failed\n", fail);
return 1;
}
printf("wwispread: `...` union-spread marker survives .wwi serialization "
"(2-arm + 3-arm, both stages cs==ww), consumer flattens bare members "
"→ exit 0 (#95)\n");
return 0;
}