ww: test sources are *_test.ww only (Go contract)

Go compiles only _test.go files as tests; discovery now keys on the
_test.ww suffix alone. The line-leading-@test compatibility allowance
(noncanonical filenames admitted as test sources) is removed from both
driver stages and the coordinator. An @test declaration outside a
*_test.ww file is rejected loudly ("@test declaration outside
*_test.ww", wording byte-identical cs/ww) instead of silently running
under compose or silently dropping in a non-T build (#6). Tree audit
found zero real carriers; the two allowance fixtures flip canonical
(dep_test.ww, widget_test.ww). New pins: direnum attest-noncanon
reject row (both-stage stderr parity) and the coordinator
noncanonical_attest_rejected package row.
This commit is contained in:
2026-08-08 20:38:02 +09:00
parent 0cf9643be8
commit 659e859f34
10 changed files with 109 additions and 47 deletions

View File

@@ -196,7 +196,7 @@ locate_import_in(const char *dir, const char *path_form, char *out,
*
* #98: "a module IS the directory" — a directory-package on ANY entry
* wins over a same-named sibling FILE on an EARLIER entry. The driver
* builds the searchpath srcd-first; a co-located `lib/<mod>/<mod>test.ww`
* builds the searchpath srcd-first; a co-located `lib/<mod>/<x>_test.ww`
* entry makes srcd = lib/<mod>, so a self-named `import <mod>` would
* else file-hit the sibling lib/<mod>/<mod>.ww and fold it
* inline under the wrong module-reset → "package <mod> does not match
@@ -243,9 +243,9 @@ strs_cmp(const void *a, const void *b)
return strcmp(sa, sb);
}
/* A temporary migration rule keeps the pre-underscore corpus working without
* reserving every filename ending in "test.ww": only a real line-leading
* @test declaration makes a noncanonical source test-only. */
/* Go's contract: only *_test.ww is a test source. A line-leading @test
* declaration anywhere else would be silently dropped by a non-T build
* (#6, the Hare model), so directory enumeration rejects it loudly. */
static int
file_has_line_test(const char *path)
{
@@ -267,9 +267,10 @@ file_has_line_test(const char *path)
}
/* enumerate_dir_ww — collect production *.ww paths in `dirpath`, excluding
* canonical *_test.ww plus the explicit line-leading-@test compatibility
* sources, then sort byte-wise. This is the sole directory-membership
* discovery path; the owning seppkg retains the returned list. */
* *_test.ww test sources, then sort byte-wise. A line-leading @test in
* any other source is diagnosed here and returns -2. This is the sole
* directory-membership discovery path; the owning seppkg retains the
* returned list. */
static int
enumerate_dir_ww(const char *dirpath, char ***out_files)
{
@@ -285,9 +286,18 @@ enumerate_dir_ww(const char *dirpath, char ***out_files)
if (strcmp(nm + nl - 3, ".ww") != 0) continue;
char path[2048];
snprintf(path, sizeof path, "%s/%s", dirpath, nm);
if ((nl >= 8 && strcmp(nm + nl - 8, "_test.ww") == 0)
|| file_has_line_test(path))
if (nl >= 8 && strcmp(nm + nl - 8, "_test.ww") == 0)
continue;
if (file_has_line_test(path)) {
fprintf(stderr,
"ww: %s: @test declaration outside *_test.ww\n",
path);
for (int i = 0; i < n; i++) free(arr[i]);
free(arr);
closedir(d);
*out_files = NULL;
return -2;
}
if (n + 1 > cap) {
cap = cap ? cap * 2 : 8;
arr = realloc(arr, cap * sizeof *arr);
@@ -634,7 +644,9 @@ sep_load_pkg(struct sepgraph *g, int pi, const char *searchpath)
if (g->pkg[pi].is_dir) {
g->pkg[pi].nsources = enumerate_dir_ww(g->pkg[pi].entry,
&g->pkg[pi].sources);
if (g->pkg[pi].nsources < 0) {
if (g->pkg[pi].nsources == -2) {
rc = -1; /* diagnosed in enumerate_dir_ww */
} else if (g->pkg[pi].nsources < 0) {
fprintf(stderr, "ww: cannot read directory %s\n",
g->pkg[pi].entry);
rc = -1;