run: reject .ww directories as named sources
This commit is contained in:
@@ -7706,6 +7706,19 @@ do_run(int argc, char **argv)
|
||||
if (src == NULL) src = ".";
|
||||
struct stat requested;
|
||||
int literal = stat(src, &requested) == 0;
|
||||
if (literal && S_ISDIR(requested.st_mode)) {
|
||||
size_t n = strlen(src);
|
||||
if (n >= 3 && strcmp(src + n - 3, ".ww") == 0) {
|
||||
if (n >= 8 && strcmp(src + n - 8, "_test.ww") == 0)
|
||||
fprintf(stderr,
|
||||
"ww: cannot run *_test.ww files (%s)\n", src);
|
||||
else
|
||||
fprintf(stderr,
|
||||
"%s is a directory, should be a WW file\n", src);
|
||||
free(incs);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
char resolved[PATH_MAX];
|
||||
int is_dir = 0;
|
||||
if (!resolve_module(src, incs, resolved, sizeof resolved, &is_dir)) {
|
||||
|
||||
@@ -10376,6 +10376,141 @@ test/run command boundaries. No serialized representation changes: build
|
||||
workdir format remains `18`, test workdir format remains `19`, semantic storage
|
||||
format remains `3`, and no cache or result record is added.
|
||||
|
||||
### 11.53 Implemented `.ww`-spelled directory run rejection
|
||||
|
||||
An existing target selected by `ww run` whose exact requested spelling ends
|
||||
`.ww` is a named-source request when ordinary symlink-following `stat` reports
|
||||
a directory. Run rejects that directory before package resolution or private
|
||||
work creation. This is deliberately different from section 11.52's build/test
|
||||
directory route: the command-specific front door, not the shared resolver,
|
||||
owns the distinction.
|
||||
|
||||
#### Pinned authority and applicability
|
||||
|
||||
- **behavior directly implemented or asserted by pinned Go** — official Go
|
||||
1.26.5 commit `c19862e5f8415b4f24b189d065ed739517c548ba`
|
||||
scans leading `.go` operands at
|
||||
`cmd/go/internal/run/run.go:73–112`, rejecting an `_test.go` spelling at
|
||||
103–109 before passing the named set to `GoFilesPackage`.
|
||||
`cmd/go/internal/load/pkg.go:3244–3289`, especially 3274–3281, follows
|
||||
`Stat` and fatals when a named operand is a directory. Later checking,
|
||||
action construction, linking, and execution at `run.go:141–173` are not
|
||||
reached.
|
||||
- **behavior directly implemented or asserted by pinned Go** — the exact Go
|
||||
status, header-free diagnostic, and terminating newline follow
|
||||
`cmd/go/internal/base/base.go:137–145,175–183`,
|
||||
`cmd/go/main.go:98–100`, and `log/log.go:200–245`. Official
|
||||
`cmd/go/testdata/script/run_hello.txt:1–10`, `run_dirs.txt:1–20`, and
|
||||
`run_set_executable_name.txt:4–17` anchor the named-file run front and its
|
||||
distinction from package-form run. Indirect official directory-kind anchors
|
||||
are `list_ambiguous_path.txt:4–15,29–36`,
|
||||
`mod_get_go_file.txt:47–58`, and `mod_symlink_dotgo.txt:4–9`. No official
|
||||
end-to-end `go run` test asserts this directory diagnostic; the result is
|
||||
implemented directly by the cited source.
|
||||
- **behavior derived from the pinned implementation** — WW's `.ww`
|
||||
named-source spelling and existing directory-package form meet at the same
|
||||
practical boundary. The applicable adaptation uses `WW file` and
|
||||
`*_test.ww` while preserving requested operand bytes. It requires no module,
|
||||
manifest, registry, network lookup, generalized import grammar, cache,
|
||||
database, CAS, or build expression.
|
||||
|
||||
#### Ownership, selection, and diagnostics
|
||||
|
||||
- **directly measured WW behavior** — before this change, Cstage followed a
|
||||
direct `.ww` directory or visible `.ww`/`_test.ww` symlink into the ordinary
|
||||
directory graph, invoked compiler, assemblers, linker, and user program, and
|
||||
returned that program's status. WWstage instead entered raw-source loading,
|
||||
returned status 1 with exact `ww: cannot read source\n`, and ran no producer
|
||||
or program. Both removed the private work they had unnecessarily created.
|
||||
- **behavior derived from the pinned implementation** — the true WW owners
|
||||
are only `cmd/ww/main.c::do_run` and
|
||||
`selfhost/cmd/ww/main.ww::dorun`, after their existing option/target parse
|
||||
and requested `stat`, but before `resolve_module`/`resolvemodule` and run
|
||||
scratch creation. Changing the shared resolver would wrongly change the
|
||||
distinct build, test, logical, and dotted-package fronts.
|
||||
- **directly measured WW behavior** — after the run-local classification,
|
||||
both stages return status 1 with empty stdout. A requested spelling ending
|
||||
`_test.ww` emits exactly
|
||||
`ww: cannot run *_test.ww files (OPERAND)\n`; every other selected directory
|
||||
ending `.ww` emits exactly
|
||||
`OPERAND is a directory, should be a WW file\n`. The requested operand is
|
||||
reproduced unchanged. `_test.ww` precedence applies before ordinary
|
||||
directory rejection, including through a symlink.
|
||||
- **directly measured WW behavior** — the rule includes direct and symlinked
|
||||
`.ww` directories and hidden directory spellings `.hidden.ww` and
|
||||
`_hidden.ww`. It also precedes malformed package clauses and missing imports
|
||||
inside the directory because no member is selected or opened. Existing run
|
||||
option errors and target determination retain their earlier precedence.
|
||||
A trailing separator does not end `.ww`; non-`.ww` directories and dotted
|
||||
logical requests retain the established package route.
|
||||
|
||||
#### Build, test, package, and import effects
|
||||
|
||||
- **directly measured WW behavior** — package/source selection is the primary
|
||||
axis. The rejected request creates no raw or directory package, canonical
|
||||
identity, declared-name instance, test variant, initializer, graph node, or
|
||||
action. Requested spelling and followed target are diagnostic observations
|
||||
only, never canonical package, action, symbol, artifact, `.wwi`,
|
||||
publication, or persistence identity.
|
||||
- **directly measured WW behavior** — build retains section 11.52's stat-first
|
||||
rule for the same direct and symlink spellings. It still enumerates the
|
||||
directory, discovers its dotted dependencies, constructs ordinary package
|
||||
actions, and applies the established default, explicit, directory,
|
||||
`/dev/null`, `-S`, publication, persistence, invalidation, and rollback
|
||||
policies. Unaffected build controls retain stage-byte-identical public and
|
||||
semantic artifacts.
|
||||
- **directly measured WW behavior** — raw `ww test`, `ww test -c`, and
|
||||
directory `ww test -S` retain their stat-first directory selection and
|
||||
established diagnostics/artifact bytes. Test package variants, generated
|
||||
main, process topology, filtering, capture, result ordering, retention, and
|
||||
result non-caching are unchanged.
|
||||
- **directly measured WW behavior** — imports are not scanned on the selected
|
||||
run rejection and create no qualifier or edge. Dotted and non-`.ww` package
|
||||
controls retain exact canonical import identity, dependency discovery, and
|
||||
runtime behavior. No physical directory becomes canonical identity through
|
||||
this rule.
|
||||
|
||||
#### Actions, lifecycle, parity, and scope
|
||||
|
||||
- **directly measured WW behavior** — selected rejection invokes no compiler,
|
||||
assembler, archiver, linker, initializer, runtime, or owned child/process
|
||||
group. A fixture-preoccupied exact `/tmp/ww_run_<pid>` plus sentinel remains
|
||||
untouched, proving that the driver does not acquire or mutate its run-scratch
|
||||
path; final cleanup leaves that path absent. It also creates no output,
|
||||
`.sepwork`, unit, `.wwi`, assembly, object, archive, executable, capture,
|
||||
result, transaction, stage, backup, or persistent record. Thus
|
||||
producer/runtime failure, publication, reuse, invalidation, and rollback are
|
||||
inapplicable on this preflight route; all prior caller and committed bytes
|
||||
remain untouched.
|
||||
- **directly measured WW behavior** — classification is request-local,
|
||||
read-only, and isolated under concurrency. An interruption in this preflight
|
||||
owns no child or filesystem resource. The general direct-driver interruption
|
||||
gap from section 11.52 remains open: a different request interrupted after
|
||||
transaction staging can still leave fixed-name `.new` files and poison
|
||||
later persistent reuse. This slice neither reaches nor fixes that machinery.
|
||||
- **directly measured WW behavior** — Cstage and WWstage match exactly on
|
||||
status, stdout, stderr, diagnostic bytes, empty producer traces, artifact
|
||||
absence, exact PID-path non-acquisition and final absence, concurrent
|
||||
isolation, and cleanup for every selected row. Unaffected controls retain
|
||||
diagnostic and public or semantic artifact-byte parity.
|
||||
- **behavior derived from the pinned implementation** — this narrow
|
||||
file-kind slice does not implement the complete suffix-first run front.
|
||||
Regular or missing `_test.ww`, missing or logical `.ww`, multiple named
|
||||
sources, finite FIFOs, and hidden regular sources retain their prior routes
|
||||
and remain separately open where different. Recursive requests, ordinary
|
||||
directory requests, and generalized imports are not changed.
|
||||
|
||||
The focused WW-native observer covers direct, symlinked, hidden, ordinary, and
|
||||
`_test.ww` directory spellings; malformed-package and missing-import
|
||||
precedence; exact requested diagnostics; empty tool/runtime traces; exact
|
||||
PID-path non-acquisition and final absence; concurrency and cleanup;
|
||||
trailing-separator,
|
||||
non-`.ww`, regular-source, and dotted logical run controls; and unchanged
|
||||
same-spelling build and raw/compile-only/assembly-only test boundaries. No
|
||||
serialized representation changes: build workdir format remains `18`, test
|
||||
workdir format remains `19`, semantic storage format remains `3`, and no cache
|
||||
or result record is added.
|
||||
|
||||
## 12. Candidate architectures and hard-gate decision
|
||||
|
||||
Five candidates were developed as coherent systems, not as feature bins.
|
||||
|
||||
29
docs/spec.md
29
docs/spec.md
@@ -370,10 +370,31 @@ ImportPath = ident { "." ident } .
|
||||
no new package, dotted-import, graph, action, symbol, artifact, `.wwi`,
|
||||
publication, or persistence identity. Raw `ww test`, `ww test -c`, and
|
||||
`ww test -S` already use the same directory classification and are unchanged.
|
||||
`ww run` retains its separate named-operand front door. Multiple operands,
|
||||
recursive and logical requests, regular and non-regular source targets, and
|
||||
dangling symlinks are not changed. Build workdir format remains 18, test
|
||||
workdir format remains 19, and semantic storage format remains 3.
|
||||
`ww run` retains its separate named-operand front door, with its
|
||||
`.ww`-spelled directory rule specified next. Multiple operands, recursive
|
||||
and logical requests, regular and non-regular source targets, and dangling
|
||||
symlinks are not changed. Build workdir format remains 18, test workdir
|
||||
format remains 19, and semantic storage format remains 3.
|
||||
- For `ww run`, an existing selected target whose exact requested spelling
|
||||
ends `.ww` is a named-source request when symlink-following `stat` reports a
|
||||
directory. The command rejects it before shared resolution, directory
|
||||
enumeration, source/import loading, graph/action construction, private run
|
||||
scratch, producers, or runtime. A requested suffix `_test.ww` has the earlier
|
||||
exact diagnostic
|
||||
`ww: cannot run *_test.ww files (OPERAND)\n`; every other such directory has
|
||||
exact diagnostic `OPERAND is a directory, should be a WW file\n`. Both forms
|
||||
return status 1 with empty stdout and reproduce the requested operand bytes
|
||||
unchanged. The rule follows a terminal symlink, includes `.hidden.ww` and
|
||||
`_hidden.ww` directory spellings, and precedes any malformed package or
|
||||
missing import inside the directory. A trailing separator does not end
|
||||
`.ww`; ordinary non-`.ww` directories, dotted logical requests, regular and
|
||||
missing operands, multiple named operands, and non-directory non-regular
|
||||
targets retain their established routes. The rejected spelling and followed
|
||||
target are diagnostic metadata only and create no package/import identity,
|
||||
graph/action, symbol, artifact, `.wwi`, publication, transaction,
|
||||
persistence, process, or filesystem owner. Build and all test forms retain
|
||||
their distinct stat-first directory behavior. Build workdir format remains
|
||||
18, test workdir format remains 19, and semantic storage format remains 3.
|
||||
- `import acme.codec;` loads the canonical package `acme.codec`. If that
|
||||
package declares `package wire;`, the importing file sees its exported names
|
||||
as `wire.Name`; `codec.Name` is not an additional binding. An explicit alias
|
||||
|
||||
@@ -278,12 +278,29 @@ parity, direct and symlinked `.ww` directories, a dotted dependency, output
|
||||
modes, cold/warm transaction behavior, compiler and signaled-linker failure
|
||||
preservation, selected-route concurrency, cleanup, and the exact test-command
|
||||
behavior. Direct driver interruption remains owned by the unchanged directory
|
||||
machinery and is not claimed fixed here. The shared resolver and `ww run` are
|
||||
deliberately unchanged—and the observer
|
||||
retains their pre-existing stage difference—because pinned Go's run command
|
||||
has a distinct named-file front door.
|
||||
No test product, result, package/import identity, or serialized format changes;
|
||||
build remains 18, test remains 19, and semantic storage remains 3.
|
||||
machinery and is not claimed fixed here.
|
||||
|
||||
Pinned Go's run command has a distinct suffix-first named-file front door.
|
||||
Accordingly, an existing `ww run` target whose exact requested spelling ends
|
||||
`.ww` and whose symlink-following `stat` result is a directory is rejected in
|
||||
the twin run fronts before the shared resolver and private run scratch. An
|
||||
`_test.ww` spelling emits exactly
|
||||
`ww: cannot run *_test.ww files (OPERAND)\n`; every other selected `.ww`
|
||||
directory emits exactly `OPERAND is a directory, should be a WW file\n`.
|
||||
Both stages return status 1 with empty stdout, open no directory member, scan
|
||||
no import, construct no package/graph/action, invoke no producer or runtime,
|
||||
and create no run scratch or artifact. This includes direct and symlinked
|
||||
directories and hidden directory spellings; directory contents cannot displace
|
||||
the classifier diagnostic. A trailing separator and non-`.ww` or dotted
|
||||
package requests retain the directory package route. The same-spelling build,
|
||||
raw test, `test -c`, and directory `test -S` routes remain stat-first and keep
|
||||
their existing diagnostics and artifact bytes. Thus this run change creates no
|
||||
test product, result, package/import identity, test-process/filter change, or
|
||||
serialized-format change; build remains 18, test remains 19, and semantic
|
||||
storage remains 3. Regular or missing `_test.ww`, missing/logical `.ww`,
|
||||
multiple named sources, finite FIFOs, and hidden regular sources are not
|
||||
claimed by this existing-directory slice. The general direct-driver `.new`
|
||||
interruption residue also remains open.
|
||||
|
||||
List mode uses that same product process and initialization boundary but starts
|
||||
no per-test child. The shared language harness emits only selected qualified
|
||||
|
||||
@@ -9321,6 +9321,16 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
case void => requestedliteral = true;
|
||||
case let e: os.oserror => void;
|
||||
};
|
||||
if (requestedliteral
|
||||
&& ((requestedstat.mode: u32) & 61440u32) == os.mode.DIR: u32
|
||||
&& cstrendswithlit(src, ".ww")) {
|
||||
if (cstrendswithlit(src, "_test.ww")) {
|
||||
cerrpath("ww: cannot run *_test.ww files (", src, ")\n");
|
||||
} else {
|
||||
cerrpath("", src, " is a directory, should be a WW file\n");
|
||||
};
|
||||
return 1;
|
||||
};
|
||||
let isdir: i32 = 0;
|
||||
let resolved: *u8 = resolvemodule(selfdir, src, incs.ptr, &isdir);
|
||||
if (resolved == nil) {
|
||||
|
||||
@@ -1466,8 +1466,8 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
};
|
||||
|
||||
// A visible raw test-source build observes only its initial package/import
|
||||
// header and then omits it. A .ww-spelled directory, including a symlink with
|
||||
// a _test.ww spelling, instead enters the ordinary directory-package route.
|
||||
// header and then omits it. Build and test retain their directory-package
|
||||
// front door, while run's named-source front rejects a .ww-spelled directory.
|
||||
// Neither classification may redefine package or dotted-import identity.
|
||||
@test fn named_test_sources_are_header_loaded_then_omitted() void = {
|
||||
let root: str = fresh();
|
||||
@@ -1593,6 +1593,8 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
let linkersignaldiag: str = "";
|
||||
let directorytestrunout: str = "";
|
||||
let directorytestrunerr: str = "";
|
||||
let directoryrunout: str = "";
|
||||
let directoryrunerr: str = "";
|
||||
let directorytestrunartifacts: str = "";
|
||||
let directorytestbin: str = "";
|
||||
let directorytestcompileartifacts: str = "";
|
||||
@@ -2202,21 +2204,25 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
if (si == 0) { directorytestasm = strings.dup(out.stderr); }
|
||||
else { assert(same(directorytestasm, out.stderr)); };
|
||||
|
||||
// Run was not changed by the build-only dispatcher fix. Cstage follows
|
||||
// the selected directory and executes it; WWstage retains its exact
|
||||
// pre-existing raw-source failure for the same requested symlink.
|
||||
// Run's named-source front rejects the requested _test.ww spelling
|
||||
// before directory loading, tool production, or runtime execution.
|
||||
let directoryrunav: []str = [driver(stages[si]), "run", "-I", include,
|
||||
dirlink];
|
||||
runcommandenv(root, strings.concat("testonly-directory-run-", tags[si]),
|
||||
directoryrunav, env,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
let directoryrundiag: str = strings.concat(
|
||||
"ww: cannot run *_test.ww files (", dirlink, ")\n");
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, directoryrundiag)
|
||||
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
||||
&& readfile(ltrace).len == 0);
|
||||
if (si == 0) {
|
||||
expectexit(&out, 5);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||
directoryrunout = strings.dup(out.stdout);
|
||||
directoryrunerr = strings.dup(out.stderr);
|
||||
} else {
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0
|
||||
&& same(out.stderr, "ww: cannot read source\n"));
|
||||
assert(same(directoryrunout, out.stdout)
|
||||
&& same(directoryrunerr, out.stderr));
|
||||
};
|
||||
rewritefile(dirtargettest, dirtargettestexcluded);
|
||||
|
||||
@@ -2420,6 +2426,396 @@ fn cwdwritedata(dir: str, label: str) void = {
|
||||
clean(root);
|
||||
};
|
||||
|
||||
fn expectrundirectoryrejection(root: str, name: str, av: []str, env: []str,
|
||||
pidrecord: str, ctrace: str, atrace: str, ltrace: str, want: str) void = {
|
||||
rewritefile(pidrecord, "");
|
||||
rewritefile(ctrace, "");
|
||||
rewritefile(atrace, "");
|
||||
rewritefile(ltrace, "");
|
||||
let out: commandout;
|
||||
runcommandenv(root, name, av, env,
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
let ownedrun: str = readfile(pidrecord);
|
||||
let ownedmarker: str = strings.concat(ownedrun, "/observer-marker");
|
||||
let ownedvalid: bool = ownedrun.len > 12
|
||||
&& strings.hasprefix(ownedrun, "/tmp/ww_run_");
|
||||
let observed: bool = out.termination == exec.termination.EXIT
|
||||
&& out.code == 1 && out.stdout.len == 0 && same(out.stderr, want)
|
||||
&& ownedvalid && os.exists(ownedrun)
|
||||
&& same(readfile(ownedmarker), "observer-owned\n")
|
||||
&& readfile(ctrace).len == 0 && readfile(atrace).len == 0
|
||||
&& readfile(ltrace).len == 0;
|
||||
let markerclean: i32 = -1;
|
||||
let dirclean: i32 = -1;
|
||||
if (ownedvalid) {
|
||||
markerclean = os.remove(ownedmarker);
|
||||
dirclean = os.rmdir(ownedrun);
|
||||
};
|
||||
assert(observed && markerclean == 0 && dirclean == 0
|
||||
&& !os.exists(ownedrun));
|
||||
};
|
||||
|
||||
// Run's named-source spelling owns the directory rejection. The physical
|
||||
// directory remains only loader metadata for the distinct build and test
|
||||
// fronts, and rejected source contents cannot create package or import state.
|
||||
@test fn run_dot_ww_directories_are_named_sources() void = {
|
||||
let root: str = fresh();
|
||||
let source: str = strings.concat(root, "/source");
|
||||
let include: str = strings.concat(root, "/include");
|
||||
let dependency: str = strings.concat(include, "/domain/value");
|
||||
let logical: str = strings.concat(include, "/domain/logical");
|
||||
let ordinary: str = strings.concat(source, "/ordinary.ww");
|
||||
let ordinarylink: str = strings.concat(source, "/requested.ww");
|
||||
let testdirectory: str = strings.concat(source, "/direct_test.ww");
|
||||
let testlink: str = strings.concat(source, "/requested_test.ww");
|
||||
let hidden: str = strings.concat(source, "/.hidden.ww");
|
||||
let private: str = strings.concat(source, "/_hidden.ww");
|
||||
let malformed: str = strings.concat(source, "/malformed.ww");
|
||||
let missingimport: str = strings.concat(source, "/missing-import.ww");
|
||||
let nonsuffix: str = strings.concat(source, "/directory-control");
|
||||
let nonsuffixlink: str = strings.concat(source, "/directory-link");
|
||||
let regular: str = strings.concat(source, "/regular.ww");
|
||||
let rawtest: str = strings.concat(source, "/raw_control_test.ww");
|
||||
let prior: str = strings.concat(root, "/prior-public-output");
|
||||
mkdirall(dependency); mkdirall(logical); mkdirall(ordinary);
|
||||
mkdirall(testdirectory); mkdirall(hidden); mkdirall(private);
|
||||
mkdirall(malformed); mkdirall(missingimport); mkdirall(nonsuffix);
|
||||
writefile(strings.concat(dependency, "/value.ww"), strings.concat(
|
||||
"package value;\n",
|
||||
"export fn number() i32 = { return 37; };\n"));
|
||||
writefile(strings.concat(ordinary, "/main.ww"), strings.concat(
|
||||
"package main;\nimport domain.value;\n",
|
||||
"fn main() i32 = { return value.number(); };\n"));
|
||||
writefile(strings.concat(testdirectory, "/main.ww"),
|
||||
"package main;\nfn main() i32 = { return 47; };\n");
|
||||
writefile(strings.concat(hidden, "/main.ww"),
|
||||
"package main;\nfn main() i32 = { return 48; };\n");
|
||||
writefile(strings.concat(private, "/main.ww"),
|
||||
"package main;\nfn main() i32 = { return 49; };\n");
|
||||
writefile(strings.concat(malformed, "/main.ww"),
|
||||
"this directory has no package clause\n");
|
||||
writefile(strings.concat(missingimport, "/main.ww"), strings.concat(
|
||||
"package main;\nimport absent.before.run.classification;\n",
|
||||
"fn main() i32 = { return 50; };\n"));
|
||||
writefile(strings.concat(nonsuffix, "/main.ww"),
|
||||
"package main;\nfn main() i32 = { return 43; };\n");
|
||||
writefile(regular,
|
||||
"package main;\nfn main() i32 = { return 39; };\n");
|
||||
writefile(strings.concat(logical, "/main.ww"),
|
||||
"package main;\nfn main() i32 = { return 41; };\n");
|
||||
writefile(rawtest, strings.concat(
|
||||
"package runroute;\n",
|
||||
"@test fn raw_control() void = { assert(true); };\n"));
|
||||
writefile(prior, "prior-public-bytes\n");
|
||||
assert(os.symlink(ordinary, ordinarylink) == 0);
|
||||
assert(os.symlink(testdirectory, testlink) == 0);
|
||||
assert(os.symlink(nonsuffix, nonsuffixlink) == 0);
|
||||
|
||||
let driverwrapper: str = strings.concat(root, "/driver-wrapper.sh");
|
||||
let compilerwrapper: str = strings.concat(root, "/compiler-wrapper.sh");
|
||||
let assemblerwrapper: str = strings.concat(root, "/assembler-wrapper.sh");
|
||||
let linkerwrapper: str = strings.concat(root, "/linker-wrapper.sh");
|
||||
writeexecutable(driverwrapper, strings.concat(
|
||||
"#!/bin/sh\nowned=/tmp/ww_run_$$\n",
|
||||
"printf '%s' \"$owned\" > \"$WW_RUN_PID_RECORD\"\n",
|
||||
"if test \"$WW_RUN_PREOCCUPY\" = 1; then\n",
|
||||
" mkdir \"$owned\" || exit 124\n",
|
||||
" printf 'observer-owned\\n' > \"$owned/observer-marker\"\n",
|
||||
"fi\n",
|
||||
"exec \"$WW_RUN_REAL_DRIVER\" \"$@\"\n"));
|
||||
writeexecutable(compilerwrapper, strings.concat(
|
||||
"#!/bin/sh\nprintf 'compile\\n' >> \"$WW_RUN_CTRACE\"\n",
|
||||
"exec \"$WW_RUN_REAL_C\" \"$@\"\n"));
|
||||
writeexecutable(assemblerwrapper, strings.concat(
|
||||
"#!/bin/sh\nprintf 'assemble\\n' >> \"$WW_RUN_ATRACE\"\n",
|
||||
"exec \"$WW_RUN_REAL_A\" \"$@\"\n"));
|
||||
writeexecutable(linkerwrapper, strings.concat(
|
||||
"#!/bin/sh\nprintf 'link\\n' >> \"$WW_RUN_LTRACE\"\n",
|
||||
"exec \"$WW_RUN_REAL_L\" \"$@\"\n"));
|
||||
|
||||
let stages: []str = ["ww", "ww_ww"];
|
||||
let compilers: []str = ["w6c", "w6c_ww"];
|
||||
let assemblers: []str = ["w6a", "w6a_ww"];
|
||||
let linkers: []str = ["w6l", "w6l_ww"];
|
||||
let tags: []str = ["c", "ww"];
|
||||
let environments: [][]str = alloc([], 2u64)!;
|
||||
let rejectenvironments: [][]str = alloc([], 2u64)!;
|
||||
let pidrecords: []str = alloc([], 2u64)!;
|
||||
let ctraces: []str = alloc([], 2u64)!;
|
||||
let atraces: []str = alloc([], 2u64)!;
|
||||
let ltraces: []str = alloc([], 2u64)!;
|
||||
let inherited: []str = os.getenvs();
|
||||
let si: i32 = 0;
|
||||
for (si < stages.len) {
|
||||
let pidrecord: str = strings.concat(root, "/driver-pid-", tags[si]);
|
||||
let ctrace: str = strings.concat(root, "/compiler-trace-", tags[si]);
|
||||
let atrace: str = strings.concat(root, "/assembler-trace-", tags[si]);
|
||||
let ltrace: str = strings.concat(root, "/linker-trace-", tags[si]);
|
||||
writefile(pidrecord, ""); writefile(ctrace, "");
|
||||
writefile(atrace, ""); writefile(ltrace, "");
|
||||
append(pidrecords, pidrecord); append(ctraces, ctrace);
|
||||
append(atraces, atrace); append(ltraces, ltrace);
|
||||
let env: []str = alloc([], (inherited.len + 11): u64)!;
|
||||
let ei: i32 = 0;
|
||||
for (ei < inherited.len) {
|
||||
if (!strings.hasprefix(inherited[ei], "WW_W6C=")
|
||||
&& !strings.hasprefix(inherited[ei], "WW_W6A=")
|
||||
&& !strings.hasprefix(inherited[ei], "WW_W6L=")
|
||||
&& !strings.hasprefix(inherited[ei], "WW_RUN_")) {
|
||||
append(env, inherited[ei]);
|
||||
};
|
||||
ei += 1;
|
||||
};
|
||||
append(env, strings.concat("WW_RUN_REAL_DRIVER=", driver(stages[si])));
|
||||
append(env, strings.concat("WW_RUN_PID_RECORD=", pidrecord));
|
||||
append(env, strings.concat("WW_W6C=", compilerwrapper));
|
||||
append(env, strings.concat("WW_W6A=", assemblerwrapper));
|
||||
append(env, strings.concat("WW_W6L=", linkerwrapper));
|
||||
append(env, strings.concat("WW_RUN_CTRACE=", ctrace));
|
||||
append(env, strings.concat("WW_RUN_ATRACE=", atrace));
|
||||
append(env, strings.concat("WW_RUN_LTRACE=", ltrace));
|
||||
append(env, strings.concat("WW_RUN_REAL_C=", driver(compilers[si])));
|
||||
append(env, strings.concat("WW_RUN_REAL_A=", driver(assemblers[si])));
|
||||
append(env, strings.concat("WW_RUN_REAL_L=", driver(linkers[si])));
|
||||
append(environments, env);
|
||||
let rejectenv: []str = alloc([], (env.len + 1): u64)!;
|
||||
let ri: i32 = 0;
|
||||
for (ri < env.len) { append(rejectenv, env[ri]); ri += 1; };
|
||||
append(rejectenv, "WW_RUN_PREOCCUPY=1");
|
||||
append(rejectenvironments, rejectenv);
|
||||
si += 1;
|
||||
};
|
||||
|
||||
let selected: []str = [ordinary, ordinarylink, testdirectory, testlink,
|
||||
hidden, private, malformed, missingimport];
|
||||
let selectedlabels: []str = ["direct", "symlink", "direct-test",
|
||||
"symlink-test", "dot-hidden", "underscore-hidden", "malformed",
|
||||
"missing-import"];
|
||||
let selectedtest: []bool = [false, false, true, true, false, false,
|
||||
false, false];
|
||||
let sourcebefore: str = strings.dup(treesnapshot(source));
|
||||
si = 0;
|
||||
for (si < stages.len) {
|
||||
let ri: i32 = 0;
|
||||
for (ri < selected.len) {
|
||||
let want: str;
|
||||
if (selectedtest[ri]) {
|
||||
want = strings.concat("ww: cannot run *_test.ww files (",
|
||||
selected[ri], ")\n");
|
||||
} else {
|
||||
want = strings.concat(selected[ri],
|
||||
" is a directory, should be a WW file\n");
|
||||
};
|
||||
let av: []str = [driverwrapper, "run", "-I", include,
|
||||
selected[ri]];
|
||||
expectrundirectoryrejection(root, strings.concat("run-reject-",
|
||||
tags[si], "-", selectedlabels[ri]), av, rejectenvironments[si],
|
||||
pidrecords[si], ctraces[si], atraces[si], ltraces[si], want);
|
||||
ri += 1;
|
||||
};
|
||||
let boundaryav: []str = [driverwrapper, "run", "-I", include,
|
||||
ordinarylink, "-I", "program-argument"];
|
||||
let boundarywant: str = strings.concat(ordinarylink,
|
||||
" is a directory, should be a WW file\n");
|
||||
expectrundirectoryrejection(root, strings.concat("run-boundary-",
|
||||
tags[si]), boundaryav, rejectenvironments[si], pidrecords[si],
|
||||
ctraces[si], atraces[si], ltraces[si], boundarywant);
|
||||
si += 1;
|
||||
};
|
||||
assert(same(sourcebefore, treesnapshot(source))
|
||||
&& same(readfile(prior), "prior-public-bytes\n")
|
||||
&& !directoryhasfragment(root, ".sepwork")
|
||||
&& !directoryhasfragment(root, ".wwtxn.")
|
||||
&& !directoryhasfragment(root, ".capture")
|
||||
&& !directoryhasfragment(root, ".result")
|
||||
&& !directoryhasfragment(root, ".request"));
|
||||
|
||||
// Concurrent preflight rejections leave independently preoccupied exact
|
||||
// run paths and diagnostics untouched.
|
||||
let pi: i32 = 0;
|
||||
for (pi < stages.len) {
|
||||
rewritefile(pidrecords[pi], ""); rewritefile(ctraces[pi], "");
|
||||
rewritefile(atraces[pi], ""); rewritefile(ltraces[pi], "");
|
||||
pi += 1;
|
||||
};
|
||||
let cav: []str = [driverwrapper, "run", ordinary];
|
||||
let wav: []str = [driverwrapper, "run", ordinarylink];
|
||||
let cc: exec.command;
|
||||
cc.path = cav[0]; cc.argv = cav;
|
||||
cc.env = rejectenvironments[0]; cc.dir = repo();
|
||||
cc.stdoutpath = strings.concat(root, "/parallel-c.stdout");
|
||||
cc.stderrpath = strings.concat(root, "/parallel-c.stderr");
|
||||
cc.deadline = time.add(time.now(time.clock.monotonic),
|
||||
(60i64 * (time.second: i64)): time.duration);
|
||||
cc.grace = (100i64 * (time.millisecond: i64)): time.duration;
|
||||
let wc: exec.command;
|
||||
wc.path = wav[0]; wc.argv = wav;
|
||||
wc.env = rejectenvironments[1]; wc.dir = repo();
|
||||
wc.stdoutpath = strings.concat(root, "/parallel-ww.stdout");
|
||||
wc.stderrpath = strings.concat(root, "/parallel-ww.stderr");
|
||||
wc.deadline = time.add(time.now(time.clock.monotonic),
|
||||
(60i64 * (time.second: i64)): time.duration);
|
||||
wc.grace = (100i64 * (time.millisecond: i64)): time.duration;
|
||||
let cp: exec.process;
|
||||
let wp: exec.process;
|
||||
exec.start(&cp, &cc); exec.start(&wp, &wc);
|
||||
let cdone: bool = false;
|
||||
let wdone: bool = false;
|
||||
for (!cdone || !wdone) {
|
||||
if (!cdone) { cdone = exec.poll(&cp); };
|
||||
if (!wdone) { wdone = exec.poll(&wp); };
|
||||
if (!cdone || !wdone) {
|
||||
time.sleep(time.millisecond, time.clock.monotonic);
|
||||
};
|
||||
};
|
||||
let cownedrun: str = readfile(pidrecords[0]);
|
||||
let wownedrun: str = readfile(pidrecords[1]);
|
||||
let cownedmarker: str = strings.concat(cownedrun, "/observer-marker");
|
||||
let wownedmarker: str = strings.concat(wownedrun, "/observer-marker");
|
||||
let cownedvalid: bool = cownedrun.len > 12
|
||||
&& strings.hasprefix(cownedrun, "/tmp/ww_run_");
|
||||
let wownedvalid: bool = wownedrun.len > 12
|
||||
&& strings.hasprefix(wownedrun, "/tmp/ww_run_");
|
||||
let parallelobserved: bool = cp.result.errno == 0
|
||||
&& cp.result.cleanuperrno == 0
|
||||
&& cp.result.termination == exec.termination.EXIT && cp.result.code == 1
|
||||
&& wp.result.errno == 0 && wp.result.cleanuperrno == 0
|
||||
&& wp.result.termination == exec.termination.EXIT && wp.result.code == 1
|
||||
&& readfile(cc.stdoutpath).len == 0 && readfile(wc.stdoutpath).len == 0
|
||||
&& same(readfile(cc.stderrpath), strings.concat(ordinary,
|
||||
" is a directory, should be a WW file\n"))
|
||||
&& same(readfile(wc.stderrpath), strings.concat(ordinarylink,
|
||||
" is a directory, should be a WW file\n"))
|
||||
&& cownedvalid && wownedvalid
|
||||
&& os.exists(cownedrun) && os.exists(wownedrun)
|
||||
&& same(readfile(cownedmarker), "observer-owned\n")
|
||||
&& same(readfile(wownedmarker), "observer-owned\n")
|
||||
&& readfile(ctraces[0]).len == 0 && readfile(atraces[0]).len == 0
|
||||
&& readfile(ltraces[0]).len == 0 && readfile(ctraces[1]).len == 0
|
||||
&& readfile(atraces[1]).len == 0 && readfile(ltraces[1]).len == 0
|
||||
&& same(sourcebefore, treesnapshot(source))
|
||||
&& same(readfile(prior), "prior-public-bytes\n");
|
||||
let cmarkerclean: i32 = -1;
|
||||
let cdirclean: i32 = -1;
|
||||
let wmarkerclean: i32 = -1;
|
||||
let wdirclean: i32 = -1;
|
||||
if (cownedvalid) {
|
||||
cmarkerclean = os.remove(cownedmarker);
|
||||
cdirclean = os.rmdir(cownedrun);
|
||||
};
|
||||
if (wownedvalid) {
|
||||
wmarkerclean = os.remove(wownedmarker);
|
||||
wdirclean = os.rmdir(wownedrun);
|
||||
};
|
||||
assert(parallelobserved && cmarkerclean == 0 && cdirclean == 0
|
||||
&& wmarkerclean == 0 && wdirclean == 0
|
||||
&& !os.exists(cownedrun) && !os.exists(wownedrun));
|
||||
|
||||
let cbuildbytes: str = "";
|
||||
let cbuildartifacts: str = "";
|
||||
let cteststdout: str = "";
|
||||
let ctestbinary: str = "";
|
||||
si = 0;
|
||||
for (si < stages.len) {
|
||||
let out: commandout;
|
||||
let trailing: str = strings.concat(ordinary, "/");
|
||||
let trailingav: []str = [driverwrapper, "run", "-I", include, trailing];
|
||||
runcommandenv(root, strings.concat("run-trailing-", tags[si]), trailingav,
|
||||
environments[si], (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 37);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||
&& !os.exists(readfile(pidrecords[si])));
|
||||
let nonwwav: []str = [driverwrapper, "run", nonsuffixlink];
|
||||
runcommandenv(root, strings.concat("run-non-ww-", tags[si]), nonwwav,
|
||||
environments[si], (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 43);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||
&& !os.exists(readfile(pidrecords[si])));
|
||||
let regularav: []str = [driverwrapper, "run", regular];
|
||||
runcommandenv(root, strings.concat("run-regular-", tags[si]), regularav,
|
||||
environments[si], (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 39);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||
&& !os.exists(readfile(pidrecords[si])));
|
||||
let logicalav: []str = [driverwrapper, "run", "-I", include,
|
||||
"domain.logical"];
|
||||
runcommandenv(root, strings.concat("run-logical-", tags[si]), logicalav,
|
||||
environments[si], (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 41);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||
&& !os.exists(readfile(pidrecords[si])));
|
||||
|
||||
let buildwork: str = strings.concat(root, "/build-work-", tags[si]);
|
||||
let buildout: str = strings.concat(root, "/build-output-", tags[si]);
|
||||
mkdirall(buildwork);
|
||||
let buildav: []str = [driverwrapper, "build", "-w", buildwork,
|
||||
"-I", include, "-o", buildout, ordinarylink];
|
||||
runcommandenv(root, strings.concat("build-same-spelling-", tags[si]),
|
||||
buildav, environments[si],
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0 && os.exists(buildout));
|
||||
let buildbytes: str = strings.dup(readfile(buildout));
|
||||
let buildartifacts: str = strings.dup(artifacttreesnapshot(buildwork));
|
||||
if (si == 0) {
|
||||
cbuildbytes = strings.dup(buildbytes);
|
||||
cbuildartifacts = strings.dup(buildartifacts);
|
||||
} else {
|
||||
assert(same(cbuildbytes, buildbytes)
|
||||
&& same(cbuildartifacts, buildartifacts));
|
||||
};
|
||||
let builtav: []str = [buildout];
|
||||
runcommand(root, strings.concat("built-control-", tags[si]), builtav,
|
||||
time.second, &out);
|
||||
expectexit(&out, 37);
|
||||
|
||||
let testwork: str = strings.concat(root, "/test-work-", tags[si]);
|
||||
mkdirall(testwork);
|
||||
let testav: []str = [driverwrapper, "test", "-w", testwork, rawtest];
|
||||
runcommandenv(root, strings.concat("raw-test-control-", tags[si]), testav,
|
||||
environments[si], (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stderr.len == 0 && has(out.stdout, "raw_control ... ok\n"));
|
||||
if (si == 0) { cteststdout = strings.dup(out.stdout); }
|
||||
else { assert(same(cteststdout, out.stdout)); };
|
||||
let testbinary: str = strings.concat(root, "/test-binary-", tags[si]);
|
||||
let compileav: []str = [driverwrapper, "test", "-c", "-o",
|
||||
testbinary, rawtest];
|
||||
runcommandenv(root, strings.concat("raw-test-compile-", tags[si]),
|
||||
compileav, environments[si],
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0 && os.exists(testbinary));
|
||||
if (si == 0) { ctestbinary = strings.dup(readfile(testbinary)); }
|
||||
else { assert(same(ctestbinary, readfile(testbinary))); };
|
||||
|
||||
let assemblywork: str = strings.concat(root, "/assembly-work-", tags[si]);
|
||||
let assemblyout: str = strings.concat(root, "/assembly-output-", tags[si]);
|
||||
mkdirall(assemblywork);
|
||||
rewritefile(ctraces[si], ""); rewritefile(atraces[si], "");
|
||||
rewritefile(ltraces[si], "");
|
||||
let assemblyav: []str = [driverwrapper, "test", "-S", "-w",
|
||||
assemblywork, "-o", assemblyout, ordinarylink];
|
||||
runcommandenv(root, strings.concat("directory-test-assembly-", tags[si]),
|
||||
assemblyav, environments[si],
|
||||
(60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 2);
|
||||
assert(out.stdout.len == 0
|
||||
&& same(out.stderr, "ww test: -S needs a single test file\n")
|
||||
&& !os.exists(assemblyout) && directoryisempty(assemblywork)
|
||||
&& readfile(ctraces[si]).len == 0
|
||||
&& readfile(atraces[si]).len == 0
|
||||
&& readfile(ltraces[si]).len == 0);
|
||||
si += 1;
|
||||
};
|
||||
assert(!directoryhasnew(root) && !directoryhasfragment(root, ".wwtxn.")
|
||||
&& !directoryhasfragment(root, ".install")
|
||||
&& !directoryhasfragment(root, ".capture")
|
||||
&& !directoryhasfragment(root, ".result")
|
||||
&& !directoryhasfragment(root, ".request"));
|
||||
clean(root);
|
||||
};
|
||||
|
||||
// A source file has one contiguous import section immediately after its
|
||||
// package clause. The parser may continue after a late import for recovery,
|
||||
// but the imports-only loader and the full compiler parser both reject it.
|
||||
|
||||
Reference in New Issue
Block a user