ww test: @test name-filter via fnmatch (both stages)

`ww test <file> <pattern>` runs only the @test fns whose names match the
fnmatch glob; no pattern runs all (byte-for-byte the pre-filter path);
zero matches prints "No tests run" and exits 0 (Hare ground truth
ref/hare/test/+test.ha:114-117). A pattern in directory mode is rejected
"ww test: pattern needs a single test file" (rc 2), identical wording in
both twins (cmd/ww/main.c do_test + selfhost/cmd/ww/main.ww dotest).

Mechanism (a): rt/start.s stashes argc/argv into rt_argc/rt_argv getters
(rt_envp twin shape, -T synth untouched so 990-997 byte-id holds);
lib/os.args() rebuilds the []str view, build-once-cached; lib/test/run.ww
imports fnmatch and filters av[1..] (argv[0] is the binary path). The
driver forwards the 2nd positional as argv[1] via fork/execv (cstage) /
procrun (wwstage) so glob metachars aren't shell-expanded.

os.args() is the first `alloc`-caller in the base os module, so os.ww now
imports rt — the `alloc` builtin's malloc lowers to rt_malloc only when
the rt binding is bundled (mirror lib/strings/strings.ww:30); without it a
plain `ww build` of any os-importing program links bare libc `malloc`
(undefined). os is bundled by ~every program, so this is load-bearing.

The lib/test floor rises os-only -> os+fnmatch+ascii+strings in every -T
build; the bundled `ascii` module vs a `@test fn ascii` collision that
exposed is closed by the preceding #30 promote commit. 989_test_filter
pins the full matrix on both twins byte-identically; 949 gains the
dir-mode reject row. (#17)
This commit is contained in:
2026-06-11 04:51:17 +09:00
parent 64f0ddf01b
commit b9c4562135
16 changed files with 853 additions and 165 deletions

View File

@@ -54,6 +54,32 @@ run(const char *cmd)
return 1;
}
/* run_test_bin — exec the built test binary with an optional name-filter
* pattern as argv[1] (lib/test run() reads it via os.args). fork+execv
* (not system()) so glob metacharacters in the pattern reach the binary
* verbatim instead of being expanded by the shell. Mirrors the wwstage
* twin (selfhost/cmd/ww/main.ww runsingletest, which always builds an
* execargv for procrun). #17 fnmatch filter. */
static int
run_test_bin(const char *bin, const char *pattern)
{
pid_t pid = fork();
if (pid < 0) { perror("ww: fork"); return -1; }
if (pid == 0) {
char *xargv[3];
xargv[0] = (char *)bin;
if (pattern) { xargv[1] = (char *)pattern; xargv[2] = NULL; }
else { xargv[1] = NULL; }
execv(bin, xargv);
perror("ww: exec");
_exit(127);
}
int status = 0;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) return WEXITSTATUS(status);
return 1;
}
/* Set of imported module paths, kept on the heap. Used to break
* cycles in `use` resolution. Linear because typical imports are
* a handful per build. */
@@ -815,6 +841,10 @@ do_test(int argc, char **argv)
* wwstage twin (selfhost/cmd/ww/main.ww dotest). */
int compileonly = 0;
char outstem[1024] = {0};
/* #17: an optional second positional after the target is a fnmatch
* name-filter pattern, forwarded to the test binary as argv[1]. Only
* meaningful for a single test file/module — rejected in dir mode. */
const char *pattern = NULL;
for (int i = 0; i < argc; i++) {
if (argv[i][0] == '-') {
if (argv[i][1] == 'I') {
@@ -849,6 +879,8 @@ do_test(int argc, char **argv)
}
} else if (src == NULL) {
src = argv[i];
} else if (pattern == NULL) {
pattern = argv[i];
}
}
const char *target = src ? src : ".";
@@ -870,7 +902,7 @@ do_test(int argc, char **argv)
if (build_one(resolved, is_dir, outp, outstem[0] ? outstem : NULL,
incs, "", "", 1) != 0) return 1;
if (compileonly) return 0;
int rc = run(outp);
int rc = run_test_bin(outp, pattern);
if (!outstem[0]) unlink(outp);
return rc;
}
@@ -883,7 +915,7 @@ do_test(int argc, char **argv)
if (build_one(target, 0, outp, outstem[0] ? outstem : NULL,
incs, "", "", 1) != 0) return 1;
if (compileonly) return 0;
int rc = run(outp);
int rc = run_test_bin(outp, pattern);
if (!outstem[0]) unlink(outp);
return rc;
}
@@ -895,6 +927,12 @@ do_test(int argc, char **argv)
fprintf(stderr, "ww test: -c/-o need a single test file\n");
return 2;
}
/* #17: a name-filter pattern is per-binary; directory mode builds one
* binary per *_test.ww, so a single pattern can't sensibly route. */
if (pattern) {
fprintf(stderr, "ww test: pattern needs a single test file\n");
return 2;
}
/* directory — run every *_test.ww inside. */
char **files = NULL;
int n = 0;