From 8f370533bb7c20859900f8a027df7f071269d803 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 13 Jun 2026 10:20:46 +0900 Subject: [PATCH] ww driver: zero discovered tests is a loud failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rundirtests returned rc=0 when it discovered no tests — a typo'd path or empty corpus read as success. Fail loudly, matching the cstage driver. --- Makefile | 11 +++ selfhost/cmd/ww/main.combined.ww | 11 +++ selfhost/cmd/ww/main.ww | 11 +++ test/wcc/989_zerotest_run.c | 145 +++++++++++++++++++++++++++++++ 4 files changed, 178 insertions(+) create mode 100644 test/wcc/989_zerotest_run.c diff --git a/Makefile b/Makefile index 581e37c0..3c596660 100644 --- a/Makefile +++ b/Makefile @@ -266,6 +266,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_wwdumpgate_run \ $(BIN)/test_datargate_run \ $(BIN)/test_dynentry_run \ + $(BIN)/test_zerotest_run \ $(BIN)/test_ampfncollide_run \ $(BIN)/test_trycallcollide_run \ $(BIN)/test_gunsigned_run \ @@ -860,6 +861,16 @@ $(BIN)/test_dynentry_run: test/wcc/989_dynentry_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +# 989_zerotest_run (#64, F15 c7): `ww test ` over a directory with no +# *_test.ww files fails loud (rc=1 + "no *_test.ww files in ...") instead of a +# silent rc=0 false-green. Runs both driver twins (rule-10). See the header. +$(BIN)/test_zerotest_run: test/wcc/989_zerotest_run.c \ + $(BIN)/ww $(BIN)/ww_ww \ + $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + # 989_ampfncollide_run (#4, c2): `&fn` synthesis (unoptype TK_AMP + # assignableaddrfn) prefers the current module's fn when a same-leaf fn is # declared in a later module. Builds/rejects on BOTH driver twins (rule-10). diff --git a/selfhost/cmd/ww/main.combined.ww b/selfhost/cmd/ww/main.combined.ww index e2e4eb91..065fc3c6 100644 --- a/selfhost/cmd/ww/main.combined.ww +++ b/selfhost/cmd/ww/main.combined.ww @@ -4394,6 +4394,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { let pass: i32 = 0; let fail: i32 = 0; + let seen: i32 = 0; // #64: count *_test.ww matches for the zero-test gate let buf: []u8 = alloc([], 8192u64)!; buf.len = 8192; let dirlen: u64 = cstrlen(dir); @@ -4409,6 +4410,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { let reclen: u64 = blo + (bhi * 256u64); let name: *u8 = buf.ptr + off + 19u64; if (cstrendswithlit(name, "_test.ww")) { + seen += 1; let nlen: u64 = cstrlen(name); // path = / let path: []u8 = alloc([], (os.PATH_MAX: u64))!; @@ -4458,6 +4460,15 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { n = os.getdents64(fd, buf.ptr, 8192u64); }; os.close(fd); + // #64: a directory with no *_test.ww files is a loud failure, not a + // silent rc=0 false-green. Mirrors cstage do_test (cmd/ww/main.c:945) + // "ww test: no *_test.ww files in %s". + if (seen == 0) { + cerr("ww test: no *_test.ww files in "); + os.write(2, dir, dirlen); + cerr("\n"); + return 1; + }; if (fail == 0) { return 0; }; return 1; }; diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index b04caf4a..607c9ab2 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -1556,6 +1556,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { let pass: i32 = 0; let fail: i32 = 0; + let seen: i32 = 0; // #64: count *_test.ww matches for the zero-test gate let buf: []u8 = alloc([], 8192u64)!; buf.len = 8192; let dirlen: u64 = cstrlen(dir); @@ -1571,6 +1572,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { let reclen: u64 = blo + (bhi * 256u64); let name: *u8 = buf.ptr + off + 19u64; if (cstrendswithlit(name, "_test.ww")) { + seen += 1; let nlen: u64 = cstrlen(name); // path = / let path: []u8 = alloc([], (os.PATH_MAX: u64))!; @@ -1620,6 +1622,15 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { n = os.getdents64(fd, buf.ptr, 8192u64); }; os.close(fd); + // #64: a directory with no *_test.ww files is a loud failure, not a + // silent rc=0 false-green. Mirrors cstage do_test (cmd/ww/main.c:945) + // "ww test: no *_test.ww files in %s". + if (seen == 0) { + cerr("ww test: no *_test.ww files in "); + os.write(2, dir, dirlen); + cerr("\n"); + return 1; + }; if (fail == 0) { return 0; }; return 1; }; diff --git a/test/wcc/989_zerotest_run.c b/test/wcc/989_zerotest_run.c new file mode 100644 index 00000000..133e514a --- /dev/null +++ b/test/wcc/989_zerotest_run.c @@ -0,0 +1,145 @@ +/* + * 989_zerotest_run (#64, F15 c7) — `ww test ` over a directory with no + * *_test.ww files must fail loud, not return rc=0 with a silent false-green. + * + * THE BUG (wwstage driver only, cat-A silent false-green under rc=0): + * rundirtests had no zero-count check — a directory with no *_test.ww files + * yielded pass=0 fail=0 → `return 0` with no output at all. A typo'd or empty + * test dir got a silent green from the wwstage driver, while cstage do_test + * errors "ww test: no *_test.ww files in %s" rc=1 (cmd/ww/main.c:945). THE FIX: + * count *_test.ww matches and loud-fail (rc=1 + the same message) when zero. + * + * row | dir contents | result (cs==ww) + * --------+---------------------------+---------------------------- + * empty | no files | rc != 0 (loud "no *_test.ww") + * nomatch | foo.ww (not *_test.ww) | rc != 0 (loud) + * healthy | foo_test.ww (1 @test) | rc == 0 (control) + * + * empty / nomatch were RED pre-c7 on wwstage (rc=0, no output, false-green). + * healthy pins the real-test path: both stages run it and exit 0. + */ +#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; +} + +/* Set up a fresh dir; optionally drop a file with `body` named `fname`. + * Returns the dir path in `dir`. */ +static void +make_dir(char *dir, size_t dsz, const char *tag, const char *fname, + const char *body) +{ + char cmd[256], fp[256]; + snprintf(dir, dsz, "/tmp/zt_%d_%s", getpid(), tag); + snprintf(cmd, sizeof cmd, "rm -rf %s && mkdir -p %s", dir, dir); + system(cmd); + if (fname) { + snprintf(fp, sizeof fp, "%s/%s", dir, fname); + FILE *f = fopen(fp, "wb"); + if (f) { fputs(body, f); fclose(f); } + } +} + +/* ` test ` rc. */ +static int +run_test(const char *driver, const char *dir) +{ + char cmd[1024]; + snprintf(cmd, sizeof cmd, "%s test %s >/dev/null 2>&1", driver, dir); + return runwait(cmd); +} + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + char absbin[1024]; + if (bin[0] != '/') { + char cwd[1024]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); + bin = absbin; + } + char cdrv[1024], wdrv[1024]; + snprintf(cdrv, sizeof cdrv, "%s/ww", bin); + snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); + int have_ww = (access(wdrv, X_OK) == 0); + if (!have_ww) + fprintf(stderr, "zerotest: skip wwstage (no %s)\n", wdrv); + + struct row { const char *tag; const char *fname; const char *body; + int want_loud; }; + struct row rows[] = { + { "empty", NULL, NULL, 1 }, + { "nomatch", "foo.ww", + "package main;\nexport fn main() i32 = { return 0; };\n", 1 }, + { "healthy", "foo_test.ww", + "package zt_test;\n@test fn t_ok() void = { };\n", 0 }, + }; + + int n = (int)(sizeof rows / sizeof rows[0]); + int fail = 0, total = 0; + for (int i = 0; i < n; i++) { + total++; + char dir[128]; + make_dir(dir, sizeof dir, rows[i].tag, rows[i].fname, rows[i].body); + + int crc = run_test(cdrv, dir); + if (rows[i].want_loud) { + if (crc == 0) { + fprintf(stderr, "zerotest[cstage][%s]: rc=0, expected loud " + "no-tests failure\n", rows[i].tag); + fail++; + } + } else if (crc != 0) { + fprintf(stderr, "zerotest[cstage][%s]: rc=%d, expected 0\n", + rows[i].tag, crc); + fail++; + } + + if (have_ww) { + int wrc = run_test(wdrv, dir); + if (rows[i].want_loud) { + if (wrc == 0) { + fprintf(stderr, "zerotest[wwstage][%s]: rc=0 on a dir with " + "no *_test.ww (silent false-green — #64)\n", rows[i].tag); + fail++; + } + if ((crc == 0) != (wrc == 0)) { + fprintf(stderr, "zerotest[%s]: loud divergence cs_rc=%d " + "ww_rc=%d (#64)\n", rows[i].tag, crc, wrc); + fail++; + } + } else { + if (wrc != 0) { + fprintf(stderr, "zerotest[wwstage][%s]: rc=%d, expected 0\n", + rows[i].tag, wrc); + fail++; + } + } + } + + char cmd[160]; + snprintf(cmd, sizeof cmd, "rm -rf %s", dir); + system(cmd); + } + + if (fail) { + fprintf(stderr, "zerotest_run: %d/%d check(s) failed\n", fail, total); + return 1; + } + printf("zerotest_run: %d/%d ok\n", total, total); + return 0; +}