ww: recursive DIR/... package discovery for ww test
This commit is contained in:
@@ -32,6 +32,7 @@ static const char *usage =
|
|||||||
" foo.ww literal file\n"
|
" foo.ww literal file\n"
|
||||||
" foo search cwd, -I dirs, then $WW_LIB for foo.ww or foo/foo.ww\n"
|
" foo search cwd, -I dirs, then $WW_LIB for foo.ww or foo/foo.ww\n"
|
||||||
" lib/foo directory: build lib/foo/foo.ww\n"
|
" lib/foo directory: build lib/foo/foo.ww\n"
|
||||||
|
" lib/... every package under lib, recursively (test only)\n"
|
||||||
" . build the cwd's <basename>.ww\n";
|
" . build the cwd's <basename>.ww\n";
|
||||||
|
|
||||||
static char *self_dir; /* directory containing this binary */
|
static char *self_dir; /* directory containing this binary */
|
||||||
@@ -1689,6 +1690,29 @@ do_test(int argc, char **argv)
|
|||||||
fprintf(stderr, "ww test: -S needs -o\n");
|
fprintf(stderr, "ww test: -S needs -o\n");
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
/* Go's ./... form: a trailing "..." element is a package-tree
|
||||||
|
* request for the coordinator, never a literal path — recognized
|
||||||
|
* before stat, with the directory-mode rejects. */
|
||||||
|
size_t tlen = strlen(target);
|
||||||
|
if (strcmp(target, "...") == 0 ||
|
||||||
|
(tlen >= 4 && strcmp(target + tlen - 4, "/...") == 0)) {
|
||||||
|
if (outstem[0]) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"ww test: -c/-S/-o need a single test file\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (workdir[0]) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"ww test: -w needs a single test file\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (pattern) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"ww test: pattern needs a single test file\n");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return exec_package_tests(argc, argv, src, NULL, 0);
|
||||||
|
}
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (stat(target, &st) != 0) {
|
if (stat(target, &st) != 0) {
|
||||||
/* not a literal path — try module resolution and run as
|
/* not a literal path — try module resolution and run as
|
||||||
|
|||||||
@@ -188,7 +188,12 @@ and nonempty-version-constant assertions.
|
|||||||
`ww test` delegates directory package requests to the native package
|
`ww test` delegates directory package requests to the native package
|
||||||
coordinator. The coordinator owns discovery, package grouping, same-package and
|
coordinator. The coordinator owns discovery, package grouping, same-package and
|
||||||
external-package test composition, filtering, result aggregation, and its
|
external-package test composition, filtering, result aggregation, and its
|
||||||
internal temporary workspace. With `-c`, it publishes each exact
|
internal temporary workspace. A trailing `...` path element (`ww test lib/...`,
|
||||||
|
Go's `./...` form) is recognized by both driver stages before path resolution
|
||||||
|
and walks the tree rooted at the prefix: every subdirectory whose name does not
|
||||||
|
begin with `.` or `_` is descended with the same lstat/no-symlink discipline,
|
||||||
|
each test-bearing directory becomes one package run, and source-bearing
|
||||||
|
directories without tests report the usual `?` line. With `-c`, it publishes each exact
|
||||||
`<package>.test` binary and adjacent `<package>.test.sepwork` tree in the
|
`<package>.test` binary and adjacent `<package>.test.sepwork` tree in the
|
||||||
package directory; those become caller-owned artifacts. Without `-c`, it
|
package directory; those become caller-owned artifacts. Without `-c`, it
|
||||||
removes the temporary binary and scratch with its workspace. The language
|
removes the temporary binary and scratch with its workspace. The language
|
||||||
@@ -306,7 +311,6 @@ timeout policy in this architecture.
|
|||||||
Carried over from the dissolved project plan; each is deliberate scope, not
|
Carried over from the dissolved project plan; each is deliberate scope, not
|
||||||
drift:
|
drift:
|
||||||
|
|
||||||
- Recursive package discovery (a `./...`-equivalent pattern) for `ww test`.
|
|
||||||
- Package-level concurrency for the coordinator's builds (it is
|
- Package-level concurrency for the coordinator's builds (it is
|
||||||
single-threaded by design today).
|
single-threaded by design today).
|
||||||
- A package-level `-o` contract (single-file `ww test -o` exists; directory
|
- A package-level `-o` contract (single-file `ww test -o` exists; directory
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ fn pkgfailpath(path: str, reason: str) void = {
|
|||||||
|
|
||||||
fn pkgusage() void = {
|
fn pkgusage() void = {
|
||||||
let s: str = strings.concat(
|
let s: str = strings.concat(
|
||||||
"usage: wwtest package [-c] [-list] [-j N] [-I DIR] [-run|-filter GLOB] [-timeout-ms=N] [DIR] [-- GLOB ...]\n",
|
"usage: wwtest package [-c] [-list] [-j N] [-I DIR] [-run|-filter GLOB] [-timeout-ms=N] [DIR | DIR/...] [-- GLOB ...]\n",
|
||||||
" *_test.ww is canonical; noncanonical files require an actual @test declaration\n",
|
" *_test.ww is canonical; noncanonical files require an actual @test declaration\n",
|
||||||
" -c retains the compiled package binaries; -j is reserved by sequential v1\n");
|
" -c retains the compiled package binaries; -j is reserved by sequential v1\n");
|
||||||
pkgput(os.STDERR_FILENO, s);
|
pkgput(os.STDERR_FILENO, s);
|
||||||
@@ -240,10 +240,12 @@ fn pkgkeepfile(name: str) bool = {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The first vertical slice owns exactly one explicit package directory. It
|
// One explicit package directory; with recurse (the DIR/... form) every
|
||||||
// never recurses, and lstat is used both for the root and each candidate so a
|
// subdirectory whose name does not begin with '.' or '_' is descended as
|
||||||
// symlink cannot be used to cross that boundary (including DT_UNKNOWN files).
|
// well, Go's ./... convention. lstat is used for the root and each
|
||||||
fn pkgdiscoverdir(path: str, st: *pkgdiscover) void = {
|
// candidate so a symlink cannot be used to cross the boundary (including
|
||||||
|
// DT_UNKNOWN files); a symlinked subdirectory is skipped, not followed.
|
||||||
|
fn pkgdiscoverdir(path: str, st: *pkgdiscover, recurse: bool) void = {
|
||||||
let rootstat: os.filestat;
|
let rootstat: os.filestat;
|
||||||
match (os.lstat(&rootstat, path)) {
|
match (os.lstat(&rootstat, path)) {
|
||||||
case void => void;
|
case void => void;
|
||||||
@@ -307,6 +309,22 @@ fn pkgdiscoverdir(path: str, st: *pkgdiscover) void = {
|
|||||||
st.errors += 1;
|
st.errors += 1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
} else if (recurse && name.len != 0
|
||||||
|
&& name[0] != '.' && name[0] != '_') {
|
||||||
|
let child: str = strings.concat(path, "/", name);
|
||||||
|
let fi: os.filestat;
|
||||||
|
match (os.lstat(&fi, child)) {
|
||||||
|
case void => {
|
||||||
|
if (pkgmodeis(fi.mode, os.mode.DIR)) {
|
||||||
|
pkgdiscoverdir(child, st, true);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
case let e: os.oserror => {
|
||||||
|
pkgfailpath(child,
|
||||||
|
"cannot stat directory entry");
|
||||||
|
st.errors += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
off += reclen;
|
off += reclen;
|
||||||
};
|
};
|
||||||
@@ -690,15 +708,28 @@ export fn packagecommand(args: []str) int = {
|
|||||||
"wwtest package: -j is reserved; sequential package scheduling is active");
|
"wwtest package: -j is reserved; sequential package scheduling is active");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Go's ./... form: a trailing "..." path element walks the tree
|
||||||
|
// rooted at the prefix instead of one explicit directory.
|
||||||
|
let discoverroot: str = roots[0];
|
||||||
|
let recurse: bool = false;
|
||||||
|
if (strings.compare(discoverroot, "...") == 0) {
|
||||||
|
discoverroot = ".";
|
||||||
|
recurse = true;
|
||||||
|
} else if (strings.hassuffix(discoverroot, "/...")) {
|
||||||
|
discoverroot = discoverroot[0:discoverroot.len - 4];
|
||||||
|
if (discoverroot.len == 0) { discoverroot = "/"; };
|
||||||
|
recurse = true;
|
||||||
|
};
|
||||||
|
|
||||||
let ds: pkgdiscover;
|
let ds: pkgdiscover;
|
||||||
let discoveredpaths: []str = alloc([], 64u64)!;
|
let discoveredpaths: []str = alloc([], 64u64)!;
|
||||||
ds.paths = discoveredpaths;
|
ds.paths = discoveredpaths;
|
||||||
ds.errors = 0;
|
ds.errors = 0;
|
||||||
pkgdiscoverdir(roots[0], &ds);
|
pkgdiscoverdir(discoverroot, &ds, recurse);
|
||||||
if (ds.errors != 0) { return 1; };
|
if (ds.errors != 0) { return 1; };
|
||||||
pkgsort(ds.paths);
|
pkgsort(ds.paths);
|
||||||
if (ds.paths.len == 0) {
|
if (ds.paths.len == 0) {
|
||||||
pkgfailpath(roots[0], "directory contains no WW package sources");
|
pkgfailpath(discoverroot, "directory contains no WW package sources");
|
||||||
return 1;
|
return 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2044,7 +2044,7 @@ fn resolvemodule(selfdir: *u8, name: *u8, incs: *u8, isdir: *i32) *u8 = {
|
|||||||
// ---- Subcommand handlers ----------------------------------------------
|
// ---- Subcommand handlers ----------------------------------------------
|
||||||
|
|
||||||
fn writeusage(fd: i32) void = {
|
fn writeusage(fd: i32) void = {
|
||||||
let s: str = "usage: ww [-V] <subcommand> [args...]\n -V print version and exit\n build [-S] [-w DIR] [-o FILE] [path] compile module; -S stops after package asm\n run [path] ... build then exec, passing extra args to the program\n test [-S -o STEM] [-w DIR] [options] [path] build/run tests; -S emits package asm\n version print version and exit\n\n path forms:\n foo.ww literal file\n foo search cwd, -I dirs, then $WW_LIB-equiv for foo.ww or foo/foo.ww\n lib/foo directory: build lib/foo/foo.ww\n . build the cwd's <basename>.ww\n";
|
let s: str = "usage: ww [-V] <subcommand> [args...]\n -V print version and exit\n build [-S] [-w DIR] [-o FILE] [path] compile module; -S stops after package asm\n run [path] ... build then exec, passing extra args to the program\n test [-S -o STEM] [-w DIR] [options] [path] build/run tests; -S emits package asm\n version print version and exit\n\n path forms:\n foo.ww literal file\n foo search cwd, -I dirs, then $WW_LIB-equiv for foo.ww or foo/foo.ww\n lib/foo directory: build lib/foo/foo.ww\n lib/... every package under lib, recursively (test only)\n . build the cwd's <basename>.ww\n";
|
||||||
os.write(fd, s.ptr, s.len: u64);
|
os.write(fd, s.ptr, s.len: u64);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -2672,6 +2672,34 @@ fn dotest(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
|||||||
return 2;
|
return 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Go's ./... form: a trailing "..." element is a package-tree
|
||||||
|
// request for the coordinator, never a literal path — recognized
|
||||||
|
// before stat, with the directory-mode rejects.
|
||||||
|
let tlen: u64 = cstrlen(target);
|
||||||
|
let istree: bool = cstreqlit(target, "...");
|
||||||
|
if (!istree && tlen >= 4u64) {
|
||||||
|
istree = target[tlen - 4u64] == '/'
|
||||||
|
&& target[tlen - 3u64] == '.'
|
||||||
|
&& target[tlen - 2u64] == '.'
|
||||||
|
&& target[tlen - 1u64] == '.';
|
||||||
|
};
|
||||||
|
if (istree) {
|
||||||
|
if (outstem != nil) {
|
||||||
|
cerr("ww test: -c/-S/-o need a single test file\n");
|
||||||
|
return 2;
|
||||||
|
};
|
||||||
|
if (workdir != nil) {
|
||||||
|
cerr("ww test: -w needs a single test file\n");
|
||||||
|
return 2;
|
||||||
|
};
|
||||||
|
if (patarg != nil) {
|
||||||
|
cerr("ww test: pattern needs a single test file\n");
|
||||||
|
return 2;
|
||||||
|
};
|
||||||
|
return execpackagetests(selfdir, argv, argc, start,
|
||||||
|
targetindex, nil, false);
|
||||||
|
};
|
||||||
|
|
||||||
let resolved: *u8 = target;
|
let resolved: *u8 = target;
|
||||||
let isdir: i32 = 0;
|
let isdir: i32 = 0;
|
||||||
let found: bool = false;
|
let found: bool = false;
|
||||||
|
|||||||
@@ -443,6 +443,102 @@ fn packagepath(relative: str) str = {
|
|||||||
clean(root);
|
clean(root);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Go's ./... parity: a trailing "..." element walks the tree, one package
|
||||||
|
// run per test-bearing directory, dot- and underscore-prefixed directory
|
||||||
|
// names excluded. The excluded sentinels hold failing tests so a wrong
|
||||||
|
// descent turns the whole run red rather than passing silently.
|
||||||
|
@test fn recursive_tree_discovery() void = {
|
||||||
|
let root: str = fresh();
|
||||||
|
let tree: str = strings.concat(root, "/tree");
|
||||||
|
assert(os.mkdir(tree, 448i32) == 0);
|
||||||
|
assert(os.mkdir(strings.concat(tree, "/alpha"), 448i32) == 0);
|
||||||
|
assert(os.mkdir(strings.concat(tree, "/beta"), 448i32) == 0);
|
||||||
|
assert(os.mkdir(strings.concat(tree, "/beta/inner"), 448i32) == 0);
|
||||||
|
assert(os.mkdir(strings.concat(tree, "/gamma"), 448i32) == 0);
|
||||||
|
assert(os.mkdir(strings.concat(tree, "/.hidden"), 448i32) == 0);
|
||||||
|
assert(os.mkdir(strings.concat(tree, "/_skip"), 448i32) == 0);
|
||||||
|
writefile(strings.concat(tree, "/alpha/alpha.ww"),
|
||||||
|
"package alpha;\nexport fn value() int = { return 7; };\n");
|
||||||
|
writefile(strings.concat(tree, "/alpha/alpha_test.ww"),
|
||||||
|
"package alpha_test;\nimport alpha;\n@test fn treealpha() void = { assert(alpha.value() == 7); };\n");
|
||||||
|
writefile(strings.concat(tree, "/beta/inner/inner_test.ww"),
|
||||||
|
"package inner;\n@test fn treeinner() void = { assert(1 == 1); };\n");
|
||||||
|
writefile(strings.concat(tree, "/gamma/gamma.ww"),
|
||||||
|
"package gamma;\nexport fn g() int = { return 1; };\n");
|
||||||
|
writefile(strings.concat(tree, "/.hidden/hid_test.ww"),
|
||||||
|
"package hid;\n@test fn hidden() void = { assert(false); };\n");
|
||||||
|
writefile(strings.concat(tree, "/_skip/skip_test.ww"),
|
||||||
|
"package skip;\n@test fn skipped() void = { assert(false); };\n");
|
||||||
|
|
||||||
|
let spec: str = strings.concat(tree, "/...");
|
||||||
|
let outc: commandout;
|
||||||
|
let outw: commandout;
|
||||||
|
let treec: []str = [driver("ww"), "test", spec];
|
||||||
|
let treew: []str = [driver("ww_ww"), "test", spec];
|
||||||
|
runcommand(root, "tree-c", treec,
|
||||||
|
(30i64 * (time.second: i64)): time.duration, &outc);
|
||||||
|
runcommand(root, "tree-ww", treew,
|
||||||
|
(30i64 * (time.second: i64)): time.duration, &outw);
|
||||||
|
expectexit(&outc, 0);
|
||||||
|
expectexit(&outw, 0);
|
||||||
|
assert(same(outc.stdout, outw.stdout));
|
||||||
|
assert(same(outc.stderr, outw.stderr));
|
||||||
|
assert(has(outc.stdout, strings.concat("ok ", tree,
|
||||||
|
"/alpha [alpha_test, external]\n")));
|
||||||
|
assert(has(outc.stdout, strings.concat("ok ", tree,
|
||||||
|
"/beta/inner [inner, same-package]\n")));
|
||||||
|
assert(has(outc.stdout, strings.concat("? ", tree,
|
||||||
|
"/gamma [no tests]\n")));
|
||||||
|
assert(!has(outc.stdout, ".hidden"));
|
||||||
|
assert(!has(outc.stdout, "_skip"));
|
||||||
|
|
||||||
|
let patc: []str = [driver("ww"), "test", spec, "glob*"];
|
||||||
|
let patw: []str = [driver("ww_ww"), "test", spec, "glob*"];
|
||||||
|
runcommand(root, "tree-pattern-c", patc, time.second, &outc);
|
||||||
|
runcommand(root, "tree-pattern-ww", patw, time.second, &outw);
|
||||||
|
expectexit(&outc, 2);
|
||||||
|
expectexit(&outw, 2);
|
||||||
|
assert(same(outc.stderr, outw.stderr));
|
||||||
|
assert(has(outc.stderr, "ww test: pattern needs a single test file\n"));
|
||||||
|
|
||||||
|
let oc: []str = [driver("ww"), "test", "-o",
|
||||||
|
strings.concat(root, "/stem"), spec];
|
||||||
|
let ow: []str = [driver("ww_ww"), "test", "-o",
|
||||||
|
strings.concat(root, "/stem"), spec];
|
||||||
|
runcommand(root, "tree-o-c", oc, time.second, &outc);
|
||||||
|
runcommand(root, "tree-o-ww", ow, time.second, &outw);
|
||||||
|
expectexit(&outc, 2);
|
||||||
|
expectexit(&outw, 2);
|
||||||
|
assert(same(outc.stderr, outw.stderr));
|
||||||
|
assert(has(outc.stderr, "ww test: -c/-S/-o need a single test file\n"));
|
||||||
|
|
||||||
|
let wdc: []str = [driver("ww"), "test", "-w", root, spec];
|
||||||
|
let wdw: []str = [driver("ww_ww"), "test", "-w", root, spec];
|
||||||
|
runcommand(root, "tree-w-c", wdc, time.second, &outc);
|
||||||
|
runcommand(root, "tree-w-ww", wdw, time.second, &outw);
|
||||||
|
expectexit(&outc, 2);
|
||||||
|
expectexit(&outw, 2);
|
||||||
|
assert(same(outc.stderr, outw.stderr));
|
||||||
|
assert(has(outc.stderr, "ww test: -w needs a single test file\n"));
|
||||||
|
|
||||||
|
let bare: str = strings.concat(root, "/bare");
|
||||||
|
assert(os.mkdir(bare, 448i32) == 0);
|
||||||
|
assert(os.mkdir(strings.concat(bare, "/sub"), 448i32) == 0);
|
||||||
|
let barec: []str = [driver("ww"), "test",
|
||||||
|
strings.concat(bare, "/...")];
|
||||||
|
let barew: []str = [driver("ww_ww"), "test",
|
||||||
|
strings.concat(bare, "/...")];
|
||||||
|
runcommand(root, "tree-bare-c", barec,
|
||||||
|
(10i64 * (time.second: i64)): time.duration, &outc);
|
||||||
|
runcommand(root, "tree-bare-ww", barew,
|
||||||
|
(10i64 * (time.second: i64)): time.duration, &outw);
|
||||||
|
expectexit(&outc, 1);
|
||||||
|
expectexit(&outw, 1);
|
||||||
|
assert(same(outc.stderr, outw.stderr));
|
||||||
|
assert(has(outc.stderr, "directory contains no WW package sources\n"));
|
||||||
|
clean(root);
|
||||||
|
};
|
||||||
|
|
||||||
fn runtimepath(relative: str) str = {
|
fn runtimepath(relative: str) str = {
|
||||||
return strings.concat(repo(), "/test/package/runtime/", relative);
|
return strings.concat(repo(), "/test/package/runtime/", relative);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user