From 763bbc8f25d766b98b6407bdc2fed5f98112ac3f Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 14 Aug 2026 19:02:49 +0900 Subject: [PATCH] test: prove package initialization semantics --- Makefile | 11 +- docs/build-system.md | 375 ++++- docs/spec.md | 59 +- docs/test-system-v2.md | 52 +- lib/bufio/stream_test.ww | 30 +- lib/math/random/random_test.ww | 16 +- test/misc/qualast_test.ww | 33 +- test/package/package_test.ww | 522 +++--- test/sep/limitexec.c | 36 + test/sep/sepinit_test.ww | 1421 +++++++++++++++++ test/wcc/200_parse.c | 2 + test/wcc/300_check.c | 9 + .../data/r681_slice_field_ptr_write/case.ww | 4 +- .../wcc/data/r778_branched_bufio_wrap/case.ww | 2 +- .../data/r778_isbuffered_v_boundary/case.ww | 2 +- .../r778_isbuffered_v_discriminate/case.ww | 2 +- test/wcc/data/r778_stream_read_unread/case.ww | 2 +- test/wcc/data/r778_stream_setflush/case.ww | 2 +- test/wcc/data/r778_stream_unread/case.ww | 2 +- test/wcc/data/r778_stream_write_flush/case.ww | 2 +- .../wcc/data/r989_libprecond_u32n_one/case.ww | 2 +- .../data/r989_libprecond_u32n_valid/case.ww | 2 +- .../wcc/data/r989_libprecond_u64n_one/case.ww | 2 +- .../data/r989_libprecond_u64n_valid/case.ww | 2 +- 24 files changed, 2243 insertions(+), 349 deletions(-) create mode 100644 test/sep/limitexec.c create mode 100644 test/sep/sepinit_test.ww diff --git a/Makefile b/Makefile index fe59689e..0c5753cf 100644 --- a/Makefile +++ b/Makefile @@ -349,7 +349,7 @@ $(PACKAGE_TEST_BIN): $(PACKAGE_TEST_SRC) $(WWTEST_BIN) \ test-package: $(PACKAGE_TEST_BIN) $(WWTEST_BIN) $(BIN)/ww $(BIN)/ww_ww \ $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6a_ww \ $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a - @WW_PACKAGE_REPO=$(CURDIR) $(CURDIR)/$(PACKAGE_TEST_BIN) + @WW_PACKAGE_REPO=$(CURDIR) $(CURDIR)/$(PACKAGE_TEST_BIN) -timeout-ms=120000 # ---- directories ------------------------------------------------------- $(BIN) $(LIB) $(OBJ)/wcc $(OBJ)/ww $(OBJ)/wwdump $(OBJ)/w6c $(OBJ)/w6a $(OBJ)/w6l $(OBJ)/rt: @@ -384,10 +384,17 @@ BYTEID_WW_TARGETS = $(BYTEID_WW_TESTS:%=wwtest/%) # export arguments, link rejects), NOT byteid suites: they run under # test-compiler beside the surviving residual carriers. SEP_WW_TESTS = test/sep/sepbuild_test.ww test/sep/sepimport_test.ww \ - test/sep/seplink_test.ww test/sep/sepscratch_test.ww \ + test/sep/seplink_test.ww test/sep/sepinit_test.ww \ + test/sep/sepscratch_test.ww \ test/sep/septest_test.ww test/sep/m3sep_test.ww \ test/sep/localbuild_test.ww test/sep/importdir_test.ww SEP_WW_TARGETS = $(SEP_WW_TESTS:%=wwtest/%) +SEP_LIMITEXEC_BIN = $(BIN)/sep-limitexec + +$(SEP_LIMITEXEC_BIN): test/sep/limitexec.c | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + +wwtest/test/sep/sepinit_test.ww: $(SEP_LIMITEXEC_BIN) # Ww-native lib env/OS arranger observers: the env contracts of the # lib/os and lib/dirs suites (getenv cohorts, XDG cohorts, the dirs # too-long abort) need per-row env construction ww cannot self-arrange diff --git a/docs/build-system.md b/docs/build-system.md index 9401eb95..9cf2652f 100644 --- a/docs/build-system.md +++ b/docs/build-system.md @@ -3078,11 +3078,13 @@ binding is identical before reusing its compile action. A different binding is a deterministic package-resolution failure for the roots that reach it, rather than a first-root-wins build. -A production action failure blocks exactly the roots that reach it. A -root-local compile or link failure does not suppress a successfully built -sibling product: the command records completion per product, and the -coordinator can run and report successful siblings while attributing each -missing product as a build failure. The single union build and completed test +A production action failure is attributed to exactly the roots that reach it, +but the complete command is one publication transaction. The driver may +continue enough of the already validated plan to retain deterministic action +and product diagnostics, but one failed producer, linker, status stage, or +commit suppresses every new action voucher, tool record, product, and status +from that request. The coordinator therefore runs no sibling test binary from +a rejected union build. After one successful union build, the completed test products share the coordinator's existing `-j` process bound; captured output is still emitted only in byte-sorted directory/package order. @@ -3153,11 +3155,11 @@ compiler-owned dispatcher with empty output is reported as `[no tests]`. Persistent workdirs keep a global driver/compiler/assembler/stamp identity and per-action committed units. When that global identity is stale, the command -first removes every old `.unit.ww` voucher while leaving artifacts recoverable. -It can then commit the new identity even if one root fails: successful actions -have freshly committed units, whereas failed or no-longer-requested actions -cannot be reused. A retry therefore recompiles the failed action without -discarding unchanged canonical packages built for independent products. +forces every requested action cold while preserving the complete old generation +as rollback state. New tool records, artifacts, units, products, statuses, and +the new stamp become visible only in the request-wide commit after every product +has staged successfully. A rejected request leaves the old generation +byte-identical, and a retry cannot reuse any uncommitted work from the rejection. Warm reuse compares the staged owner-only unit, committed artifacts, and the actual bytes of each direct dependency export. A changed shared dependency is @@ -4176,12 +4178,13 @@ succeeds; a rejected request therefore leaves neither directory behind. Resolution, identity collision checks, contextual legality, dependency-failure propagation, cycles, command kind, publication paths, and action closures all -finish before cold scratch acquisition, product status removal, stale-voucher -invalidation, tool identity records, producer execution, publication, or -runtime execution. A request whose every product is already invalid returns -without acquiring scratch; independent valid sibling products retain the -existing isolated-build behavior. A forbidden warm request leaves committed -vouchers, status files, tool records, and products byte-unchanged. +finish before cold scratch acquisition, tool identity staging, producer +execution, publication, or runtime execution. A request whose every product is +already invalid returns without acquiring scratch. If a later producer or +product fails, otherwise viable siblings may have been produced only into the +same transaction; none is committed or executed. A forbidden or otherwise +rejected warm request leaves committed vouchers, status files, tool records, +and products byte-unchanged. This is only manifest-free local source-tree behavior. It does not implement Go modules, module vendor mode, `go.mod`, `vendor/modules.txt`, importcfg, build @@ -4557,20 +4560,15 @@ qualifier or canonical dependency. A package-scope declaration collides with an equal import binding from any contributing file, matching Go's reconciliation of package and file scopes. Conflicting production package clauses remain a loader-owned deterministic error before producers. Compiler-owned scope or use -errors may invoke the compiler, but the driver removes that action's staged -`.new` unit/export/assembly/object/archive files instead of renaming them. No -completion/status marker is written, and no publication occurs after a failed -compile. If an artifact rename itself fails after an earlier rename has -published part of a new generation, the driver removes every committed unit -voucher as well as the remaining staged files. Before the first artifact -rename, it must successfully remove the workdir identity stamp; failure is a -pre-commit rejection that leaves the prior artifact generation untouched. The -stamp remains absent after any partial commit, and later actions in that -invocation cannot take the freshness shortcut. Each non-reusable action also -loses its old unit voucher before its producers run. The next request must -therefore rebuild the incomplete action set and reconsider direct importers; a -partly replaced `.wwi` or a failed importer can never make an old importer -voucher look fresh. +errors may invoke the compiler, but every action artifact remains staged under +an adjacent `.new` name. No completion/status marker is written and no product +is published after a failed compile. Once every action and product has staged, +the driver preserves each existing destination under a request-owned backup, +installs the complete new generation, and rolls all installed destinations back +if any installation fails. Unit vouchers and the global stamp are transaction +members rather than early invalidation markers. Thus a rejected or interrupted +request leaves the previous committed generation byte-identical and removes all +remaining stages; a mixed `.wwi`/object/archive generation is never reusable. Package kind follows the declaration. `package main`, not a path component, marks a command. A path ending in `main` remains importable when it declares a @@ -4690,8 +4688,8 @@ The behavior follows pinned official Go 1.26.5 source at commit [`cmd/go/internal/test/test.go`, lines 1133–1226](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1133-L1226)). Section 11.19 completes ordinary explicit aliases while retaining dotted, -unquoted paths. Grouped, quoted, dot, and blank imports remain deliberately -unimplemented. +unquoted paths. Section 11.20 adds blank side-effect imports without adding a +name. Grouped, quoted, and dot imports remain deliberately unimplemented. The focused native observer `declared_name_identity_and_file_import_scope` generates every tree @@ -4820,19 +4818,21 @@ Go's leaf comparison: a binding equal to the path leaf says `imported and not used`; any other binding, including an unusual default declared name, says `imported as and not used`. -`_` is rejected at parse time as `blank import alias _ is not implemented`. -Grouped imports, quoted paths, dot imports, and blank imports are deliberately -deferred rather than partially implemented. +`_` selects the no-binding side-effect mode completed in section 11.20. It is +never an ordinary effective qualifier and therefore neither collides with +another `_` occurrence nor receives an unused diagnostic. Grouped imports, +quoted paths, and dot imports remain deliberately deferred. #### Graph, export, artifact, and persistence identity The imports-only parser and full parser share one import-spec routine and retain -alias and dotted path separately. Both drivers sort and resolve occurrences by -the dotted spelling, perform contextual internal and nearest-first vendor checks -at every real site, and intern the expanded canonical action. `sepbind` and -`--import-map` continue to mean source dotted spelling to expanded vendor -identity; neither contains the alias. Repeated occurrences remain separate -scope facts but form one sorted canonical edge/action. +ordinary alias, blank mode, and dotted path separately. Both drivers sort and +resolve occurrences by the dotted spelling, perform contextual internal and +nearest-first vendor checks at every real site, and intern the expanded +canonical action. `sepbind` and `--import-map` continue to mean source dotted +spelling to expanded vendor identity; neither contains the alias or `_`. +Repeated occurrences remain separate file facts but form one sorted canonical +edge/action. The compiler independently reads the direct dependency's declared name. It installs the explicit alias when present or that declared name otherwise, while @@ -4877,7 +4877,7 @@ unchanged semantic bytes stop reverse rebuilding. Parser, identity, alias, declaration, binding, and scope failures occur before publication. Compiler-owned failures may start the compiler, but staged unit, interface, assembly, object, archive, voucher, stamp, status, and product state -is discarded or invalidated under section 11.18's partial-commit protocol. +is discarded under section 11.20's request transaction. #### Stage and observer ownership @@ -4896,11 +4896,300 @@ artifact, `.wwi`, and rejection-state matrix. The existing `declared_name_identity_and_file_import_scope` observer owns default-versus- stable-alias invalidation and propagation. Existing directory, command-test, vendor, recursive, exact-argv, link-closure, persistent-workdir, and -partial-commit observers retain their broader variant and action ownership. +request-transaction observers retain their broader variant and action ownership. Every applicable proof runs Cstage and WWstage from independent cold roots and compares diagnostics, normalized tool arguments, artifacts, binaries, and runtime output. +### 11.20 Implemented blank side-effect imports and package initialization + +WW now completes the third file-local import mode and the package +initialization path needed to give it meaning: + +```ww +import acme.codec; // qualifier from the dependency declaration +import stable acme.codec; // explicit file-local qualifier stable +import _ acme.codec; // no qualifier; initialization side effect only +``` + +A blank occurrence retains its dotted source spelling, owning file, line, +column, source section, and resolved canonical action, but creates no source +binding and is exempt from unused-import checking. It exposes neither +`wire.Name`, `codec.Name`, nor bare `Name`. Resolution always uses +`acme.codec`, never `_`: missing-package, self-import, cycle, final-`internal`, +nearest-first vendor, vendor-spelling, and imported-`main` checks run at every +blank site exactly as they do for a named occurrence. A vendored blank import +therefore keeps a source-to-expanded-identity `--import-map`; `_` enters no +map, action key, task symbol, artifact, voucher, stamp, variant, or link input. + +Repeated blank imports of one path, in one file or several files, are valid. +A blank occurrence may coexist with the default binding or any explicit alias +of the same path. Every named occurrence remains independently subject to its +ordinary duplicate-binding and usage rules. All occurrences survive in the +owner unit and per-site validation data, while their package-wide union still +forms one sorted canonical dependency edge, one package action, and one direct +compiler export input. + +#### Pinned Go evidence + +The reference is official Go 1.26.5 at commit +`c19862e5f8415b4f24b189d065ed739517c548ba`: + +- `ast.ImportSpec` keeps an optional local `Name`, including `_`, independent + from `Path`, and the parser reads that optional name before the path + ([`go/ast/ast.go`, lines 908–915 and 939–955](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/ast/ast.go#L908-L955), + [`go/parser/parser.go`, lines 2509–2546](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/parser/parser.go#L2509-L2546)). + Function parsing treats `init` as an ordinary syntactic function name; its + special meaning is assigned later + ([`go/parser/parser.go`, lines 2784–2842](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/parser/parser.go#L2784-L2842)). +- `go/types` creates an import object for each occurrence, inserts no binding + for `_`, exempts `_` from unused checking, and keeps a valid `init` function + out of package scope while checking its body and signature + ([`go/types/decl.go`, lines 16–33](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/types/decl.go#L16-L33), + [`go/types/resolver.go`, lines 103–124](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/types/resolver.go#L103-L124), + [lines 279–350](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/types/resolver.go#L279-L350), + [lines 400–433](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/types/resolver.go#L400-L433), + [lines 701–716](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/types/resolver.go#L701-L716)). + The production compiler equivalent is + [`cmd/compile/internal/types2/resolver.go`, lines 90–111](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L90-L111), + [lines 264–335](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L264-L335), + [lines 416–444](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L416-L444) + and + [lines 706–721](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/resolver.go#L706-L721). +- Official import testdata accepts repeated blanks and blank plus default or + explicit named imports of the same path, while diagnosing only the unused + named occurrences + ([`internal/types/testdata/check/importdecl0/importdecl0a.go`, lines 9–31 and 43–52](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/importdecl0/importdecl0a.go#L9-L52)). + Multiple valid init declarations, invalid signatures, and direct + invisibility are pinned by + [`test/init.go`, lines 12–18](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/init.go#L12-L18), + [`test/noinit.go`, lines 315–326](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/noinit.go#L315-L326), + [`internal/types/testdata/check/decls0.go`, lines 40–46](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/decls0.go#L40-L46), + and + [`decls1.go`, lines 141–146](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/decls1.go#L141-L146). +- After constants, variable initialization chooses the declaration with the + fewest unresolved variable dependencies and uses source order as its tie; + references through functions are dependencies. Cycle reporting is + deterministic, and graph removal continues so later independent cycles are + also diagnosed + ([`go/types/initorder.go`, lines 19–185 and 226–335](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/types/initorder.go#L19-L335), + [`cmd/compile/internal/types2/initorder.go`, lines 16–182 and 223–332](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/types2/initorder.go#L16-L332)). + Source ties and calls are exercised by + [`go/types/api_test.go`, lines 1408–1619](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/types/api_test.go#L1408-L1619), + with cycle ordering in + [`internal/types/testdata/check/init0.go`, lines 22–89](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/internal/types/testdata/check/init0.go#L22-L89). +- The compiler first attempts static initialization and serializes remaining + ordered assignments into a compiler-generated init function. It then emits + one package task whose dependency tasks are separate `R_INITORDER` + relocations and whose payload is an ordered function-pointer list + ([`cmd/compile/internal/staticinit/sched.go`, lines 34–145](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/staticinit/sched.go#L34-L145), + [`cmd/compile/internal/noder/writer.go`, lines 2717–2773](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/noder/writer.go#L2717-L2773), + [`cmd/compile/internal/noder/reader.go`, lines 3288–3345](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/noder/reader.go#L3288-L3345), + [lines 3389–3416](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/noder/reader.go#L3389-L3416), + [`cmd/compile/internal/pkginit/init.go`, lines 20–145](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/pkginit/init.go#L20-L145)). + The requested historical `cmd/compile/internal/walk/init.go` path does not + exist at this tag; the noder, `staticinit`, and `pkginit` files above are the + active implementation. +- The linker schedules ready tasks by canonical task symbol and emits each + exactly once, and the runtime executes those tasks before user main + ([`cmd/link/internal/ld/inittask.go`, lines 19–39 and 104–180](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/inittask.go#L19-L180), + [`cmd/link/internal/ld/heap.go`, lines 56–99](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/link/internal/ld/heap.go#L56-L99), + [`runtime/proc.go`, lines 203–290](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/runtime/proc.go#L203-L290), + [lines 8049–8124](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/runtime/proc.go#L8049-L8124)). + The `issue31636` packages exercise imports written out of order, while the + linker queue above—not that fixture's stale comment—pins lexical ready-task + order + ([`test/fixedbugs/issue31636.dir/main.go`, lines 7–17](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/test/fixedbugs/issue31636.dir/main.go#L7-L17)). +- `go/build` specifies sorted directory presentation; its default path obtains + byte-sorted names and then classifies production, internal-test, and + external-test files/imports separately + ([`go/build/build.go`, lines 108–111 and 193–207](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L108-L207), + [`os/dir.go`, lines 109–125](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/os/dir.go#L109-L125), + [`go/build/build.go`, lines 948–1040](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L948-L1040), + [lines 1061–1063](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L1061-L1063), + [lines 1512–1518](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/go/build/build.go#L1512-L1518)). + The test loader constructs and rewrites canonical internal, external, and + generated-main variants before work actions. In the internal variant it + presents the already-sorted production category first and the already-sorted + internal-test category second, rather than globally sorting the merge; its + effective-test-cycle rule is pinned by + [`cmd/go/internal/load/test.go`, lines 85–101, 175–293, and 421–550](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/test.go#L85-L550) + and + [`cmd/go/testdata/script/list_test_cycle.txt`, lines 1–20](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/testdata/script/list_test_cycle.txt#L1-L20). + Compile/link action ownership remains canonical in + [`cmd/go/internal/work/action.go`, lines 437–455, 628–708, and 919–1068](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/action.go#L437-L1068), + while `go test` rejects a bad test graph before creating those actions + ([`cmd/go/internal/test/test.go`, lines 1185–1226](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/test/test.go#L1185-L1226)). +- Unified export data separates public objects from private bodies/init data; + a blank import declaration serializes no declaration, and import readers + consume the semantic package export independently of that local spelling + ([`cmd/compile/internal/noder/unified.go`, lines 314–353](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/noder/unified.go#L314-L353), + [lines 463–570](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/noder/unified.go#L463-L570), + [`cmd/compile/internal/noder/writer.go`, lines 2742–2749](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/noder/writer.go#L2742-L2749), + [`cmd/compile/internal/importer/ureader.go`, lines 41–62](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/compile/internal/importer/ureader.go#L41-L62)). + Together with the resolver's omission from package scope, that public/private + split is why another package cannot select `pkg.init`. +- Loader import checks, build-root action ownership, direct compiler import + inputs, and transitive linker inputs remain per canonical package rather + than per local import name + ([`cmd/go/internal/load/pkg.go`, lines 787–805](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/load/pkg.go#L787-L805), + [`cmd/go/internal/work/build.go`, lines 519–558](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/build.go#L519-L558), + [`cmd/go/internal/work/exec.go`, lines 864–884](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/exec.go#L864-L884), + [lines 1592–1653](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/exec.go#L1592-L1653), + [`cmd/go/internal/work/gc.go`, lines 136–177](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/gc.go#L136-L177), + [lines 590–672](https://github.com/golang/go/blob/c19862e5f8415b4f24b189d065ed739517c548ba/src/cmd/go/internal/work/gc.go#L590-L672)). + +#### Declaration and package-variable semantics + +`fn init() void = { ... };` is a special initializer declaration. It must have +a body, no parameters, no result, no `export`, and no attribute. Multiple valid +declarations are accepted in one file and across files. No declaration of +another kind may claim `init`; a rejected non-function form is not inserted +into scope, so later references still fail lookup. A valid init retains its +file, source section, and position, but is never installed as a callable +declaration: `init()` and `pkg.init` fail lookup, it is absent from `.wwi`, and +it cannot affect canonical package identity. + +Mutable package-level `let` is WW's Go-variable analogue. An initializer that +the existing static-data emitter can represent remains static. Every other +otherwise valid expression—including calls, allocation, and supported nested +array, struct, tuple, and slice values—is evaluated once by a hidden +package-owned helper and assigned to zero-backed package storage. Runtime slice +literals use canonical writable backing storage rather than escaping a helper +stack. `def` and `const` retain their existing compile-time/static rules and are +not broadened by this implementation. + +The checker orders all initialized mutable lets by their checked declaration +dependencies. References through package functions are transparent edges. +Among remaining declarations, the one with the fewest unresolved dependencies +wins and original declaration order breaks ties. Files arrive byte-sorted +inside each loader category; a combined internal-test variant presents its +production category before its internal-test category, and declarations retain +source order inside each file. A cycle is +reported on the same deterministic walk as the pinned type checkers; removal +continues to expose later independent cycles, but any cycle suppresses all init +lowering and publication. Runtime variable assignments execute in that order, +then every special init function executes in owner-file/source order. + +Each semantic package action owns one hidden task symbol: + +```text +__ww..pkg.p..v.r.init +``` + +The reversible empty-owner form is likewise variant/role qualified. Neither a +declared package name, default qualifier, explicit alias, blank spelling, path +leaf, physical directory, request ordinal, nor output name contributes to this +symbol. Compiler argv supplies it with +`--package-init-symbol `. Only an executable command root or generated +test main additionally receives `--init-dispatch-symbol __ww..dispatch`, and +its compiler-generated entry calls that dispatcher before source `main` or the +generated test main body. + +#### Product graph, variants, artifacts, and persistence + +Before any producer, the driver forms the effective reachable graph for each +product, including internal-test replacement, and rejects a cycle introduced by +that replacement. It repeatedly chooses the byte-lexically smallest ready +canonical path, then variant and role, while blocking every importer on its +dependencies. The resulting root-owned dispatcher calls each effective package +task exactly once. Thus dependencies precede importers, a shared diamond task +runs once per product, independent ties ignore source import order and linker +argv order, and the root task completes before user main or tests. + +An ordinary library object/archive contains its hidden task but building the +library does not execute it. An executable or generated-main root archive has +two deterministic members, `pkg.o/` followed by `init.o/`; the second member is +the root-owned dispatcher. Dependency archives remain ordinary `pkg.o/` +archives. Both driver stages stream member bytes through the same bounded +transfer buffer instead of retaining archive-sized allocations. Existing +linker archive fixpoint extraction pulls the dispatcher and +all referenced package tasks without a new linker format or free-floating +artifact. Logical linker argv is still the canonical root archive followed by +the reachable archive closure and runtime/native inputs; no `.wwi`, alias, +blank spelling, or dispatcher sidecar appears. + +Production, production-plus-internal-test, external `_test`, test support, and +generated main retain separate action identities. An internal product replaces +the colocated production task with its combined variant and includes test-only +blank edges/init declarations exactly once. An external product initializes +ordinary production and its production dependencies before the external task. +Support is an ordinary dependency task; generated main owns only the final +dispatcher call and cannot duplicate a tested task. Test-file-only imports, +runtime lets, and init functions never enter production. A blank import cannot +bypass imported-`main` rejection, including through vendor expansion. + +`.wwi` contains neither blank-only spelling, init declarations/bodies, hidden +variable helpers, slice backing symbols, package tasks, nor dispatcher facts. +It continues to encode only semantic exported declarations and their canonical +reachable type/constant facts. Consequently an init-body-only edit rebuilds the +owning object/archive and relinks affected products, while unchanged `.wwi` +bytes prevent importer recompilation. Adding or removing a blank edge rebuilds +the owning source action and changes exactly the affected reachable dispatcher; +a dispatcher-only change rebuilds the root `init.o/` member/archive without +recompiling an unchanged root source object. Reverse propagation stops at the +first regenerated byte-identical semantic export. + +The owner source voucher remains `.unit.ww`; a linked product root also +owns `.init.unit.ww` for its dispatcher unit, `.init.s`, `.init.o`, and +two-member archive. Current persistent formats are build 17 and test 16. Warm +consumers select a dependency's staged `.wwi.new` or `.a.new` when that exact +action changed in the same request. All action artifacts, init artifacts, tool +identity copies, stamp, library/executable publications, and test statuses are +then one rollback-capable request transaction. No destination changes unless +every product stages successfully; a compiler, checker, init-order, +dispatcher, assembler, archiver, linker, allocation, status, or installation +failure removes remaining stages and restores the complete prior generation. +Before scratch acquisition or producer execution, `lstat`-style no-follow +checks reserve every action, tool, product, interface, status, and rollback +name; a dangling staging or backup symlink is an occupied structural conflict +and is never followed or removed. A committed dispatcher voucher must itself +be a regular file before it can authorize reuse. Compiler assembly and +interface bytes are first generated through anonymous files with checked full +writes and then published as their own rollback group. A non-regular compiler +destination is rejected before preservation, and installation—not cleanup of +a recoverable old backup—is the commit point. Cold rejection removes the exact +request-owned scratch tree. Stale init code, stale closure metadata, and mixed +committed generations therefore cannot be reused. + +#### Stage and observer ownership + +The shared syntax AST, C parser, and WW parser own blank/init facts and source +positions. Both imports-only driver scans resolve the dotted path and retain +per-site legality; neither treats `_` as an alias. The C and WW checkers own +special-init validation, invisibility, mutable-let dependency ordering, cycle +diagnostics, and runtime lowering. The interface writers omit initialization +implementation; the code generators emit static storage, runtime helpers, +package tasks, canonical slice backings, and the entry dispatcher call. The two +drivers own task identity, effective test graphs, global dispatcher ordering, +exact direct compiler inputs, archive membership, link closure, persistence, +and the request transaction. The assemblers consume the dynamically sized +canonical symbols. Both linkers are unchanged and use their existing iterative +archive extraction. `internal/wwpackage` and `cmd/wwtest` retain package/test +classification and execution coordination; they do not parse imports, invent +tasks, or call init manually. + +The focused native `test/sep/sepinit_test.ww` observer generates every source +tree temporarily and proves runtime let/init order, dependency chains, +diamonds, independent lexical ties, aggregate and allocation initialization, +multiple-cycle diagnostics, special-init rejection, test-variant isolation, +canonical task/dispatcher/archive bytes, init-only invalidation, and warm +invalid-init rollback across independent Cstage and WWstage roots. Direct and +recursive test legs compare variant dispatcher/archive bytes and observable +production-dependency order; exact task counts include support and generated +main. Rejection rows compare complete normalized diagnostics and prove cold +scratch absence. The observer also uses a repository-built `setrlimit` launcher +to find one shared bounded-memory ceiling at which both drivers fail before a +producer, and proves no-follow atomic rejection of dangling driver staging and +compiler rollback names, non-regular compiler destinations, and compiler +output-write failures. The extended +`explicit_import_alias_binding_modes` observer owns repeated blank/default/ +alias/file-scope combinations and no-binding/unused behavior. Existing +internal, vendor, imported-command, recursive, exact-argv, link-closure, +persistent-workdir, rejection-state, byte-identity, and bootstrap observers own +their unchanged broader boundaries. Grouped imports, quoted imports, and dot +imports remain deliberately unimplemented. + ## 12. Candidate architectures and hard-gate decision Five candidates were developed as coherent systems, not as feature bins. diff --git a/docs/spec.md b/docs/spec.md index 564bf9fd..436f177d 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -246,7 +246,8 @@ does not return (e.g. a call to `abort`). ``` SourceFile = PackageClause { ImportDecl } { TopDecl } . PackageClause = "package" ident ";" . -ImportDecl = "import" ( ImportPath | ident ImportPath ) ";" . +ImportDecl = "import" ( ImportPath | ImportName ImportPath ) ";" . +ImportName = ident . ImportPath = ident { "." ident } . ``` @@ -263,8 +264,18 @@ ImportPath = ident { "." ident } . Neither form exposes an imported declaration as a bare `Name`; ordinary unqualified lookup remains limited to lexical, builtin, and same-package declarations. +- `import _ acme.codec;` is a blank side-effect import. The lone `_` creates no + qualifier, exposes no bare declaration, and is never diagnosed as unused. + It is nevertheless a real import occurrence: resolution and all missing, + self-import, cycle, `internal`, vendor, and imported-command checks use the + dotted path, and the dependency participates in executable and test package + initialization. Repeated blank occurrences and blank plus default/explicit + named occurrences of one path are valid; every named occurrence remains + independently subject to duplicate-binding and unused checks. The package dependency graph is the sorted, deduplicated union of the real - imports in all eligible files. Self-import is rejected. + imports in all eligible files. Occurrences retain their owning file and + position, but equal canonical targets create one graph edge/package action. + Self-import is rejected. - An executable package is one declared `package main` and containing a `fn main`; path and directory spelling do not classify commands. An ordinary import of a package declared `main` is rejected, except for the toolchain's @@ -272,8 +283,8 @@ ImportPath = ident { "." ident } . - Only names marked `export` (§5) are visible across module boundaries. Import paths remain unquoted and dotted. Grouped imports, quoted import paths, -dot imports, and blank imports are not implemented. In particular, `_` is -rejected as an alias rather than being treated as a blank import. +and dot imports are not implemented. `_` is reserved here for the blank form; +it is not an ordinary alias. --- @@ -298,23 +309,42 @@ The `=` between signature and body is required. A bodiless fn (`;` terminator) declares an external symbol (C FFI); the bare `...` C-style variadic is legal only on a bodiless declaration (§6.3). +At package scope, exactly `fn init() void = Block;` declares a special package +initializer. It must have a body, no parameters, no result, no `export`, and no +attribute. Multiple init declarations are permitted and retain owner-file and +source order. `init` is not inserted into ordinary package scope: it cannot be +called as `init()`, selected as `pkg.init`, exported, or used by another kind +of declaration. Package-variable initialization completes before these +functions run. + ### 5.2 `let` ``` LetDecl = "let" ident ":" Type [ "=" Expr ] ";" . ``` -A mutable binding. Without an initialiser the storage is uninitialised. +A mutable binding. Without an initialiser, package-level storage is +zero-backed; an uninitialized function-local binding retains the existing +uninitialized-local rule. A `static` qualifier (`static let …`) gives function-local storage static lifetime. +For a package-level `let` with an initializer, values representable by the +static-data emitter are installed statically. Every other otherwise valid +initializer is evaluated exactly once at runtime. Checked references through +package functions contribute variable-dependency edges; dependencies precede +dependents, and source declaration order breaks ready ties. Initialization +cycles are errors. Runtime package lets execute after imported package tasks and +before the package's init functions. `const` and `def` are not broadened by +this runtime path. + ### 5.3 `const` -`const` introduces an immutable binding of the same shape as `let`. - -TODO(spec): pin the exact `const` vs `def` boundary against `cmd/wcc` -(both keywords exist; this text takes `def` = compile-time constant -[Hare's `def`] and `const` = immutable runtime binding — confirm). +`const` introduces an immutable storage binding of the same declaration shape +as `let`; assignment to it is rejected. At package scope its initializer stays +within the existing static-data forms. A `const` is not entered into the +runtime package-variable schedule described above; use mutable `let` when an +otherwise valid initializer requires runtime evaluation. ### 5.4 `def` @@ -561,6 +591,15 @@ the program. Two placements, following the Go `foo` / `foo_test` model: }; ``` +Production, production-plus-white-box-test, external black-box-test, test +support, and generated test main are distinct package actions. Before a test +function runs, the generated test product initializes its exact reachable +variant graph dependency-first and once per canonical action. A white-box +variant replaces the colocated production task rather than initializing both; +an external variant depends on ordinary production. Imports, runtime lets, and +init declarations found only in `*_test.ww` never enter an ordinary production +build. + --- ## 11. Concurrency (reserved) diff --git a/docs/test-system-v2.md b/docs/test-system-v2.md index b5bf8e06..4159f1c3 100644 --- a/docs/test-system-v2.md +++ b/docs/test-system-v2.md @@ -24,7 +24,7 @@ categories out of the ordinary developer target. | --- | --- | | Arena, lexer, parser, checker, module-decl parse, narrow codegen | Six in-process C unit binaries | | Compile success/rejection, stage-routed diagnostics, and runtime exit | `test/wcc/data/*/case.ww`, executed by `wwfixture` | -| Package semantics | `test/package` and the native package-test coordinator | +| Package semantics | `test/package`, `test/sep/sepinit_test.ww`, and the native package-test coordinator | | Language behavior | `test/lang/*_test.ww` through the language `@test` runtime | | Library behavior | The package coordinator's `lib/...` tree walk (`ww test -j N lib/...`) | | Standalone library-source compilation | Three import-free real source paths named by `LIBRARY_STANDALONE_SOURCES`, compiled directly by both frontends | @@ -236,25 +236,41 @@ qualifier remain distinct. The alias matrix covers default and explicit-only qualification, bare function/type/def/const/variable rejection with exact unused-before-undefined diagnostics, same-file duplicate bindings, accepted same-path imports under distinct bindings, alias reuse across files, sibling -scope isolation, package-declaration collisions, unused aliases, and explicit -blank-alias rejection. It also pins canonical `.wwi` spelling, archive/action -ownership, stage-equal diagnostics and binaries, and clean rejection state. +scope isolation, package-declaration collisions, unused aliases, blank +side-effect imports, repeated blank/default/explicit combinations, and blank +no-binding/no-unused behavior. It also pins canonical `.wwi` spelling, +archive/action ownership, one canonical direct edge for repeated occurrences, +stage-equal diagnostics and binaries, and clean rejection state. The declared-name observer proves imports are file-scoped while dependency edges are the package-wide sorted union; command and production/internal/external/generated-main variants retain canonical action ownership; vendor expansion changes identity but not the effective qualifier; compiler argv contains only direct `.wwi` inputs; and its named rejected actions leave neither committed nor staged action artifacts or a published -binary. It also forces a staged multi-artifact commit to fail after the -interface rename, then proves that both stages invalidate old unit vouchers and -reconsider the importer rather than accepting a mixed warm generation. A -separate injected stamp-removal failure proves the pre-commit gate leaves all -previously committed artifacts byte-identical. +binary. It also forces a request transaction to reject after dependency work +has staged, then proves that both stages restore every prior artifact, unit, +tool record, stamp, and product rather than accepting a mixed warm generation. Together with the existing directory, recursive, vendor, exact-argv, command, and persistent-workdir observers, the package suite proves archive-only link argv and exact warm/rejection-state behavior without duplicating those broader mechanisms in this observer. +`test/sep/sepinit_test.ww` is the single focused package-initialization owner. +It generates all source trees temporarily and runs independent cold/persistent +Cstage and WWstage legs. Its matrix covers blank-only reachability; dependency, +diamond, and independent ready-task order; runtime call/allocation and aggregate +package lets; multiple init declarations and invalid forms; direct/qualified +init invisibility; initialization cycles and diagnostic order; command, +internal-test, external-test, support, and generated-main ownership; canonical +task and dispatcher symbols; deterministic one- and two-member archives; +`.wwi` exclusion; byte-identical artifacts/binaries; init-only invalidation; +warm invalid-init rollback; direct/recursive variant equivalence; exact +production, support, and generated-main task ordering; complete normalized +rejection diagnostics; dangling staging/rollback no-follow rejection; and +checked compiler-output failure/non-regular-destination rollback; and +bounded-memory allocation-failure parity under one shared ceiling supplied by +the repository-built `sep-limitexec` helper. + `ww build`, an explicit single-file `ww test -o `, and each successful directory-package `ww test -c` build publish `.sepwork` as a caller-owned artifact directory. The driver acquires it with one fresh `mkdir` and refuses @@ -279,13 +295,17 @@ no hashes, every decision reproducible with `cmp` against plain files. The driver identity covers graph construction, owner-unit composition, direct-export argument construction, archive creation, and commit algorithms that neither unit bytes nor compiler identity can name. -Recompiled artifacts land at staged `.new` names and commit by rename with the -unit renamed last, so an interrupted build forces a recompile rather than a -false reuse; the link always reruns. One workdir serves one invocation at a -time and may hold the complete command-global action universe for all selected -roots and isolated variants; semantic identity, rather than request shape, -controls reuse. Both driver stages implement the identical contract. This is -build staleness in the Make/mk/Go sense, not a result cache: +Recompiled artifacts, dispatcher artifacts, products, statuses, tool copies, +and the stamp land at staged `.new` names. After every requested product stages +successfully, one rollback-capable transaction installs the whole generation; +any producer or installation failure preserves the prior committed bytes and +removes remaining stages. An interrupted or rejected request therefore cannot +create false reuse or a mixed generation; a successful executable link still +reruns when required. One workdir serves one invocation at a time and may hold +the complete command-global action universe for all selected roots and isolated +variants; semantic identity, rather than request shape, controls reuse. Both +driver stages implement the identical contract. This is build staleness in the +Make/mk/Go sense, not a result cache: tests always run, and the byte-identity and bootstrap gates keep building on fresh scratch. `make clean` reclaims every workdir under `out/`. diff --git a/lib/bufio/stream_test.ww b/lib/bufio/stream_test.ww index 79483550..192cbac7 100644 --- a/lib/bufio/stream_test.ww +++ b/lib/bufio/stream_test.ww @@ -73,7 +73,7 @@ fn checkpartialflush(zero: bool) void = { sink.zero = zero; let rb: [1]u8; let wb: [4]u8; - let b: bufio.stream = bufio.init(&sink.vt, rb[0:1], wb[0:4]); + let b: bufio.stream = bufio.newstream(&sink.vt, rb[0:1], wb[0:4]); let src: [4]u8; let _: i32 = sputstr("ABCD", src[0:4], 0); let wr: (size | io.error) = io.write(&b.vt, src[0:4]); @@ -109,7 +109,7 @@ fn checkpartialflush(zero: bool) void = { let rb: [8]u8; let wb: [8]u8; - let b: bufio.stream = bufio.init(m, rb[0:8], wb[0:8]); + let b: bufio.stream = bufio.newstream(m, rb[0:8], wb[0:8]); // Write under the buffer limit — nothing reaches src. let buf: [5]u8; @@ -139,7 +139,7 @@ fn checkpartialflush(zero: bool) void = { let rb: [4]u8; let wb: [4]u8; - let b: bufio.stream = bufio.init(m, rb[0:4], wb[0:4]); + let b: bufio.stream = bufio.newstream(m, rb[0:4], wb[0:4]); // 10B payload through a 4B wbuf: bwrite must flush twice mid-write. let buf: [10]u8; @@ -170,7 +170,7 @@ fn checkpartialflush(zero: bool) void = { let rb: [4]u8; let wb: [16]u8; - let b: bufio.stream = bufio.init(m, rb[0:4], wb[0:16]); + let b: bufio.stream = bufio.newstream(m, rb[0:4], wb[0:16]); let p: io.stream = &b.vt; @@ -204,7 +204,7 @@ fn checkpartialflush(zero: bool) void = { let rb: [4]u8; let wb: [16]u8; - let b: bufio.stream = bufio.init(m, rb[0:4], wb[0:16]); + let b: bufio.stream = bufio.newstream(m, rb[0:4], wb[0:16]); let nilbs: [1]u8; bufio.setflush(&b, nilbs[0:0]); @@ -245,7 +245,7 @@ fn checkpartialflush(zero: bool) void = { let rb: [4]u8; let wb: [4]u8; - let b: bufio.stream = bufio.init(m, rb[0:4], wb[0:4]); + let b: bufio.stream = bufio.newstream(m, rb[0:4], wb[0:4]); assert(!(!bufio.isbuffered(&b.vt))); assert(!(bufio.isbuffered(m))); @@ -264,7 +264,7 @@ fn checkpartialflush(zero: bool) void = { let rb: [8]u8; let wb: [4]u8; - let b: bufio.stream = bufio.init(m, rb[0:8], wb[0:4]); + let b: bufio.stream = bufio.newstream(m, rb[0:8], wb[0:4]); let p: io.stream = &b.vt; @@ -303,7 +303,7 @@ fn checkpartialflush(zero: bool) void = { let rb: [8]u8; let wb: [4]u8; - let b: bufio.stream = bufio.init(m, rb[0:8], wb[0:4]); + let b: bufio.stream = bufio.newstream(m, rb[0:8], wb[0:4]); let p: io.stream = &b.vt; @@ -339,7 +339,7 @@ fn checkpartialflush(zero: bool) void = { let rb: [8]u8; let wb: [4]u8; - let b: bufio.stream = bufio.init(m, rb[0:8], wb[0:4]); + let b: bufio.stream = bufio.newstream(m, rb[0:8], wb[0:4]); let p: io.stream = &b.vt; @@ -385,7 +385,7 @@ fn checkpartialflush(zero: bool) void = { let rb: [4]u8; let _: i32 = sputstr("ABCD", rb[0:4], 0); let wb: [1]u8; - let b: bufio.stream = bufio.init(&mem.vt, rb[0:4], wb[0:1]); + let b: bufio.stream = bufio.newstream(&mem.vt, rb[0:4], wb[0:1]); bufio.unread(&b, rb[0:3]); let out: [3]u8; let r: (size | io.eof | io.error) = io.read(&b.vt, out[0:3]); @@ -406,7 +406,7 @@ fn checkpartialflush(zero: bool) void = { let rb: [16]u8; let wb: [4]u8; - let b: bufio.stream = bufio.init(m, rb[0:16], wb[0:4]); + let b: bufio.stream = bufio.newstream(m, rb[0:16], wb[0:4]); let push: [3]u8; push[0] = 88u8; push[1] = 89u8; push[2] = 90u8; // "XYZ" @@ -437,7 +437,7 @@ fn checkpartialflush(zero: bool) void = { let rb: [4]u8; let wb: [4]u8; - let b: bufio.stream = bufio.init(m, rb[0:4], wb[0:4]); + let b: bufio.stream = bufio.newstream(m, rb[0:4], wb[0:4]); let r: (void | io.error) = bufio.flush(&b); match (r) { @@ -454,7 +454,7 @@ fn checkpartialflush(zero: bool) void = { let rb: [4]u8; let wb: [4]u8; - let b: bufio.stream = bufio.init(m, rb[0:4], wb[0:4]); + let b: bufio.stream = bufio.newstream(m, rb[0:4], wb[0:4]); let nilbs: [1]u8; bufio.setflush(&b, nilbs[0:0]); // suppress the default '\n' detector @@ -490,7 +490,7 @@ fn checkpartialflush(zero: bool) void = { let rb: [4]u8; let wb: [4]u8; - let b: bufio.stream = bufio.init(src, rb[0:4], wb[0:4]); + let b: bufio.stream = bufio.newstream(src, rb[0:4], wb[0:4]); let nilbs: [1]u8; bufio.setflush(&b, nilbs[0:0]); // suppress the default '\n' detector @@ -524,7 +524,7 @@ fn checkpartialflush(zero: bool) void = { let rb: [4]u8; let wb: [16]u8; - let b: bufio.stream = bufio.init(m, rb[0:4], wb[0:16]); + let b: bufio.stream = bufio.newstream(m, rb[0:4], wb[0:16]); let bs: [1]u8; bs[0] = 32u8; // ' ' bufio.setflush(&b, bs[0:1]); diff --git a/lib/math/random/random_test.ww b/lib/math/random/random_test.ww index 16cc7fb0..97216155 100644 --- a/lib/math/random/random_test.ww +++ b/lib/math/random/random_test.ww @@ -4,7 +4,7 @@ import math.random; import test; @test fn seq() void = { - let r: random.random = random.init(1234567u64); + let r: random.random = random.fromseed(1234567u64); assert(!(random.next(&r) != 6457827717110365317u64)); assert(!(random.next(&r) != 3203168211198807973u64)); assert(!(random.next(&r) != 9817491932198370423u64)); @@ -13,8 +13,8 @@ import test; }; @test fn deterministic() void = { - let a: random.random = random.init(42u64); - let b: random.random = random.init(42u64); + let a: random.random = random.fromseed(42u64); + let b: random.random = random.fromseed(42u64); let i: i32 = 0; for (i < 32) { assert(!(random.next(&a) != random.next(&b))); @@ -23,7 +23,7 @@ import test; }; @test fn u32n_inrange() void = { - let r: random.random = random.init(7u64); + let r: random.random = random.fromseed(7u64); let i: i32 = 0; for (i < 200) { let v: u32 = random.u32n(&r, 17u32); @@ -35,7 +35,7 @@ import test; }; @test fn u64n_pow2() void = { - let r: random.random = random.init(99u64); + let r: random.random = random.fromseed(99u64); let i: i32 = 0; for (i < 200) { let v: u64 = random.u64n(&r, 16u64); @@ -45,7 +45,7 @@ import test; }; @test fn u64n_nonpow2() void = { - let r: random.random = random.init(123u64); + let r: random.random = random.fromseed(123u64); let i: i32 = 0; for (i < 200) { let v: u64 = random.u64n(&r, 100u64); @@ -60,12 +60,12 @@ import test; @test fn u32n_zero_aborts() void = { test.expectabort(); - let r: random.random = random.init(1u64); + let r: random.random = random.fromseed(1u64); random.u32n(&r, 0u32); }; @test fn u64n_zero_aborts() void = { test.expectabort(); - let r: random.random = random.init(1u64); + let r: random.random = random.fromseed(1u64); random.u64n(&r, 0u64); }; diff --git a/test/misc/qualast_test.ww b/test/misc/qualast_test.ww index bd8e13f9..c1eab19b 100644 --- a/test/misc/qualast_test.ww +++ b/test/misc/qualast_test.ww @@ -75,31 +75,6 @@ fn astrow(label: str, src: str, needles: []str) void = { testenv.clean(td); }; -fn rejectrow(label: str, src: str, needle: str) void = { - let td: str = testenv.fresh(); - testenv.writefile(strings.concat(td, "/a.ww"), src); - let tools: []str = ["wwdump", "wwdump_ww"]; - let stages: []str = ["cstage", "wwstage"]; - let errors: []str = ["", ""]; - let i: i32 = 0; - for (i < tools.len) { - let av: []str = [testenv.driver(tools[i]), "-a", - strings.concat(td, "/a.ww")]; - let co: testenv.commandout; - testenv.runcommand(td, td, stages[i], av, tmo(), &co); - if (co.termination != exec.termination.EXIT || co.code == 0 - || !testenv.has(co.stderr, needle)) { - fail(label, strings.concat(stages[i], " accepted rejected import")); - }; - errors[i] = strings.dup(co.stderr); - i += 1; - }; - if (!testenv.same(errors[0], errors[1])) { - fail(label, "cs/ww parse diagnostics DIFFER"); - }; - testenv.clean(td); -}; - @test fn astproof() void = { let trailing: []str = ["(dot \"x\"", "(structlit", "(tname \"pkg.point\""]; @@ -127,9 +102,11 @@ fn rejectrow(label: str, src: str, needle: str) void = { "package main;\n", "import stable acme.codec;\n", "fn main() i32 = { return 0; };\n"), aliased); - rejectrow("blank-alias", strings.concat( + // A blank N_USE deliberately has no `.str` qualifier, so astprint's + // `(use` line has no quoted binding while remaining a real use node. + let blank: []str = ["(use\n"]; + astrow("blank-import", strings.concat( "package main;\n", "import _ acme.codec;\n", - "fn main() i32 = { return 0; };\n"), - "blank import alias _ is not implemented"); + "fn main() i32 = { return 0; };\n"), blank); }; diff --git a/test/package/package_test.ww b/test/package/package_test.ww index 1611c44f..f447be88 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -1157,12 +1157,16 @@ fn hexbytes(value: str) str = { assert(!has(externalproduction, "//ww:module ")); assert(!has(externalproduction, "SAME_TEST_SOURCE")); assert(!has(externalproduction, "EXTERNAL_TEST_SOURCE")); - assert(same(samemainunit, strings.concat( + assert(strings.hasprefix(samemainunit, strings.concat( "//ww:module-reset __wwtestmain.pkg.internal.main\n", "package main;\nimport pkg;\nimport test;\n"))); - assert(same(externalmainunit, strings.concat( + assert(strings.hasprefix(externalmainunit, strings.concat( "//ww:module-reset __wwtestmain.pkg_test.external.main\n", "package main;\nimport pkg_test;\nimport test;\n"))); + assert(has(samemainunit, "//ww:direct-export pkg ")); + assert(has(samemainunit, "//ww:direct-export test ")); + assert(has(externalmainunit, "//ww:direct-export pkg_test ")); + assert(has(externalmainunit, "//ww:direct-export test ")); assert(!has(samemainunit, "PACKAGE_PRODUCTION")); assert(!has(samemainunit, "SAME_TEST_SOURCE")); assert(!has(externalmainunit, "EXTERNAL_TEST_SOURCE")); @@ -1221,13 +1225,15 @@ fn hexbytes(value: str) str = { let productioncompile: str = linecontaining(ctrace, "/pkg.unit.ww"); assert(same(samecompile, strings.concat( - "--test-package -c --import __same ", + "--test-package --package-init-symbol ", + "__ww..pkg.p.pkg.v1.r0.init -c --import __same ", sharedwork, "__same.wwi --import api ", sharedwork, "api.wwi -I ", sharedwork, "pkg-internal-test.wwi -o ", sharedwork, "pkg-internal-test.s ", sharedwork, "pkg-internal-test.unit.ww"))); assert(same(externalcompile, strings.concat( - "--test-package -c --import __external ", + "--test-package --package-init-symbol ", + "__ww..pkg.p.pkg_test.v2.r0.init -c --import __external ", sharedwork, "__external.wwi --import pkg ", sharedwork, "pkg.wwi -I ", sharedwork, "pkg_test-external-test.wwi -o ", sharedwork, @@ -1239,7 +1245,9 @@ fn hexbytes(value: str) str = { "pkg_test-external-test-main.unit.ww"); assert(same(samemaincompile, strings.concat( "-T --entry --test-support-module test ", - "--test-target-package pkg -c --import pkg ", + "--test-target-package pkg --package-init-symbol ", + "__ww..pkg.p.__wwtestmain.pkg.internal.main.v3.r2.init ", + "--init-dispatch-symbol __ww..dispatch -c --import pkg ", sharedwork, "pkg-internal-test.wwi --import test ", sharedwork, "test.wwi -I ", sharedwork, "pkg-internal-test-main.wwi -o ", sharedwork, @@ -1247,22 +1255,25 @@ fn hexbytes(value: str) str = { "pkg-internal-test-main.unit.ww"))); assert(same(externalmaincompile, strings.concat( "-T --entry --test-support-module test ", - "--test-target-package pkg_test -c --import pkg_test ", + "--test-target-package pkg_test --package-init-symbol ", + "__ww..pkg.p.__wwtestmain.pkg_test.external.main.v3.r2.init ", + "--init-dispatch-symbol __ww..dispatch -c --import pkg_test ", sharedwork, "pkg_test-external-test.wwi --import test ", sharedwork, "test.wwi -I ", sharedwork, "pkg_test-external-test-main.wwi -o ", sharedwork, "pkg_test-external-test-main.s ", sharedwork, "pkg_test-external-test-main.unit.ww"))); assert(same(productioncompile, strings.concat( + "--package-init-symbol __ww..pkg.p.pkg.v0.r0.init ", "-c --import api ", sharedwork, "api.wwi -I ", sharedwork, "pkg.wwi -o ", sharedwork, "pkg.s ", sharedwork, "pkg.unit.ww"))); let ltrace: str = readfile(linkertrace); assert(occurrences(ltrace, "\n") == 2); let samelink: str = linecontaining(ltrace, - strings.concat("-o ", samebin, " ")); + strings.concat("-o ", samebin, ".new ")); let externallink: str = linecontaining(ltrace, - strings.concat("-o ", externalbin, " ")); + strings.concat("-o ", externalbin, ".new ")); assert(has(samelink, strings.concat(sharedwork, "api.a"))); assert(has(samelink, strings.concat(sharedwork, "implementation.a"))); @@ -1848,7 +1859,7 @@ fn hexbytes(value: str) str = { ri = 0; for (ri < bins.len) { let link: str = linecontaining(ltrace, - strings.concat("-o ", bins[ri], " ")); + strings.concat("-o ", bins[ri], ".new ")); assert(has(link, strings.concat(sharedwork, mainkeys[ri], ".a"))); assert(has(link, strings.concat(sharedwork, rootkeys[ri], ".a"))); assert(!has(link, ".wwi")); @@ -2403,13 +2414,13 @@ fn hexbytes(value: str) str = { let links: str = readfile(linktrace); assert(occurrences(links, "\n") == 4); let namedsame: str = linecontaining(links, - strings.concat("-o ", bins[0], " ")); + strings.concat("-o ", bins[0], ".new ")); let namedexternal: str = linecontaining(links, - strings.concat("-o ", bins[1], " ")); + strings.concat("-o ", bins[1], ".new ")); let consumersame: str = linecontaining(links, - strings.concat("-o ", bins[2], " ")); + strings.concat("-o ", bins[2], ".new ")); let consumerexternal: str = linecontaining(links, - strings.concat("-o ", bins[3], " ")); + strings.concat("-o ", bins[3], ".new ")); assert(has(namedsame, strings.concat(work, "__wwtest.a"))); assert(!has(namedsame, strings.concat(work, "test.a"))); assert(has(namedexternal, strings.concat(work, "test.a"))); @@ -2595,19 +2606,22 @@ fn hexbytes(value: str) str = { let atrace: str = readfile(assemblertraces[si]); let ltrace: str = readfile(linkertraces[si]); assert(occurrences(ctrace, "\n") == packagecount + 1); - assert(occurrences(atrace, "\n") == packagecount + 1); + assert(occurrences(atrace, "\n") == packagecount + 2); assert(occurrences(ltrace, "\n") == 1); let rootline: str = linecontaining(ctrace, strings.concat("/", rootaction, ".unit.new")); assert(occurrences(rootline, "<--import>") == packagecount); - let expectedroot: str = "BEGIN<--entry><-c>"; + let expectedroot: str = strings.concat( + "BEGIN<--entry><--package-init-symbol><__ww..pkg.p.", + rootaction, ".v0.r0.init><--init-dispatch-symbol>", + "<__ww..dispatch><-c>"); i = 0; for (i < packagecount) { let name: str = boundarypkgname(i); expectedroot = strings.concat(expectedroot, "<--import><", name, - "><", works[si], "/", name, ".wwi>"); + "><", works[si], "/", name, ".wwi.new>"); assert(occurrences(rootline, strings.concat("<", name, "><", - works[si], "/", name, ".wwi>")) == 1); + works[si], "/", name, ".wwi.new>")) == 1); i += 1; }; expectedroot = strings.concat(expectedroot, "<-I><", works[si], "/", @@ -2617,12 +2631,14 @@ fn hexbytes(value: str) str = { i = 0; for (i < packagecount) { let name: str = boundarypkgname(i); - let expectedcompile: str = "BEGIN<-c>"; + let expectedcompile: str = strings.concat( + "BEGIN<--package-init-symbol><__ww..pkg.p.", name, + ".v0.r0.init><-c>"); if (i + 1 < packagecount) { let next: str = boundarypkgname(i + 1); expectedcompile = strings.concat(expectedcompile, "<--import><", next, "><", works[si], "/", next, - ".wwi>"); + ".wwi.new>"); }; expectedcompile = strings.concat(expectedcompile, "<-I><", works[si], "/", name, ".wwi.new><-o><", works[si], "/", @@ -2634,7 +2650,7 @@ fn hexbytes(value: str) str = { assert(same(linecontaining(atrace, strings.concat("/", name, ".o.new")), expectedassemble)); assert(occurrences(ltrace, strings.concat("<", works[si], "/", - name, ".a>")) == 1); + name, ".a.new>")) == 1); assert(os.exists(strings.concat(works[si], "/", name, ".wwi"))); assert(os.exists(strings.concat(works[si], "/", name, ".s"))); assert(os.exists(strings.concat(works[si], "/", name, ".o"))); @@ -2652,35 +2668,47 @@ fn hexbytes(value: str) str = { }; let z: str = strings.concat("package ", name, ";\n// OWNER_Z_", name, "\nfn private_value() i32 = { return value(); };\n"); - assert(same(readfile(strings.concat(works[si], "/", name, - ".unit.ww")), strings.concat("//ww:module-reset ", name, - "\n", a, "\n//ww:module-reset ", name, "\n", z, "\n"))); + let unit: str = readfile(strings.concat(works[si], "/", name, + ".unit.ww")); + assert(strings.hasprefix(unit, strings.concat("//ww:module-reset ", + name, "\n", a, "\n//ww:module-reset ", name, "\n", z, "\n"))); + if (i + 1 < packagecount) { + assert(has(unit, strings.concat("//ww:direct-export ", + boundarypkgname(i + 1), " "))); + }; assert(strings.hasprefix(readfile(strings.concat(works[si], "/", name, ".wwi")), strings.concat("//ww:module ", name, "\n"))); i += 1; }; assert(occurrences(ltrace, strings.concat("<", works[si], "/", - rootaction, ".a>")) == 1); + rootaction, ".a.new>")) == 1); assert(!has(ltrace, ".wwi>")); - let expectedlink: str = strings.concat("BEGIN<-o><", bins[si], "><", - works[si], "/", rootaction, ".a>"); + let expectedlink: str = strings.concat("BEGIN<-o><", bins[si], ".new><", + works[si], "/", rootaction, ".a.new>"); i = 0; for (i < packagecount) { expectedlink = strings.concat(expectedlink, "<", works[si], "/", - boundarypkgname(i), ".a>"); + boundarypkgname(i), ".a.new>"); i += 1; }; expectedlink = strings.concat(expectedlink, "<", repo(), "/out/bin/../lib/libwwrt.a>\n"); assert(same(ltrace, expectedlink)); - assert(same(readfile(strings.concat(works[si], "/", rootaction, - ".unit.ww")), + let rootunit: str = readfile(strings.concat(works[si], "/", rootaction, + ".unit.ww")); + assert(strings.hasprefix(rootunit, strings.concat("//ww:module-reset target\n", roota, "\n//ww:module-reset target\n", rootz, "\n"))); + assert(occurrences(rootunit, "//ww:direct-export p") == packagecount); let rootassemble: str = strings.concat("BEGIN<-o><", works[si], "/", rootaction, ".o.new><", works[si], "/", rootaction, ".s.new>"); assert(same(linecontaining(atrace, strings.concat("/", rootaction, ".o.new")), rootassemble)); + let dispatchassemble: str = strings.concat("BEGIN<-o><", works[si], + "/", rootaction, ".init.o.new><", works[si], "/", rootaction, + ".init.s.new>"); + assert(same(linecontaining(atrace, + strings.concat("/", rootaction, ".init.o.new")), dispatchassemble)); // Repeating the same semantic request in the persistent work root may // relink the requested binary, but must not compile or assemble any action. @@ -2711,16 +2739,21 @@ fn hexbytes(value: str) str = { let adelta: str = strings.sub(chainatrace, chainabefore, chainatrace.len); let ldelta: str = strings.sub(chainltrace, chainlbefore, chainltrace.len); assert(occurrences(cdelta, "\n") == 1); - assert(occurrences(adelta, "\n") == 1); + assert(occurrences(adelta, "\n") == 2); assert(occurrences(ldelta, "\n") == 1); - assert(same(cdelta, strings.concat("BEGIN<--entry><-c><--import><", + assert(same(cdelta, strings.concat( + "BEGIN<--entry><--package-init-symbol><__ww..pkg.p.", + chainaction, ".v0.r0.init><--init-dispatch-symbol>", + "<__ww..dispatch><-c><--import><", works[si], "/p000.wwi><-I><", works[si], "/", chainaction, ".wwi.new><-o><", works[si], "/", chainaction, ".s.new><", works[si], "/", chainaction, ".unit.new>\n"))); assert(same(adelta, strings.concat("BEGIN<-o><", works[si], "/", - chainaction, ".o.new><", works[si], "/", chainaction, ".s.new>\n"))); + chainaction, ".o.new><", works[si], "/", chainaction, ".s.new>\n", + "BEGIN<-o><", works[si], "/", chainaction, ".init.o.new><", + works[si], "/", chainaction, ".init.s.new>\n"))); let expectedchainlink: str = strings.concat("BEGIN<-o><", chainbins[si], - "><", works[si], "/", chainaction, ".a>"); + ".new><", works[si], "/", chainaction, ".a.new>"); i = 0; for (i < packagecount) { expectedchainlink = strings.concat(expectedchainlink, "<", works[si], @@ -2731,9 +2764,11 @@ fn hexbytes(value: str) str = { "/out/bin/../lib/libwwrt.a>\n"); assert(same(ldelta, expectedchainlink)); assert(!has(ldelta, ".wwi>")); - assert(same(readfile(strings.concat(works[si], "/", chainaction, - ".unit.ww")), strings.concat("//ww:module-reset chain\n", - chainsource, "\n"))); + let chainunit: str = readfile(strings.concat(works[si], "/", chainaction, + ".unit.ww")); + assert(strings.hasprefix(chainunit, strings.concat( + "//ww:module-reset chain\n", chainsource, "\n"))); + assert(has(chainunit, "//ww:direct-export p000 ")); let chainrun: []str = [chainbins[si]]; runcommand(root, strings.concat("boundary-chain-run-", stages[si]), chainrun, (60i64 * (time.second: i64)): time.duration, &out); @@ -3308,26 +3343,20 @@ fn hexbytes(value: str) str = { runcommand(root, strings.concat("multidir-failure-", stages[i]), av, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 1); - assert(has(out.stdout, "alpha_same_survives ... ok\n")); - assert(has(out.stdout, "beta_same_survives ... ok\n")); - assert(has(out.stdout, "beta_external_survives ... ok\n")); - assert(has(out.stdout, strings.concat("ok ", alpha, - " [alpha, same-package]\n"))); - assert(has(out.stdout, strings.concat("ok ", beta, - " [beta, same-package]\n"))); - assert(has(out.stdout, strings.concat("ok ", beta, - " [beta_test, external]\n"))); - assert(!has(out.stdout, "alpha_external_fails ...")); + assert(out.stdout.len == 0); assert(has(out.stderr, "has no exported declaration 'hidden'")); + assert(has(out.stderr, strings.concat("FAIL ", alpha, + " [alpha] (build exit 1)\n"))); assert(has(out.stderr, strings.concat("FAIL ", alpha, " [alpha_test] (build exit 1)\n"))); - assert(!has(out.stderr, strings.concat("FAIL ", alpha, - " [alpha]"))); - assert(!has(out.stderr, strings.concat("FAIL ", beta, " ["))); - let alphapos: i32 = pos(out.stdout, strings.concat("ok ", alpha, - " [alpha, same-package]\n")); - let betapos: i32 = pos(out.stdout, strings.concat("ok ", beta, - " [beta, same-package]\n")); + assert(has(out.stderr, strings.concat("FAIL ", beta, + " [beta] (build exit 1)\n"))); + assert(has(out.stderr, strings.concat("FAIL ", beta, + " [beta_test] (build exit 1)\n"))); + let alphapos: i32 = pos(out.stderr, strings.concat("FAIL ", alpha, + " [alpha] (build exit 1)\n")); + let betapos: i32 = pos(out.stderr, strings.concat("FAIL ", beta, + " [beta] (build exit 1)\n")); assert(alphapos >= 0 && alphapos < betapos); diagnostics[i] = strings.dup(primarydiagnostic(out.stderr)); if (i == 0) { referenceout = strings.dup(out.stdout); } @@ -3338,8 +3367,8 @@ fn hexbytes(value: str) str = { clean(root); }; -// One failing root is a product failure, not permission for the shared build -// request to suppress an independently compilable sibling root. +// Roots remain independently diagnosed and may be staged in parallel, but one +// failing product rejects publication and execution of the entire request. @test fn sibling_test_variant_failures_are_isolated() void = { let root: str = fresh(); let badexternal: str = strings.concat(root, "/badexternal"); @@ -3380,15 +3409,15 @@ fn hexbytes(value: str) str = { runcommand(root, strings.concat("isolated-external-", tags[i]), externalav, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 1); - assert(has(out.stdout, "valid_same_runs ... ok\n")); - assert(has(out.stdout, "[badexternal, same-package]\n")); + assert(out.stdout.len == 0); assert(has(out.stderr, "has no exported declaration 'hidden'")); + assert(has(out.stderr, + strings.concat("FAIL ", badexternal, + " [badexternal] (build exit 1)\n"))); assert(has(out.stderr, strings.concat("FAIL ", badexternal, " [badexternal_test] (build exit 1)\n"))); - assert(!has(out.stderr, strings.concat("FAIL ", badexternal, - " [badexternal]"))); externaldiagnostics[i] = strings.dup(primarydiagnostic(out.stderr)); let sameav: []str = [driver(stages[i]), "test", "-I", root, @@ -3396,23 +3425,20 @@ fn hexbytes(value: str) str = { runcommand(root, strings.concat("isolated-same-", tags[i]), sameav, (120i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 1); - assert(has(out.stdout, "valid_external_runs ... ok\n")); - assert(has(out.stdout, "[badsame_test, external]\n")); + assert(out.stdout.len == 0); assert(has(out.stderr, "undefined: missing")); assert(has(out.stderr, strings.concat("FAIL ", badsame, " [badsame] (build exit 1)\n"))); - assert(!has(out.stderr, strings.concat("FAIL ", badsame, - " [badsame_test]"))); + assert(has(out.stderr, strings.concat("FAIL ", badsame, + " [badsame_test] (build exit 1)\n"))); samediagnostics[i] = strings.dup(primarydiagnostic(out.stderr)); i += 1; }; assert(same(externaldiagnostics[0], externaldiagnostics[1])); assert(same(samediagnostics[0], samediagnostics[1])); - // A first persistent union pass may contain a root-local compiler failure. - // Its successfully committed actions still become reusable: only the - // uncommitted failing root is compiled again on the next request in both - // driver stages. + // A persistent request is one publication transaction. A rejection leaves + // no reusable generation, so every staged action is reconsidered next time. let warmroot: str = strings.concat(root, "/warm-partial"); assert(os.mkdir(warmroot, 448i32) == 0); let trace: str = strings.concat(root, "/warm-partial.trace"); @@ -3452,9 +3478,14 @@ fn hexbytes(value: str) str = { expectexit(&out, 1); let secondtrace: str = readfile(trace); assert(occurrences(secondtrace, - "/badexternal-internal-test.unit.new") == 1); + "/badexternal-internal-test.unit.new") == 2); assert(occurrences(secondtrace, "/badexternal_test-external-test.unit.new") == 2); + assert(!os.exists(strings.concat(warmroot, + "/badexternal-internal-test.unit.ww"))); + assert(!os.exists(strings.concat(warmroot, + "/badexternal_test-external-test.unit.ww"))); + assert(!os.exists(strings.concat(warmroot, "/.wwtool.stamp"))); let wwtracebin: str = strings.concat(repo(), "/out/partial-trace-", workescape(root)); @@ -3502,9 +3533,14 @@ fn hexbytes(value: str) str = { expectexit(&out, 1); let wwsecond: str = readfile(wwtrace); assert(occurrences(wwsecond, - "/badexternal-internal-test.unit.new") == 1); + "/badexternal-internal-test.unit.new") == 2); assert(occurrences(wwsecond, "/badexternal_test-external-test.unit.new") == 2); + assert(!os.exists(strings.concat(wwwarmroot, + "/badexternal-internal-test.unit.ww"))); + assert(!os.exists(strings.concat(wwwarmroot, + "/badexternal_test-external-test.unit.ww"))); + assert(!os.exists(strings.concat(wwwarmroot, "/.wwtool.stamp"))); clean(wwtracebin); clean(root); }; @@ -3850,8 +3886,10 @@ fn hexbytes(value: str) str = { let wantsecret: str = strings.concat( "//ww:module-reset domain.internal.secret\n", secreta, "\n//ww:module-reset domain.internal.secret\n", secretz, "\n"); - assert(same(readfile(strings.concat(works[si], - "/domain.internal.secret.unit.ww")), wantsecret)); + let secretunit: str = readfile(strings.concat(works[si], + "/domain.internal.secret.unit.ww")); + assert(strings.hasprefix(secretunit, wantsecret)); + assert(has(secretunit, "//ww:direct-export domain.leaf ")); let ai: i32 = 0; for (ai < artifacts.len) { let xi: i32 = 0; @@ -3869,8 +3907,13 @@ fn hexbytes(value: str) str = { let atrace: str = readfile(assemblertraces[si]); let ltrace: str = readfile(linkertraces[si]); assert(occurrences(ctrace, "\n") == 3); - assert(occurrences(atrace, "\n") == 3); + assert(occurrences(atrace, "\n") == 4); assert(occurrences(ltrace, "\n") == 1); + let dispatchline: str = strings.concat("BEGIN<-o><", works[si], + "/domain.client.init.o.new><", works[si], + "/domain.client.init.s.new>"); + assert(same(linecontaining(atrace, "/domain.client.init.o.new"), + dispatchline)); let secretline: str = linecontaining(ctrace, "domain.internal.secret.unit.new"); assert(occurrences(secretline, "domain.leaf.wwi") == 1); @@ -4420,7 +4463,16 @@ fn hexbytes(value: str) str = { for (ui < unitartifacts.len) { let unit: str = readfile(strings.concat(work, unitartifacts[ui], ".unit.ww")); - assert(same(unit, wantunits[ui])); + assert(strings.hasprefix(unit, wantunits[ui])); + if (ui == 1 || ui == 2) { + assert(has(unit, strings.concat("//ww:direct-export ", + baseidentity, " "))); + } else { if (ui == 3) { + assert(has(unit, strings.concat("//ww:direct-export ", + leftidentity, " "))); + assert(has(unit, strings.concat("//ww:direct-export ", + rightidentity, " "))); + }; }; assert(!has(unit, "//ww:module ")); if (si == 0) { referenceunits[ui] = strings.dup(unit); } else { assert(same(referenceunits[ui], unit)); }; @@ -4458,23 +4510,32 @@ fn hexbytes(value: str) str = { let atrace: str = readfile(assemblertrace); let ltrace: str = readfile(linkertrace); assert(occurrences(ctrace, "\n") == 4); - let baseline: str = strings.concat("BEGIN<-c><-I><", work, + let baseline: str = strings.concat( + "BEGIN<--package-init-symbol><__ww..pkg.p.", baseidentity, + ".v0.r0.init><-c><-I><", work, artifacts[0], ".wwi><-o><", work, artifacts[0], ".s><", work, artifacts[0], ".unit.ww>"); - let leftline: str = strings.concat("BEGIN<-c><--import>", + let leftline: str = strings.concat( + "BEGIN<--package-init-symbol><__ww..pkg.p.", leftidentity, + ".v0.r0.init><-c><--import>", "<", baseidentity, "><", work, artifacts[0], ".wwi><-I><", work, artifacts[1], ".wwi><-o><", work, artifacts[1], ".s><", work, artifacts[1], ".unit.ww>"); - let rightline: str = strings.concat("BEGIN<-c><--import>", + let rightline: str = strings.concat( + "BEGIN<--package-init-symbol><__ww..pkg.p.", rightidentity, + ".v0.r0.init><-c><--import>", "<", baseidentity, "><", work, artifacts[0], ".wwi><-I><", work, artifacts[2], ".wwi><-o><", work, artifacts[2], ".s><", work, artifacts[2], ".unit.ww>"); - let rootline: str = strings.concat("BEGIN<--entry><-c><--import>", + let rootline: str = strings.concat( + "BEGIN<--entry><--package-init-symbol><__ww..pkg.p.", + rootidentity, ".v0.r0.init><--init-dispatch-symbol>", + "<__ww..dispatch><-c><--import>", "<", leftidentity, "><", work, artifacts[1], ".wwi><--import>", "<", rightidentity, "><", work, @@ -4520,7 +4581,7 @@ fn hexbytes(value: str) str = { expectexit(&out, 1); assert(same(out.stderr, strings.concat("w6c: import ", leftidentity, ": export owner mismatch in ", work, artifacts[0], ".wwi\n"))); - assert(occurrences(atrace, "\n") == 4); + assert(occurrences(atrace, "\n") == 5); ai = 0; for (ai < artifacts.len) { let assemblerline: str = strings.concat("BEGIN<-o><", work, @@ -4529,8 +4590,12 @@ fn hexbytes(value: str) str = { artifacts[ai], ".o")), assemblerline)); ai += 1; }; + let dispatcherline: str = strings.concat("BEGIN<-o><", work, + artifacts[3], ".init.o><", work, artifacts[3], ".init.s>"); + assert(same(linecontaining(atrace, strings.concat(work, + artifacts[3], ".init.o")), dispatcherline)); assert(occurrences(ltrace, "\n") == 1); - assert(has(ltrace, strings.concat("BEGIN<-o><", bin, "><", work, + assert(has(ltrace, strings.concat("BEGIN<-o><", bin, ".new><", work, artifacts[3], ".a>"))); ai = 0; for (ai < artifacts.len) { @@ -4842,17 +4907,17 @@ fn hexbytes(value: str) str = { let externalline: str = linecontaining(ctrace, strings.concat(artifacts[2], ".unit.new")); assert(has(externalline, strings.concat("<--import><", commandid, - "><", work, artifacts[0], ".wwi>"))); + "><", work, artifacts[0], ".wwi.new>"))); let internalmainline: str = linecontaining(ctrace, strings.concat(artifacts[3], ".unit.new")); let externalmainline: str = linecontaining(ctrace, strings.concat(artifacts[4], ".unit.new")); assert(has(internalmainline, strings.concat("<--import><", internalid, - "><", work, artifacts[1], ".wwi>"))); + "><", work, artifacts[1], ".wwi.new>"))); assert(has(internalmainline, strings.concat( "<--test-target-package><", internalid, ">"))); assert(has(externalmainline, strings.concat("<--import><", externalid, - "><", work, artifacts[2], ".wwi>"))); + "><", work, artifacts[2], ".wwi.new>"))); assert(has(externalmainline, strings.concat( "<--test-target-package><", externalid, ">"))); assert(occurrences(ctrace, strings.concat("<", work, artifacts[0], @@ -5470,27 +5535,37 @@ fn hexbytes(value: str) str = { let commandatrace: str = readfile(commandassemblertraces[commandstage]); let commandltrace: str = readfile(commandlinkertraces[commandstage]); assert(occurrences(commandctrace, "\n") == 3); - assert(occurrences(commandatrace, "\n") == 3); + assert(occurrences(commandatrace, "\n") == 5); assert(occurrences(commandltrace, "\n") == 2); let commandalphacompile: str = linecontaining(commandctrace, strings.concat(commandalphaid, ".unit.new")); assert(occurrences(commandalphacompile, strings.concat( - "<--import><", commandwork, "library.wwi>")) == 1); + "<--import><", commandwork, "library.wwi.new>")) == 1); assert(!has(commandalphacompile, "cmd-zeta.wwi")); + let alphaassembly: str = strings.concat("BEGIN<-o><", commandwork, + commandalphaid, ".init.o.new><", commandwork, commandalphaid, + ".init.s.new>"); + let zetaassembly: str = strings.concat("BEGIN<-o><", commandwork, + commandzetaid, ".init.o.new><", commandwork, commandzetaid, + ".init.s.new>"); + assert(same(linecontaining(commandatrace, strings.concat(commandalphaid, + ".init.o.new")), alphaassembly)); + assert(same(linecontaining(commandatrace, strings.concat(commandzetaid, + ".init.o.new")), zetaassembly)); let commandalphalink: str = linecontaining(commandltrace, - strings.concat("<-o><", commandbins[commandstage], "/cmd-alpha>")); + strings.concat("<-o><", commandbins[commandstage], "/cmd-alpha.new>")); let commandzetalink: str = linecontaining(commandltrace, - strings.concat("<-o><", commandbins[commandstage], "/cmd-zeta>")); + strings.concat("<-o><", commandbins[commandstage], "/cmd-zeta.new>")); assert(occurrences(commandalphalink, strings.concat("<", commandwork, - commandalphaid, ".a>")) == 1); + commandalphaid, ".a.new>")) == 1); assert(occurrences(commandalphalink, strings.concat("<", commandwork, - "library.a>")) == 1); + "library.a.new>")) == 1); assert(!has(commandalphalink, strings.concat(commandzetaid, ".a"))); assert(!has(commandalphalink, ".wwi>")); assert(has(commandalphalink, strings.concat("<-L><", repo(), "/out/lib><-l>"))); assert(has(commandzetalink, strings.concat("<", commandwork, - commandzetaid, ".a>"))); + commandzetaid, ".a.new>"))); assert(!has(commandzetalink, "library.a")); assert(!has(commandzetalink, ".wwi>")); let normalizedcommandcompiler: str = normalizedtrace(commandctrace, @@ -7276,8 +7351,10 @@ fn runtimepath(relative: str) str = { "//ww:module-reset ", expanded, "\n", neara, "\n", "//ww:module-reset ", expanded, "\n", nearz, "\n", "//ww:vendor-dir ", hexbytes(nearest), "\n"); - assert(same(readfile(strings.concat(works[si], "/", expanded, - ".unit.ww")), wantnear)); + let nearunit: str = readfile(strings.concat(works[si], "/", expanded, + ".unit.ww")); + assert(strings.hasprefix(nearunit, wantnear)); + assert(has(nearunit, "//ww:direct-export lib.leaf ")); let clientunit: str = readfile(strings.concat(works[si], "/domain.app.client.unit.ww")); assert(has(clientunit, strings.concat("//ww:import-map lib.math ", @@ -7299,8 +7376,14 @@ fn runtimepath(relative: str) str = { let atrace: str = readfile(assemblertraces[si]); let ltrace: str = readfile(linkertraces[si]); assert(occurrences(ctrace, "\n") == 3); - assert(occurrences(atrace, "\n") == 3); + assert(occurrences(atrace, "\n") == 4); assert(occurrences(ltrace, "\n") == 1); + assert(has(ltrace, strings.concat("BEGIN<-o><", bins[si], ".new>"))); + let dispatchline: str = strings.concat("BEGIN<-o><", works[si], + "/domain.app.client.init.o.new><", works[si], + "/domain.app.client.init.s.new>"); + assert(same(linecontaining(atrace, + "/domain.app.client.init.o.new"), dispatchline)); let nearline: str = linecontaining(ctrace, strings.concat(expanded, ".unit.new")); assert(occurrences(nearline, "<--import>") == 1); @@ -7308,16 +7391,16 @@ fn runtimepath(relative: str) str = { let clientline: str = linecontaining(ctrace, "domain.app.client.unit.new"); assert(occurrences(clientline, strings.concat("<--import><", expanded, - "><", works[si], "/", expanded, ".wwi>")) == 1); + "><", works[si], "/", expanded, ".wwi.new>")) == 1); assert(occurrences(clientline, strings.concat( "<--import-map><", expanded, ">")) == 1); assert(!has(clientline, "lib.leaf.wwi")); assert(occurrences(ltrace, strings.concat("<", works[si], - "/domain.app.client.a>")) == 1); + "/domain.app.client.a.new>")) == 1); assert(occurrences(ltrace, strings.concat("<", works[si], "/", - expanded, ".a>")) == 1); + expanded, ".a.new>")) == 1); assert(occurrences(ltrace, strings.concat("<", works[si], - "/lib.leaf.a>")) == 1); + "/lib.leaf.a.new>")) == 1); assert(!has(ltrace, ".wwi>")); let nc: str = normalizedtrace(ctrace, strings.concat(works[si], "/"), bins[si]); @@ -7732,9 +7815,9 @@ fn runtimepath(relative: str) str = { let externalline: str = linecontaining(testtrace, "domain.app.testclient_test-external-test.unit.new"); assert(occurrences(internalline, strings.concat("<--import><", expanded, - "><", testwork, expanded, ".wwi>")) == 1); + "><", testwork, expanded, ".wwi.new>")) == 1); assert(occurrences(externalline, strings.concat("<--import><", expanded, - "><", testwork, expanded, ".wwi>")) == 1); + "><", testwork, expanded, ".wwi.new>")) == 1); assert(occurrences(internalline, strings.concat( "<--import-map><", expanded, ">")) == 1); assert(occurrences(externalline, strings.concat( @@ -8029,15 +8112,7 @@ fn runtimepath(relative: str) str = { "for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ", "\"$WW_NAME_COMPILER_TRACE\"; done\n", "printf '\\n' >> \"$WW_NAME_COMPILER_TRACE\"\n", - "\"$WW_NAME_REAL_COMPILER\" \"$@\"\n", - "status=$?\n", - "if [ \"$status\" -eq 0 ] && ", - "[ -n \"$WW_COMMIT_BLOCK_STAMP\" ]; then\n", - " mv -- \"$WW_COMMIT_BLOCK_STAMP\" ", - "\"$WW_COMMIT_BLOCK_STAMP.saved\" || exit 97\n", - " mkdir -- \"$WW_COMMIT_BLOCK_STAMP\" || exit 98\n", - "fi\n", - "exit \"$status\"\n")); + "exec \"$WW_NAME_REAL_COMPILER\" \"$@\"\n")); let stages: []str = ["ww", "ww_ww"]; let compilers: []str = ["w6c", "w6c_ww"]; @@ -8059,8 +8134,6 @@ fn runtimepath(relative: str) str = { strings.concat(root, "/ww-commit-work")]; let commitcoldbins: []str = [strings.concat(root, "/c-commit-cold"), strings.concat(root, "/ww-commit-cold")]; - let commitblockbins: []str = [strings.concat(root, "/c-commit-block"), - strings.concat(root, "/ww-commit-block")]; let commitrejectbins: []str = [strings.concat(root, "/c-commit-reject"), strings.concat(root, "/ww-commit-reject")]; let commitfixedbins: []str = [strings.concat(root, "/c-commit-fixed"), @@ -8087,8 +8160,6 @@ fn runtimepath(relative: str) str = { let repeatedtraceref: str = ""; let changedtraceref: str = ""; let aliastraceref: str = ""; - let commitfailref: str = ""; - let commitpreflightref: str = ""; let commitbindingref: str = ""; let commitbinref: str = ""; let initialwwiref: str = ""; @@ -8115,9 +8186,7 @@ fn runtimepath(relative: str) str = { && !strings.hasprefix(baseenv[ei], "WW_NAME_COMPILER_TRACE=") && !strings.hasprefix(baseenv[ei], - "WW_NAME_REAL_COMPILER=") - && !strings.hasprefix(baseenv[ei], - "WW_COMMIT_BLOCK_STAMP=")) { + "WW_NAME_REAL_COMPILER=")) { append(env, baseenv[ei]); }; ei += 1; @@ -8168,11 +8237,10 @@ fn runtimepath(relative: str) str = { (30i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 42); - // A failed multi-file artifact rename may have published a new - // interface before a later rename fails. It invalidates every old unit - // voucher and the workdir identity, so the next request must reconsider - // an importer even when the regenerated dependency export now compares - // equal to that partially published interface. + // The package and command form one publication transaction. A changed + // declared name is visible to the importer through the staged .wwi, but + // its resulting semantic rejection must preserve the complete prior + // generation and force the next request to reconsider both actions. let commitcoldav: []str = [driver(stages[si]), "build", "-w", commitworks[si], "-I", source, "-o", commitcoldbins[si], commitapp]; runcommandenv(root, strings.concat("name-commit-cold-", stages[si]), @@ -8182,96 +8250,72 @@ fn runtimepath(relative: str) str = { runcommand(root, strings.concat("name-commit-cold-run-", stages[si]), commitrun, (30i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 42); + let commitpaths: []str = ["/.wwtool.ww", "/.wwtool.w6c", + "/.wwtool.w6a", "/.wwtool.stamp", "/commit.codec.unit.ww", + "/commit.codec.wwi", "/commit.codec.s", "/commit.codec.o", + "/commit.codec.a", "/cmd.commit.unit.ww", "/cmd.commit.wwi", + "/cmd.commit.s", "/cmd.commit.o", "/cmd.commit.a", + "/cmd.commit.init.unit.ww", "/cmd.commit.init.s", + "/cmd.commit.init.o"]; + let commitsnapshots: []str = alloc([], commitpaths.len: u64)!; + let cpi: i32 = 0; + for (cpi < commitpaths.len) { + append(commitsnapshots, readfile(strings.concat(commitworks[si], + commitpaths[cpi]))); + cpi += 1; + }; + let commitcoldbytes: str = readfile(commitcoldbins[si]); rewritefile(commitcodecfile, cablebody); - let commitstamp: str = strings.concat(commitworks[si], - "/.wwtool.stamp"); - let savedstamp: str = strings.concat(commitstamp, ".saved"); - let oldcommitwwi: str = readfile(strings.concat(commitworks[si], - "/commit.codec.wwi")); - let oldcommits: str = readfile(strings.concat(commitworks[si], - "/commit.codec.s")); - let oldcommito: str = readfile(strings.concat(commitworks[si], - "/commit.codec.o")); - let oldcommita: str = readfile(strings.concat(commitworks[si], - "/commit.codec.a")); - let blockenv: []str = alloc([], (env.len + 1): u64)!; - let bei: i32 = 0; - for (bei < env.len) { append(blockenv, env[bei]); bei += 1; }; - append(blockenv, strings.concat("WW_COMMIT_BLOCK_STAMP=", commitstamp)); - let commitblockav: []str = [driver(stages[si]), "build", "-w", - commitworks[si], "-I", source, "-o", commitblockbins[si], - commitapp]; - runcommandenv(root, strings.concat("name-commit-preflight-", stages[si]), - commitblockav, blockenv, - (120i64 * (time.second: i64)): time.duration, &out); - expectexit(&out, 1); - assert(has(out.stderr, "ww: cannot invalidate package workdir\n")); - if (si == 0) { commitpreflightref = strings.dup(out.stderr); } - else { assert(same(commitpreflightref, out.stderr)); }; - assert(same(oldcommitwwi, readfile(strings.concat(commitworks[si], - "/commit.codec.wwi")))); - assert(same(oldcommits, readfile(strings.concat(commitworks[si], - "/commit.codec.s")))); - assert(same(oldcommito, readfile(strings.concat(commitworks[si], - "/commit.codec.o")))); - assert(same(oldcommita, readfile(strings.concat(commitworks[si], - "/commit.codec.a")))); - assert(!os.exists(strings.concat(commitworks[si], - "/commit.codec.unit.ww"))); - assert(!os.exists(commitblockbins[si])); - assert(os.rmdir(commitstamp) == 0); - assert(os.rename(savedstamp, commitstamp) == 0); - - // With the stamp preflight restored, force a later artifact rename to - // fail after the changed interface has already been published. - let blockedasm: str = strings.concat(commitworks[si], - "/commit.codec.s"); - assert(os.remove(blockedasm) == 0); - assert(os.mkdir(blockedasm, 448i32) == 0); let commitrejectav: []str = [driver(stages[si]), "build", "-w", commitworks[si], "-I", source, "-o", commitrejectbins[si], commitapp]; - runcommandenv(root, strings.concat("name-commit-partial-", stages[si]), - commitrejectav, env, - (120i64 * (time.second: i64)): time.duration, &out); - expectexit(&out, 1); - assert(has(out.stderr, "ww: cannot commit commit.codec\n")); - if (si == 0) { commitfailref = strings.dup(out.stderr); } - else { assert(same(commitfailref, out.stderr)); }; - assert(has(readfile(strings.concat(commitworks[si], - "/commit.codec.wwi")), - "//ww:module commit.codec\npackage cable;\n")); - assert(!os.exists(strings.concat(commitworks[si], - "/commit.codec.unit.ww"))); - assert(!os.exists(strings.concat(commitworks[si], - "/cmd.commit.unit.ww"))); - assert(!os.exists(strings.concat(commitworks[si], "/.wwtool.stamp"))); let partialsuffixes: []str = [".unit.new", ".wwi.new", ".s.new", - ".o.new", ".a.new"]; - let psi: i32 = 0; - for (psi < partialsuffixes.len) { - assert(!os.exists(strings.concat(commitworks[si], - "/commit.codec", partialsuffixes[psi]))); - psi += 1; + ".o.new", ".a.new", ".init.unit.new", ".init.s.new", + ".init.o.new"]; + let rejectedactions: []str = ["commit.codec", "cmd.commit"]; + let attemptlabels: []str = ["0", "1"]; + let attempt: i32 = 0; + for (attempt < 2) { + rewritefile(traces[si], ""); + runcommandenv(root, strings.concat("name-commit-reject-", + stages[si], "-", attemptlabels[attempt]), + commitrejectav, env, + (120i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stderr, "imported as cable and not used")); + assert(has(out.stderr, "undefined: wire")); + assert(has(out.stderr, "ww: w6c failed for cmd.commit\n")); + let ncommitbinding: str = normalizedtrace(out.stderr, + strings.concat(commitworks[si], "/"), commitrejectbins[si]); + if (si == 0 && attempt == 0) { + commitbindingref = strings.dup(ncommitbinding); + } else { assert(same(commitbindingref, ncommitbinding)); }; + let rejecttrace: str = readfile(traces[si]); + assert(occurrences(rejecttrace, + "/commit.codec.unit.new") == 1); + assert(occurrences(rejecttrace, + "/cmd.commit.unit.new") == 1); + cpi = 0; + for (cpi < commitpaths.len) { + assert(same(commitsnapshots[cpi], readfile(strings.concat( + commitworks[si], commitpaths[cpi])))); + cpi += 1; + }; + assert(same(commitcoldbytes, readfile(commitcoldbins[si]))); + let rai: i32 = 0; + for (rai < rejectedactions.len) { + let psi: i32 = 0; + for (psi < partialsuffixes.len) { + assert(!os.exists(strings.concat(commitworks[si], "/", + rejectedactions[rai], partialsuffixes[psi]))); + psi += 1; + }; + rai += 1; + }; + assert(!os.exists(commitrejectbins[si])); + assert(!os.exists(strings.concat(commitrejectbins[si], ".new"))); + attempt += 1; }; - assert(!os.exists(commitrejectbins[si])); - assert(os.rmdir(blockedasm) == 0); - rewritefile(traces[si], ""); - runcommandenv(root, strings.concat("name-commit-reconsider-", stages[si]), - commitrejectav, env, - (120i64 * (time.second: i64)): time.duration, &out); - expectexit(&out, 1); - assert(has(out.stderr, "undefined: wire")); - let ncommitbinding: str = normalizedtrace(out.stderr, - strings.concat(commitworks[si], "/"), commitrejectbins[si]); - if (si == 0) { commitbindingref = strings.dup(ncommitbinding); } - else { assert(same(commitbindingref, ncommitbinding)); }; - assert(has(readfile(traces[si]), "/cmd.commit.unit.new")); - assert(os.exists(strings.concat(commitworks[si], - "/commit.codec.unit.ww"))); - assert(!os.exists(strings.concat(commitworks[si], - "/cmd.commit.unit.ww"))); - assert(!os.exists(commitrejectbins[si])); rewritefile(commitappfile, strings.concat( "package main;\nimport commit.codec;\n", "fn main() i32 = { return cable.value(); };\n")); @@ -8285,6 +8329,10 @@ fn runtimepath(relative: str) str = { runcommand(root, strings.concat("name-commit-fixed-run-", stages[si]), commitfixedrun, (30i64 * (time.second: i64)): time.duration, &out); expectexit(&out, 42); + assert(has(readfile(strings.concat(commitworks[si], + "/commit.codec.wwi")), + "//ww:module commit.codec\npackage cable;\n")); + assert(same(commitcoldbytes, readfile(commitcoldbins[si]))); let commitbinbytes: str = readfile(commitfixedbins[si]); if (si == 0) { commitbinref = strings.dup(commitbinbytes); } else { assert(same(commitbinref, commitbinbytes)); }; @@ -8614,10 +8662,12 @@ fn runtimepath(relative: str) str = { let one: str = strings.concat(source, "/pkg/one"); let two: str = strings.concat(source, "/pkg/two"); let positives: []str = ["default", "alias", "reuse", "repeatok", - "repeatedfiles"]; + "repeatedfiles", "blankalias", "blankrepeat", "blankdefault", + "blankexplicit", "blankfiles"]; let negatives: []str = ["barefn", "baretype", "baredef", "bareconst", "barevar", "aliasbad", "aliasleak", "repeatdup", "aliasdup", - "defaultalias", "declcollision", "unusedalias", "blankalias"]; + "defaultalias", "declcollision", "unusedalias", "blankqual", + "blankbare", "blankordinaryunused"]; let dirs: []str = [one, two]; let i: i32 = 0; for (i < positives.len) { @@ -8673,6 +8723,24 @@ fn runtimepath(relative: str) str = { "fn second() i32 = { return right.value(); };\n")); writefile(strings.concat(source, "/cmd/repeatedfiles/main.ww"), "package main;\nfn main() i32 = { return first() + second(); };\n"); + writefile(strings.concat(source, "/cmd/blankalias/main.ww"), + "package main;\nimport _ pkg.one;\nfn main() i32 = { return 0; };\n"); + writefile(strings.concat(source, "/cmd/blankrepeat/main.ww"), + strings.concat("package main;\nimport _ pkg.one;\n", + "import _ pkg.one;\nfn main() i32 = { return 0; };\n")); + writefile(strings.concat(source, "/cmd/blankdefault/main.ww"), + strings.concat("package main;\nimport _ pkg.one;\nimport pkg.one;\n", + "fn main() i32 = { return wire.value(); };\n")); + writefile(strings.concat(source, "/cmd/blankexplicit/main.ww"), + strings.concat("package main;\nimport _ pkg.one;\n", + "import stable pkg.one;\n", + "fn main() i32 = { return stable.value(); };\n")); + writefile(strings.concat(source, "/cmd/blankfiles/a.ww"), + "package main;\nimport _ pkg.one;\n"); + writefile(strings.concat(source, "/cmd/blankfiles/b.ww"), + "package main;\nimport _ pkg.one;\n"); + writefile(strings.concat(source, "/cmd/blankfiles/main.ww"), + "package main;\nfn main() i32 = { return 0; };\n"); let bareexprs: []str = ["value()", "take(7)", "DEFINED", "CONSTANT", "VARIABLE"]; @@ -8716,22 +8784,29 @@ fn runtimepath(relative: str) str = { "fn main() i32 = { return stable(); };\n")); writefile(strings.concat(source, "/cmd/unusedalias/main.ww"), "package main;\nimport stable pkg.one;\nfn main() i32 = { return 0; };\n"); - writefile(strings.concat(source, "/cmd/blankalias/main.ww"), - "package main;\nimport _ pkg.one;\nfn main() i32 = { return 0; };\n"); + writefile(strings.concat(source, "/cmd/blankqual/main.ww"), + strings.concat("package main;\nimport _ pkg.one;\n", + "fn main() i32 = { return wire.value(); };\n")); + writefile(strings.concat(source, "/cmd/blankbare/main.ww"), + strings.concat("package main;\nimport _ pkg.one;\n", + "fn main() i32 = { return value(); };\n")); + writefile(strings.concat(source, "/cmd/blankordinaryunused/main.ww"), + strings.concat("package main;\nimport _ pkg.one;\nimport pkg.one;\n", + "fn main() i32 = { return 0; };\n")); let stages: []str = ["ww", "ww_ww"]; - let expected: []i32 = [83, 83, 40, 33, 22]; - let binaryrefs: []str = ["", "", "", "", ""]; + let expected: []i32 = [83, 83, 40, 33, 22, 0, 0, 11, 11, 0]; + let binaryrefs: []str = ["", "", "", "", "", "", "", "", "", ""]; let diagrefs: []str = ["", "", "", "", "", "", "", "", "", - "", "", "", ""]; + "", "", "", "", "", ""]; let needles: []str = ["undefined: value", "unknown type 'Thing'", "undefined: DEFINED", "undefined: CONSTANT", "undefined: VARIABLE", "unknown type 'wire.Thing'", "package 'stable' is not directly imported", "wire redeclared in this block", "stable redeclared in this block", "wire redeclared in this block", "stable already declared through import of package stable", - "\"pkg.one\" imported as stable and not used", - "blank import alias _ is not implemented"]; + "\"pkg.one\" imported as stable and not used", "undefined: wire", + "undefined: value", "\"pkg.one\" imported as wire and not used"]; let exactdiags: []str = [ strings.concat( "$WORK/cmd.barefn.unit.new:3:1: error: \"pkg.one\" imported as wire and not used\n", @@ -8777,8 +8852,7 @@ fn runtimepath(relative: str) str = { strings.concat( "$WORK/cmd.unusedalias.unit.new:3:1: error: \"pkg.one\" imported as stable and not used\n", "ww: w6c failed for cmd.unusedalias\n"), - strings.concat(source, - "/cmd/blankalias/main.ww:2:8: error: blank import alias _ is not implemented\n"), + "", "", "", ]; let si: i32 = 0; for (si < stages.len) { @@ -8820,6 +8894,26 @@ fn runtimepath(relative: str) str = { let repeatedunit: str = readfile(strings.concat(work, "/cmd.repeatedfiles.unit.ww")); assert(occurrences(repeatedunit, "pkg.one;") == 2); + let blankunit: str = readfile(strings.concat(work, + "/cmd.blankalias.unit.ww")); + assert(has(blankunit, "import _ pkg.one;")); + let blankwwi: str = readfile(strings.concat(work, + "/cmd.blankalias.wwi")); + assert(!has(blankwwi, "import _") && !has(blankwwi, "__ww..")); + let blankrepeat: str = readfile(strings.concat(work, + "/cmd.blankrepeat.unit.ww")); + assert(occurrences(blankrepeat, "import _ pkg.one;") == 2); + assert(occurrences(blankrepeat, + "//ww:direct-export pkg.one ") == 1); + let blankfiles: str = readfile(strings.concat(work, + "/cmd.blankfiles.unit.ww")); + assert(occurrences(blankfiles, "import _ pkg.one;") == 2); + assert(occurrences(blankfiles, + "//ww:direct-export pkg.one ") == 1); + let blankdispatch: str = readfile(strings.concat(work, + "/cmd.blankdefault.init.unit.ww")); + assert(occurrences(blankdispatch, + "//ww:init-call __ww..pkg.p.pkg.one.v0.r0.init") == 1); i = 0; for (i < negatives.len) { diff --git a/test/sep/limitexec.c b/test/sep/limitexec.c new file mode 100644 index 00000000..9a6a70b4 --- /dev/null +++ b/test/sep/limitexec.c @@ -0,0 +1,36 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include + +/* Test-only deterministic launcher for allocation-failure observers. Keeping + * setrlimit in a repository-built helper avoids depending on util-linux paths + * or shell-specific ulimit syntax. */ +int +main(int argc, char **argv) +{ + if (argc < 3) { + fputs("usage: sep-limitexec bytes program [arg ...]\n", stderr); + return 2; + } + errno = 0; + char *end = NULL; + unsigned long long value = strtoull(argv[1], &end, 10); + if (errno != 0 || end == argv[1] || *end != '\0') { + fputs("sep-limitexec: invalid limit\n", stderr); + return 2; + } + struct rlimit limit; + limit.rlim_cur = (rlim_t)value; + limit.rlim_max = (rlim_t)value; + if (setrlimit(RLIMIT_AS, &limit) != 0) { + fputs("sep-limitexec: setrlimit failed\n", stderr); + return 2; + } + execv(argv[2], &argv[2]); + fputs("sep-limitexec: exec failed\n", stderr); + return 127; +} diff --git a/test/sep/sepinit_test.ww b/test/sep/sepinit_test.ww new file mode 100644 index 00000000..8d2aa387 --- /dev/null +++ b/test/sep/sepinit_test.ww @@ -0,0 +1,1421 @@ +package sepinit_test; + +// Go-1.26.5-aligned blank-import/package-initialization observer. All source +// trees are temporary and every semantic/artifact leg runs through both the C +// and WW drivers with independent work roots. + +import os; +import os.exec; +import strconv; +import strings; +import testenv; +import time; + +fn fail(label: str, why: str) void = { + let m: str = strings.concat("sepinit FAIL: ", label, " -- ", why, + "\n"); + os.write(2, m.ptr, m.len: u64); + assert(false); +}; + +fn tmo() time.duration = { + return (240i64 * (time.second: i64)): time.duration; +}; + +fn replacefile(path: str, body: str) void = { + assert(os.remove(path) == 0); + testenv.writefile(path, body); +}; + +fn run(dir: str, name: str, argv: []str, out: *testenv.commandout) void = { + testenv.runcommand(dir, dir, name, argv, tmo(), out); +}; + +fn runenv(dir: str, name: str, argv: []str, env: []str, + out: *testenv.commandout) void = { + testenv.runcommandenv(dir, dir, name, argv, env, tmo(), out); +}; + +fn allocpkgname(n: i32) str = { + assert(n >= 0 && n < 1000); + let out: []u8 = alloc([], 4u64)!; + append(out, 'p'); + append(out, ('0': i32 + (n / 100)): u8); + append(out, ('0': i32 + ((n / 10) % 10)): u8); + append(out, ('0': i32 + (n % 10)): u8); + return strings.frombytes(out); +}; + +fn compilertraceenv(wrapper: str, real: str, trace: str) []str = { + let base: []str = os.getenvs(); + let env: []str = alloc([], (base.len + 3): u64)!; + let i: i32 = 0; + for (i < base.len) { + if (!strings.hasprefix(base[i], "WW_W6C=") + && !strings.hasprefix(base[i], "WW_SEPINIT_REAL_W6C=") + && !strings.hasprefix(base[i], "WW_SEPINIT_W6C_TRACE=")) { + append(env, base[i]); + }; + i += 1; + }; + append(env, strings.concat("WW_W6C=", wrapper)); + append(env, strings.concat("WW_SEPINIT_REAL_W6C=", real)); + append(env, strings.concat("WW_SEPINIT_W6C_TRACE=", trace)); + return env; +}; + +fn exitcode(out: *testenv.commandout) i32 = { + if (out.termination != exec.termination.EXIT) { return -1; }; + return out.code; +}; + +fn normalized(s: str, path: str) str = { + match (strings.replace(s, path, "$WORK")) { + case let v: str => return v; + case nomem => abort("normalize allocation"); + }; +}; + +fn hasat(s: str, off: i32, want: str) bool = { + if (off < 0 || off + want.len > s.len) { return false; }; + let i: i32 = 0; + for (i < want.len) { + if (s[off + i] != want[i]) { return false; }; + i += 1; + }; + return true; +}; + +fn arsize(s: str, off: i32) i32 = { + let n: i32 = 0; + let seen: bool = false; + let i: i32 = 0; + for (i < 10) { + let c: u8 = s[off + i]; + if (c >= '0' && c <= '9') { + seen = true; + n = n * 10 + ((c - '0'): i32); + } else { if (c != ' ') { return -1; }; }; + i += 1; + }; + if (!seen) { return -1; }; + return n; +}; + +fn checkmember(label: str, ar: str, off: i32, name: str) i32 = { + if (!hasat(ar, off, name) || !hasat(ar, off + 16, "0") + || !hasat(ar, off + 28, "0") || !hasat(ar, off + 34, "0") + || !hasat(ar, off + 40, "100644") + || !hasat(ar, off + 58, "`\n")) { + fail(label, strings.concat("bad deterministic ar header for ", name)); + }; + let n: i32 = arsize(ar, off + 48); + if (n < 0) { fail(label, "bad ar member size"); }; + return off + 60 + n + (n & 1); +}; + +fn checkarchives(label: str, root: str, dep: str) void = { + let ar: str = testenv.readfile(root); + if (!hasat(ar, 0, "!\n")) { fail(label, "root ar magic"); }; + let next: i32 = checkmember(label, ar, 8, "pkg.o/"); + next = checkmember(label, ar, next, "init.o/"); + if (next != ar.len) { fail(label, "root archive has extra members"); }; + let dar: str = testenv.readfile(dep); + if (!hasat(dar, 0, "!\n")) { fail(label, "dep ar magic"); }; + next = checkmember(label, dar, 8, "pkg.o/"); + if (next != dar.len) { fail(label, "dependency archive is not one-member"); }; +}; + +fn comparefile(label: str, a: str, b: str) void = { + if (!testenv.same(testenv.readfile(a), testenv.readfile(b))) { + fail(label, strings.concat("stage bytes differ: ", a)); + }; +}; + +@test fn runtime_order_artifacts_and_cycles() void = { + let td: str = testenv.fresh(); + let names: []str = ["a", "b", "c", "d", "graph", "vars", "cycle", + "cycles"]; + let ni: i32 = 0; + for (ni < names.len) { + assert(os.mkdir(strings.concat(td, "/", names[ni]), 493) == 0); + ni += 1; + }; + testenv.writefile(strings.concat(td, "/d/d.ww"), strings.concat( + "package d;\nimport os;\n", + "fn init() void = { os.write(1, \"D\".ptr, 1u64); };\n", + "fn init() void = { os.write(1, \"d\".ptr, 1u64); };\n")); + testenv.writefile(strings.concat(td, "/d/z.ww"), strings.concat( + "package d;\nimport os;\n", + "fn init() void = { os.write(1, \"!\".ptr, 1u64); };\n")); + testenv.writefile(strings.concat(td, "/a/a.ww"), strings.concat( + "package a;\nimport _ d;\nimport os;\n", + "fn init() void = { os.write(1, \"A\".ptr, 1u64); };\n")); + testenv.writefile(strings.concat(td, "/b/b.ww"), strings.concat( + "package b;\nimport _ d;\nimport os;\n", + "fn init() void = { os.write(1, \"B\".ptr, 1u64); };\n")); + testenv.writefile(strings.concat(td, "/c/c.ww"), strings.concat( + "package c;\nimport os;\n", + "fn init() void = { os.write(1, \"C\".ptr, 1u64); };\n", + "fn init() void = { os.write(1, \"c\".ptr, 1u64); };\n")); + testenv.writefile(strings.concat(td, "/graph/main.ww"), strings.concat( + "package main;\nimport _ c;\nimport _ b;\nimport _ a;\nimport os;\n", + "fn main() i32 = { os.write(1, \"M\".ptr, 1u64); return 0; };\n")); + testenv.writefile(strings.concat(td, "/vars/a.ww"), strings.concat( + "package main;\nimport os;\n", + "let A: i32 = makea();\n", + "fn makea() i32 = { os.write(1, \"A\".ptr, 1u64); return B + 1; };\n", + "let B: i32 = makeb();\n", + "fn makeb() i32 = { os.write(1, \"B\".ptr, 1u64); return 4; };\n", + "let P: *i64 = alloc(7i64)!;\n", + "fn init() void = { assert(*P == 7i64); os.write(1, \"1\".ptr, 1u64); };\n")); + testenv.writefile(strings.concat(td, "/vars/z.ww"), strings.concat( + "package main;\nimport os;\n", + "fn init() void = { os.write(1, \"2\".ptr, 1u64); };\n", + "fn main() i32 = { os.write(1, \"M\".ptr, 1u64); return A - 5; };\n")); + testenv.writefile(strings.concat(td, "/cycle/main.ww"), strings.concat( + "package main;\n", + "let A: i32 = geta();\n", + "fn geta() i32 = { return B; };\n", + "let B: i32 = getb();\n", + "fn getb() i32 = { return A; };\n", + "fn main() i32 = { return 0; };\n")); + testenv.writefile(strings.concat(td, "/cycles/main.ww"), strings.concat( + "package main;\n", + "let Tail: i32 = tail();\nfn tail() i32 = { return B; };\n", + "let B: i32 = getb();\nfn getb() i32 = { return C; };\n", + "let C: i32 = getc();\nfn getc() i32 = { return B; };\n", + "let D: i32 = getd();\nfn getd() i32 = { return D; };\n", + "fn main() i32 = { return 0; };\n")); + + let stages: []str = ["ww", "ww_ww"]; + let tags: []str = ["c", "ww"]; + let works: []str = [strings.concat(td, "/work-c"), + strings.concat(td, "/work-ww")]; + let progs: []str = [strings.concat(td, "/graph-c"), + strings.concat(td, "/graph-ww")]; + let varprogs: []str = [strings.concat(td, "/vars-c"), + strings.concat(td, "/vars-ww")]; + let i: i32 = 0; + for (i < 2) { + assert(os.mkdir(works[i], 493) == 0); + let av: []str = [testenv.driver(stages[i]), "build", "-w", works[i], + "-I", td, "-o", progs[i], strings.concat(td, "/graph")]; + let out: testenv.commandout; + run(td, strings.concat("graph-build-", tags[i]), av, &out); + if (exitcode(&out) != 0) { fail("graph", out.stderr); }; + let rav: []str = [progs[i]]; + run(td, strings.concat("graph-run-", tags[i]), rav, &out); + if (exitcode(&out) != 0 || !testenv.same(out.stdout, "CcDd!ABM")) { + fail("graph", strings.concat("runtime order: ", out.stdout)); + }; + let vav: []str = [testenv.driver(stages[i]), "build", "-w", works[i], + "-I", td, "-o", varprogs[i], strings.concat(td, "/vars")]; + run(td, strings.concat("vars-build-", tags[i]), vav, &out); + if (exitcode(&out) != 0) { fail("vars", out.stderr); }; + let vrav: []str = [varprogs[i]]; + run(td, strings.concat("vars-run-", tags[i]), vrav, &out); + if (exitcode(&out) != 0 || !testenv.same(out.stdout, "BA12M")) { + fail("vars", strings.concat("runtime order: ", out.stdout)); + }; + let dispatch: str = testenv.readfile(strings.concat(works[i], + "/graph.init.s")); + let cs: str = "CALL\t__ww..pkg.p.c.v0.r0.init(SB)"; + let ds: str = "CALL\t__ww..pkg.p.d.v0.r0.init(SB)"; + let acall: str = "CALL\t__ww..pkg.p.a.v0.r0.init(SB)"; + let bs: str = "CALL\t__ww..pkg.p.b.v0.r0.init(SB)"; + let rs: str = "CALL\t__ww..pkg.p.graph.v0.r0.init(SB)"; + if (!(testenv.pos(dispatch, cs) < testenv.pos(dispatch, ds) + && testenv.pos(dispatch, ds) < testenv.pos(dispatch, acall) + && testenv.pos(dispatch, acall) < testenv.pos(dispatch, bs) + && testenv.pos(dispatch, bs) < testenv.pos(dispatch, rs))) { + fail("graph", "dispatcher is not lexical-ready dependency order"); + }; + if (testenv.occurrences(dispatch, ds) != 1 + || testenv.occurrences(dispatch, acall) != 1 + || testenv.occurrences(dispatch, bs) != 1) { + fail("graph", "diamond task not exactly once"); + }; + let roots: str = testenv.readfile(strings.concat(works[i], "/graph.s")); + let deps: str = testenv.readfile(strings.concat(works[i], "/d.s")); + if (testenv.occurrences(roots, "CALL\t__ww..dispatch(SB)") != 1 + || testenv.has(deps, "__ww..dispatch")) { + fail("graph", "dispatcher call ownership"); + }; + let iface: str = testenv.readfile(strings.concat(works[i], "/vars.wwi")); + if (testenv.has(iface, "fn init") || testenv.has(iface, "__ww..")) { + fail("vars", "private initialization leaked to export"); + }; + checkarchives("graph", strings.concat(works[i], "/graph.a"), + strings.concat(works[i], "/d.a")); + i += 1; + }; + let suffixes: []str = [".unit.ww", ".wwi", ".s", ".o", ".a"]; + let owners: []str = ["a", "b", "c", "d", "graph", "vars"]; + i = 0; + for (i < owners.len) { + let j: i32 = 0; + for (j < suffixes.len) { + comparefile("stage-byte-id", + strings.concat(works[0], "/", owners[i], suffixes[j]), + strings.concat(works[1], "/", owners[i], suffixes[j])); + j += 1; + }; + i += 1; + }; + let initnames: []str = ["graph.init.unit.ww", "graph.init.s", + "graph.init.o", "vars.init.unit.ww", "vars.init.s", "vars.init.o"]; + i = 0; + for (i < initnames.len) { + comparefile("stage-byte-id", strings.concat(works[0], "/", initnames[i]), + strings.concat(works[1], "/", initnames[i])); + i += 1; + }; + comparefile("stage-byte-id", progs[0], progs[1]); + comparefile("stage-byte-id", varprogs[0], varprogs[1]); + + let cerr: str = ""; + i = 0; + for (i < 2) { + let prog: str = strings.concat(td, "/cycle-", tags[i]); + let av: []str = [testenv.driver(stages[i]), "build", "-I", td, + "-o", prog, strings.concat(td, "/cycle")]; + let out: testenv.commandout; + run(td, strings.concat("cycle-build-", tags[i]), av, &out); + if (exitcode(&out) == 0 || testenv.exists(prog)) { + fail("cycle", "accepted or published"); + }; + let work: str = strings.concat(prog, ".sepwork"); + let diag: str = normalized(out.stderr, work); + if (!testenv.has(diag, "error: initialization cycle for A\n") + || !testenv.has(diag, "A refers to B\n") + || !testenv.has(diag, "B refers to A\n")) { + fail("cycle", diag); + }; + if (i == 0) { cerr = strings.dup(diag); } + else { if (!testenv.same(cerr, diag)) { fail("cycle", "stage diagnostics"); }; }; + if (testenv.exists(work)) { + fail("cycle", "cold rejection retained its scratch tree"); + }; + i += 1; + }; + let multiref: str = ""; + i = 0; + for (i < 2) { + let prog: str = strings.concat(td, "/cycles-", tags[i]); + let av: []str = [testenv.driver(stages[i]), "build", "-I", td, + "-o", prog, strings.concat(td, "/cycles")]; + let out: testenv.commandout; + run(td, strings.concat("cycles-build-", tags[i]), av, &out); + let work: str = strings.concat(prog, ".sepwork"); + let diag: str = normalized(out.stderr, work); + let bc: i32 = testenv.pos(diag, "error: initialization cycle for B\n"); + let dd: i32 = testenv.pos(diag, + "error: initialization cycle: D refers to itself\n"); + if (exitcode(&out) == 0 || testenv.exists(prog) || bc < 0 || dd <= bc + || !testenv.has(diag, "B refers to C\n") + || !testenv.has(diag, "C refers to B\n") + || testenv.has(diag, "initialization cycle for Tail") + || testenv.exists(work)) { + fail("cycles", diag); + }; + if (i == 0) { multiref = strings.dup(diag); } + else { if (!testenv.same(multiref, diag)) { + fail("cycles", "C/WW multiple-cycle diagnostics"); + }; }; + i += 1; + }; + testenv.clean(td); +}; + +@test fn runtime_aggregate_initializers() void = { + let td: str = testenv.fresh(); + let pkg: str = strings.concat(td, "/agg"); + assert(os.mkdir(pkg, 493) == 0); + testenv.writefile(strings.concat(pkg, "/main.ww"), strings.concat( + "package main;\n", + "let seq: i64;\n", + "fn next() i64 = { seq += 1; return seq; };\n", + "fn word() str = { seq += 1; return \"ok\"; };\n", + "type box = struct { nums: [2][2]i64, names: []str, };\n", + "let nested: [2][2]i64 = [[next(), next()], [next(), next()]];\n", + "let names: []str = [word(), word()];\n", + "let wide: (i64, i64, i64, i64, i64) = ", + "(next(), next(), next(), next(), next());\n", + "let value: box = box{ nums = [[next(), next()], ", + "[next(), next()]], names = [word(), word()], };\n", + "type zeroelem = struct {};\n", + "type zerobox = struct { tick: i64, empty: []u8, zeros: []zeroelem, };\n", + "let zero: zerobox = zerobox{ tick = next(), empty = [], ", + "zeros = [zeroelem{}, zeroelem{}], };\n", + "fn main() i32 = {\n", + " let junk: [32]str = [\"bad\"...];\n", + " assert(junk[31] == \"bad\");\n", + " assert(nested[0][0] == 1 && nested[1][1] == 4);\n", + " assert(names.len == 2 && names[0] == \"ok\" ", + "&& names[1] == \"ok\");\n", + " assert(wide.0 == 7 && wide.4 == 11);\n", + " assert(value.nums[0][0] == 12 && value.nums[1][1] == 15);\n", + " assert(value.names.len == 2 && value.names[0] == \"ok\" ", + "&& value.names[1] == \"ok\");\n", + " assert(zero.tick == 18 && zero.empty.ptr != nil ", + "&& zero.empty.len == 0 && zero.empty.cap == 0);\n", + " assert(zero.zeros.ptr != nil && zero.zeros.len == 2 ", + "&& zero.zeros.cap == 2);\n", + " assert(seq == 18); return 0;\n", + "};\n")); + let stages: []str = ["ww", "ww_ww"]; + let tags: []str = ["c", "ww"]; + let works: []str = [strings.concat(td, "/work-c"), + strings.concat(td, "/work-ww")]; + let progs: []str = [strings.concat(td, "/agg-c"), + strings.concat(td, "/agg-ww")]; + let i: i32 = 0; + for (i < 2) { + assert(os.mkdir(works[i], 493) == 0); + let av: []str = [testenv.driver(stages[i]), "build", "-w", works[i], + "-I", td, "-o", progs[i], pkg]; + let out: testenv.commandout; + run(td, strings.concat("aggregate-build-", tags[i]), av, &out); + if (exitcode(&out) != 0) { fail("aggregate", out.stderr); }; + let rav: []str = [progs[i]]; + run(td, strings.concat("aggregate-run-", tags[i]), rav, &out); + if (exitcode(&out) != 0) { + fail("aggregate", strings.concat("runtime failed: ", out.stderr)); + }; + let asm: str = testenv.readfile(strings.concat(works[i], "/agg.s")); + let own: str = "__ww..pkg.p.agg.v0.r0.init"; + if (testenv.occurrences(asm, + strings.concat(own, ".v.2.b.1")) != 3 + || testenv.occurrences(asm, + strings.concat(own, ".v.4.b.1")) != 3 + || testenv.occurrences(asm, + strings.concat(own, ".v.5.b.1")) != 2 + || testenv.occurrences(asm, + strings.concat(own, ".v.5.b.2")) != 2) { + fail("aggregate", "canonical slice backing ownership"); + }; + let iface: str = testenv.readfile(strings.concat(works[i], "/agg.wwi")); + if (testenv.has(iface, "__ww..") || testenv.has(iface, "fn init")) { + fail("aggregate", "runtime backing leaked to semantic export"); + }; + i += 1; + }; + let files: []str = ["agg.unit.ww", "agg.wwi", "agg.s", "agg.o", + "agg.a", "agg.init.unit.ww", "agg.init.s", "agg.init.o"]; + i = 0; + for (i < files.len) { + comparefile("aggregate-stage-byte-id", + strings.concat(works[0], "/", files[i]), + strings.concat(works[1], "/", files[i])); + i += 1; + }; + comparefile("aggregate-stage-byte-id", progs[0], progs[1]); + testenv.clean(td); +}; + +@test fn blank_import_resolution_and_visibility() void = { + let td: str = testenv.fresh(); + let dirs: []str = [ + "/domain", "/domain/internal", "/domain/internal/secret", + "/domain/client", "/domain/app", "/domain/app/client", + "/domain/app/vendor", "/domain/app/vendor/lib", + "/domain/app/vendor/lib/side", "/outside", "/outsidevendor", + "/program", "/importprogram", "/blankmissing", "/selfpkg", + "/cyclea", "/cycleb", "/tools", + ]; + let di: i32 = 0; + for (di < dirs.len) { + assert(os.mkdir(strings.concat(td, dirs[di]), 493) == 0); + di += 1; + }; + testenv.writefile(strings.concat(td, "/domain/internal/secret/secret.ww"), + strings.concat("package hiddenname;\nimport os;\n", + "fn init() void = { os.write(1, \"I\".ptr, 1u64); };\n")); + testenv.writefile(strings.concat(td, "/domain/client/main.ww"), + strings.concat("package main;\nimport _ domain.internal.secret;\n", + "import os;\nfn main() i32 = { os.write(1, \"M\".ptr, 1u64); return 0; };\n")); + testenv.writefile(strings.concat(td, "/outside/main.ww"), + "package main;\nimport _ domain.internal.secret;\nfn main() i32 = { return 0; };\n"); + testenv.writefile(strings.concat(td, "/domain/app/vendor/lib/side/side.ww"), + strings.concat("package localname;\nimport os;\n", + "fn init() void = { os.write(1, \"V\".ptr, 1u64); };\n")); + testenv.writefile(strings.concat(td, "/domain/app/client/main.ww"), + strings.concat("package main;\nimport _ lib.side;\nimport os;\n", + "fn main() i32 = { os.write(1, \"M\".ptr, 1u64); return 0; };\n")); + testenv.writefile(strings.concat(td, "/outsidevendor/main.ww"), + "package main;\nimport _ domain.app.vendor.lib.side;\nfn main() i32 = { return 0; };\n"); + testenv.writefile(strings.concat(td, "/program/main.ww"), + "package main;\nfn main() i32 = { return 0; };\n"); + testenv.writefile(strings.concat(td, "/importprogram/main.ww"), + "package main;\nimport _ program;\nfn main() i32 = { return 0; };\n"); + testenv.writefile(strings.concat(td, "/blankmissing/main.ww"), + "package main;\nimport _ nowhere;\nfn main() i32 = { return 0; };\n"); + testenv.writefile(strings.concat(td, "/selfpkg/self.ww"), + "package selfpkg;\nimport _ selfpkg;\n"); + testenv.writefile(strings.concat(td, "/cyclea/a.ww"), + "package cyclea;\nimport _ cycleb;\n"); + testenv.writefile(strings.concat(td, "/cycleb/b.ww"), + "package cycleb;\nimport _ cyclea;\n"); + + let wrapper: str = strings.concat(td, "/tools/w6c-wrapper.sh"); + testenv.writeexecutable(wrapper, strings.concat( + "#!/bin/sh\n", + "printf 'BEGIN' >> \"$WW_SEPINIT_W6C_TRACE\"\n", + "for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ", + "\"$WW_SEPINIT_W6C_TRACE\"; done\n", + "printf '\\n' >> \"$WW_SEPINIT_W6C_TRACE\"\n", + "exec \"$WW_SEPINIT_REAL_W6C\" \"$@\"\n")); + let stages: []str = ["ww", "ww_ww"]; + let compilers: []str = ["w6c", "w6c_ww"]; + let tags: []str = ["c", "ww"]; + let diagrefs: []str = ["", "", "", "", "", ""]; + let si: i32 = 0; + for (si < stages.len) { + let work: str = strings.concat(td, "/work-", tags[si]); + assert(os.mkdir(work, 493) == 0); + let trace: str = strings.concat(td, "/compiler-", tags[si], ".trace"); + let env: []str = compilertraceenv(wrapper, + testenv.driver(compilers[si]), trace); + let internalbin: str = strings.concat(td, "/internal-", tags[si]); + testenv.writefile(trace, ""); + let av: []str = [testenv.driver(stages[si]), "build", "-w", work, + "-I", td, "-o", internalbin, + strings.concat(td, "/domain/client")]; + let out: testenv.commandout; + testenv.runcommandenv(td, td, strings.concat("blank-internal-build-", + tags[si]), av, env, tmo(), &out); + if (exitcode(&out) != 0) { fail("blank-internal", out.stderr); }; + let rav: []str = [internalbin]; + run(td, strings.concat("blank-internal-run-", tags[si]), rav, &out); + if (exitcode(&out) != 0 || !testenv.same(out.stdout, "IM")) { + fail("blank-internal", out.stdout); + }; + + let vendorbin: str = strings.concat(td, "/vendor-", tags[si]); + replacefile(trace, ""); + let vav: []str = [testenv.driver(stages[si]), "build", "-w", work, + "-I", td, "-o", vendorbin, + strings.concat(td, "/domain/app/client")]; + testenv.runcommandenv(td, td, strings.concat("blank-vendor-build-", + tags[si]), vav, env, tmo(), &out); + if (exitcode(&out) != 0) { fail("blank-vendor", out.stderr); }; + let vrav: []str = [vendorbin]; + run(td, strings.concat("blank-vendor-run-", tags[si]), vrav, &out); + if (exitcode(&out) != 0 || !testenv.same(out.stdout, "VM")) { + fail("blank-vendor", out.stdout); + }; + let compilerlog: str = testenv.readfile(trace); + if (!testenv.has(compilerlog, + "<--import><") + || !testenv.has(compilerlog, + "<--import-map>") + || testenv.has(compilerlog, "<_>")) { + fail("blank-vendor", compilerlog); + }; + let unit: str = testenv.readfile(strings.concat(work, + "/domain.app.client.unit.ww")); + if (!testenv.has(unit, "import _ lib.side;") + || !testenv.has(unit, + "//ww:import-map lib.side domain.app.vendor.lib.side ")) { + fail("blank-vendor", "owner unit lost blank/vendor identity"); + }; + let dispatch: str = testenv.readfile(strings.concat(work, + "/domain.app.client.init.unit.ww")); + if (testenv.occurrences(dispatch, + "__ww..pkg.p.domain.app.vendor.lib.side.v0.r0.init") != 1 + || testenv.has(dispatch, "localname")) { + fail("blank-vendor", "dispatcher identity used a local name"); + }; + + let rejectdirs: []str = ["/outside", "/outsidevendor", + "/importprogram", "/blankmissing", "/selfpkg", "/cyclea"]; + let rejectlabels: []str = ["internal", "vendor", "main", "missing", + "self", "cycle"]; + let needles: []str = ["use of internal package domain.internal.secret not allowed", + "use of vendored package not allowed", + "package program is a program, not an importable package", + "cannot find package nowhere", + "self-import: package 'selfpkg' cannot import itself", + "ww: dependency cycle: cyclea -> cycleb -> cyclea"]; + let ri: i32 = 0; + for (ri < rejectdirs.len) { + replacefile(trace, ""); + let rejected: str = strings.concat(td, "/reject-", tags[si], "-", + rejectlabels[ri]); + let rejectav: []str = [testenv.driver(stages[si]), "build", "-w", work, + "-I", td, "-o", rejected, + strings.concat(td, rejectdirs[ri])]; + testenv.runcommandenv(td, td, strings.concat("blank-reject-", + tags[si], "-", rejectlabels[ri]), rejectav, env, tmo(), &out); + if (exitcode(&out) == 0 || testenv.exists(rejected) + || !testenv.has(out.stderr, needles[ri]) + || testenv.readfile(trace).len != 0) { + fail("blank-visibility", out.stderr); + }; + let diag: str = normalized(out.stderr, work); + if (si == 0) { diagrefs[ri] = strings.dup(diag); } + else { if (!testenv.same(diagrefs[ri], diag)) { + fail("blank-visibility", "stage diagnostic mismatch"); + }; }; + ri += 1; + }; + si += 1; + }; + testenv.clean(td); +}; + +@test fn direct_recursive_and_reordered_roots_match() void = { + let td: str = testenv.fresh(); + let tree: str = strings.concat(td, "/tree"); + let dep: str = strings.concat(tree, "/dep"); + let cmd: str = strings.concat(tree, "/cmd"); + assert(os.mkdir(tree, 493) == 0); + assert(os.mkdir(dep, 493) == 0); + assert(os.mkdir(cmd, 493) == 0); + testenv.writefile(strings.concat(dep, "/dep.ww"), strings.concat( + "package hidden;\nimport os;\n", + "fn init() void = { os.write(1, \"D\".ptr, 1u64); };\n")); + testenv.writefile(strings.concat(cmd, "/main.ww"), strings.concat( + "package main;\nimport _ dep;\nimport os;\n", + "fn main() i32 = { os.write(1, \"M\".ptr, 1u64); return 0; };\n")); + let files: []str = ["dep.unit.ww", "dep.wwi", "dep.s", "dep.o", + "dep.a", "cmd.unit.ww", "cmd.wwi", "cmd.s", "cmd.o", "cmd.a", + "cmd.init.unit.ww", "cmd.init.s", "cmd.init.o"]; + let refs: []str = alloc([], files.len: u64)!; + let fi: i32 = 0; + for (fi < files.len) { append(refs, ""); fi += 1; }; + let binref: str = ""; + let stages: []str = ["ww", "ww_ww"]; + let tags: []str = ["c", "ww"]; + let mode: i32 = 0; + for (mode < 2) { + let si: i32 = 0; + for (si < stages.len) { + let mt: str = "direct"; + if (mode != 0) { mt = "recursive"; }; + let work: str = strings.concat(td, "/work-", mt, "-", tags[si]); + let outdir: str = strings.concat(td, "/out-", mt, "-", tags[si]); + assert(os.mkdir(work, 493) == 0); + assert(os.mkdir(outdir, 493) == 0); + let out: testenv.commandout; + if (mode == 0 && si == 0) { + let av: []str = [testenv.driver(stages[si]), "build", "-w", + work, "-I", tree, "-o", outdir, cmd, dep]; + run(td, "direct-c-cmd-first", av, &out); + } else { if (mode == 0) { + let av: []str = [testenv.driver(stages[si]), "build", "-w", + work, "-I", tree, "-o", outdir, dep, cmd]; + run(td, "direct-ww-dep-first", av, &out); + } else { + let av: []str = [testenv.driver(stages[si]), "build", "-w", + work, "-I", tree, "-o", outdir, + strings.concat(tree, "/...")]; + run(td, strings.concat("recursive-", tags[si]), av, &out); + }; }; + if (exitcode(&out) != 0) { fail("direct-recursive", out.stderr); }; + let bin: str = strings.concat(outdir, "/cmd"); + let rav: []str = [bin]; + run(td, strings.concat("direct-recursive-run-", mt, "-", tags[si]), + rav, &out); + if (exitcode(&out) != 0 || !testenv.same(out.stdout, "DM")) { + fail("direct-recursive", out.stdout); + }; + let dispatch: str = testenv.readfile(strings.concat(work, + "/cmd.init.unit.ww")); + if (testenv.occurrences(dispatch, + "//ww:init-call __ww..pkg.p.dep.v0.r0.init") != 1 + || testenv.pos(dispatch, + "//ww:init-call __ww..pkg.p.dep.v0.r0.init") + >= testenv.pos(dispatch, + "//ww:init-call __ww..pkg.p.cmd.v0.r0.init")) { + fail("direct-recursive", "dispatcher order changed with roots"); + }; + fi = 0; + for (fi < files.len) { + let bytes: str = testenv.readfile(strings.concat(work, "/", + files[fi])); + if (mode == 0 && si == 0) { refs[fi] = strings.dup(bytes); } + else { if (!testenv.same(refs[fi], bytes)) { + fail("direct-recursive", strings.concat("artifact differs: ", + files[fi])); + }; }; + fi += 1; + }; + let binbytes: str = testenv.readfile(bin); + if (mode == 0 && si == 0) { binref = strings.dup(binbytes); } + else { if (!testenv.same(binref, binbytes)) { + fail("direct-recursive", "binary differs"); + }; }; + si += 1; + }; + mode += 1; + }; + testenv.clean(td); +}; + +fn rejectrow(td: str, label: str, src: str, expected: str) void = { + let stages: []str = ["ww", "ww_ww"]; + let tags: []str = ["c", "ww"]; + let i: i32 = 0; + for (i < 2) { + let prog: str = strings.concat(td, "/", label, "-", tags[i]); + let av: []str = [testenv.driver(stages[i]), "build", "-I", td, + "-o", prog, src]; + let out: testenv.commandout; + run(td, strings.concat(label, "-", tags[i]), av, &out); + let work: str = strings.concat(prog, ".sepwork"); + let diag: str = normalized(out.stderr, work); + if (exitcode(&out) != 1 || testenv.exists(prog) + || testenv.exists(work) || !testenv.same(diag, expected)) { + fail(label, diag); + }; + i += 1; + }; +}; + +@test fn special_init_declarations() void = { + let td: str = testenv.fresh(); + let dep: str = strings.concat(td, "/dep"); + assert(os.mkdir(dep, 493) == 0); + testenv.writefile(strings.concat(dep, "/dep.ww"), strings.concat( + "package dep;\nfn init() void = {};\n", + "export fn value() i32 = { return 1; };\n")); + let labels: []str = ["direct", "qualified", "exported", "params", + "result", "error-result", "error-alias-result", "missing", + "attribute", "nonfunc", "reserved"]; + let bodies: []str = [ + "package main;\nfn init() void = {};\nfn main() i32 = { init(); return 0; };\n", + "package main;\nimport dep;\nfn main() i32 = { dep.init(); return 0; };\n", + "package main;\nexport fn init() void = {};\nfn main() i32 = { return 0; };\n", + "package main;\nfn init(x: i32) void = {};\nfn main() i32 = { return 0; };\n", + "package main;\nfn init() i32 = { return 1; };\nfn main() i32 = { return 0; };\n", + "package main;\nfn init() !void = {};\nfn main() i32 = { return 0; };\n", + "package main;\ntype errvoid = !void;\nfn init() errvoid = {};\nfn main() i32 = { return 0; };\n", + "package main;\nfn init() void;\nfn main() i32 = { return 0; };\n", + "package main;\n@test fn init() void = {};\nfn main() i32 = { return 0; };\n", + "package main;\nlet init: i32 = 1;\nfn main() i32 = { return init; };\n", + "package main;\n@symbol(\"__ww..dispatch\") fn claim() void = {};\nfn main() i32 = { return 0; };\n", + ]; + let expected: []str = [ + strings.concat("$WORK/__root.unit.ww:4:19: error: undefined: init\n", + "ww: w6c failed for (root)\n"), + strings.concat("$WORK/__root.unit.ww:4:19: error: package 'dep' has no exported declaration 'init'\n", + "ww: w6c failed for (root)\n"), + strings.concat("$WORK/__root.unit.ww:3:8: error: func init cannot be exported\n", + "ww: w6c failed for (root)\n"), + strings.concat("$WORK/__root.unit.ww:3:1: error: func init must have no arguments and no return values\n", + "ww: w6c failed for (root)\n"), + strings.concat("$WORK/__root.unit.ww:3:1: error: func init must have no arguments and no return values\n", + "ww: w6c failed for (root)\n"), + strings.concat("$WORK/__root.unit.ww:3:1: error: func init must have no arguments and no return values\n", + "ww: w6c failed for (root)\n"), + strings.concat("$WORK/__root.unit.ww:4:1: error: func init must have no arguments and no return values\n", + "ww: w6c failed for (root)\n"), + strings.concat("$WORK/__root.unit.ww:3:1: error: func init must have a body\n", + "ww: w6c failed for (root)\n"), + strings.concat("$WORK/__root.unit.ww:3:7: error: func init cannot have attributes\n", + "ww: w6c failed for (root)\n"), + strings.concat("$WORK/__root.unit.ww:3:5: error: cannot declare init - must be func\n", + "$WORK/__root.unit.ww:4:26: error: undefined: init\n", + "ww: w6c failed for (root)\n"), + strings.concat("$WORK/__root.unit.ww:3:27: error: @symbol name uses reserved prefix __ww..\n", + "ww: w6c failed for (root)\n"), + ]; + let i: i32 = 0; + for (i < labels.len) { + let src: str = strings.concat(td, "/", labels[i], ".ww"); + testenv.writefile(src, bodies[i]); + rejectrow(td, labels[i], src, expected[i]); + i += 1; + }; + testenv.clean(td); +}; + +@test fn allocation_failure_is_command_global() void = { + let td: str = testenv.fresh(); + let tree: str = strings.concat(td, "/tree"); + assert(os.mkdir(tree, 493) == 0); + let body: str = "package main;\n"; + let i: i32 = 0; + for (i < 600) { + let name: str = allocpkgname(i); + let dir: str = strings.concat(tree, "/", name); + assert(os.mkdir(dir, 493) == 0); + testenv.writefile(strings.concat(dir, "/side.ww"), + "package side;\nfn init() void = {};\n"); + body = strings.concat(body, "import _ ", name, ";\n"); + i += 1; + }; + body = strings.concat(body, "fn main() i32 = { return 0; };\n"); + let src: str = strings.concat(td, "/main.ww"); + testenv.writefile(src, body); + let wrapper: str = strings.concat(td, "/w6c-trace.sh"); + testenv.writeexecutable(wrapper, strings.concat("#!/bin/sh\n", + "printf x >> \"$WW_SEPINIT_W6C_TRACE\"\nexit 99\n")); + let stages: []str = ["ww", "ww_ww"]; + let compilers: []str = ["w6c", "w6c_ww"]; + let launcher: str = testenv.driver("sep-limitexec"); + let found: bool = false; + let last: str = ""; + let limit: i64 = 2621440i64; + let li: i32 = 0; + // Both implementations must reject under the same address-space ceiling. + // Search a bounded same-limit ladder because loader mappings differ across + // supported hosts; the 600-action graph makes the driver-allocation window + // broad, while the compiler trace pins failure before the first tool. + for (limit <= 16777216i64 && !found) { + let limitstr: str = strconv.i64tos(limit, strconv.base.DEC); + let good: [2]bool; + let diags: [2]str; + i = 0; + for (i < stages.len) { + let tag: str = allocpkgname(li); + let trace: str = strings.concat(td, "/alloc-", stages[i], "-", + tag, ".trace"); + testenv.writefile(trace, ""); + let env: []str = compilertraceenv(wrapper, + testenv.driver(compilers[i]), trace); + let output: str = strings.concat(td, "/alloc-", stages[i], "-", + tag); + let av: []str = [launcher, limitstr, testenv.driver(stages[i]), + "build", "-I", tree, "-o", output, src]; + let out: testenv.commandout; + runenv(td, strings.concat("allocation-", stages[i], "-", tag), + av, env, &out); + last = strings.dup(out.stderr); + good[i] = exitcode(&out) == 1 && testenv.same(out.stderr, + "ww: out of memory\n") + && testenv.readfile(trace).len == 0; + diags[i] = strings.dup(out.stderr); + if (testenv.exists(output) || testenv.exists(strings.concat(output, + ".sepwork"))) { + fail("allocation", "failed pressure leg published state"); + }; + i += 1; + }; + if (good[0] && good[1]) { + if (!testenv.same(diags[0], diags[1])) { + fail("allocation", "C/WW allocation diagnostic mismatch"); + }; + found = true; + }; + limit += 131072i64; + li += 1; + }; + if (!found) { fail("allocation", last); }; + testenv.clean(td); +}; + +@test fn compiler_publication_is_nofollow_and_atomic() void = { + let td: str = testenv.fresh(); + let src: str = strings.concat(td, "/p.ww"); + testenv.writefile(src, + "package p;\nexport fn value() i32 = { return 7; };\n"); + let wrapper: str = strings.concat(td, "/publish-wrapper.sh"); + testenv.writeexecutable(wrapper, strings.concat( + "#!/bin/sh\n", + "out=\nprev=\n", + "for arg do\n", + " if [ \"$prev\" = -o ]; then out=$arg; fi\n", + " prev=$arg\n", + "done\n", + "backup=${out}.w6c.$$.old\n", + "printf '%s' \"$backup\" > \"$WW_SEPINIT_W6C_TRACE\"\n", + "ln -s \"${WW_SEPINIT_W6C_TRACE}.missing\" \"$backup\" || exit 98\n", + "exec \"$WW_SEPINIT_REAL_W6C\" \"$@\"\n")); + let writefail: str = strings.concat(td, "/writefail-wrapper.sh"); + testenv.writeexecutable(writefail, strings.concat( + "#!/bin/sh\n", + "trap '' XFSZ\n", + "ulimit -f 0 || exit 97\n", + "exec \"$WW_SEPINIT_REAL_W6C\" \"$@\"\n")); + let compilers: []str = ["w6c", "w6c_ww"]; + let tags: []str = ["c", "ww"]; + let i: i32 = 0; + for (i < compilers.len) { + let asm: str = strings.concat(td, "/publish-", tags[i], ".s"); + let iface: str = strings.concat(td, "/publish-", tags[i], ".wwi"); + let record: str = strings.concat(td, "/publish-", tags[i], ".path"); + testenv.writefile(asm, "old assembly\n"); + testenv.writefile(iface, "old interface\n"); + testenv.writefile(record, ""); + let env: []str = compilertraceenv(wrapper, + testenv.driver(compilers[i]), record); + let av: []str = [wrapper, "-c", "--package-init-symbol", + "__ww..pkg.p.pub.v0.r0.init", "-I", iface, "-o", asm, src]; + let out: testenv.commandout; + runenv(td, strings.concat("compiler-publish-", tags[i]), av, env, &out); + let backup: str = testenv.readfile(record); + let want: str = strings.concat( + "w6c: publication backup exists for ", asm, "\n"); + if (exitcode(&out) != 1 || !testenv.same(out.stderr, want) + || !nofollowexists(backup) + || testenv.exists(strings.concat(record, ".missing")) + || !testenv.same(testenv.readfile(asm), "old assembly\n") + || !testenv.same(testenv.readfile(iface), "old interface\n") + || !nostaged(td)) { + fail("compiler-publication", out.stderr); + }; + assert(os.remove(backup) == 0); + + let dirout: str = strings.concat(td, "/publish-dir-", tags[i]); + assert(os.mkdir(dirout, 493) == 0); + let dirav: []str = [testenv.driver(compilers[i]), "-c", + "--package-init-symbol", "__ww..pkg.p.pub.v0.r0.init", + "-I", iface, "-o", dirout, src]; + run(td, strings.concat("compiler-publish-dir-", tags[i]), dirav, &out); + want = strings.concat( + "w6c: publication destination is not a regular file: ", dirout, + "\n"); + if (exitcode(&out) != 1 || !testenv.same(out.stderr, want) + || testenv.listdir(dirout).len != 0 + || !testenv.same(testenv.readfile(asm), "old assembly\n") + || !testenv.same(testenv.readfile(iface), "old interface\n") + || !nostaged(td)) { + fail("compiler-publication-directory", out.stderr); + }; + + let writeenv: []str = compilertraceenv(writefail, + testenv.driver(compilers[i]), record); + let writeav: []str = [writefail, "-c", "--package-init-symbol", + "__ww..pkg.p.pub.v0.r0.init", "-I", iface, "-o", asm, src]; + runenv(td, strings.concat("compiler-write-failure-", tags[i]), writeav, + writeenv, &out); + if (exitcode(&out) != 1 || out.stderr.len != 0 + || !testenv.same(testenv.readfile(asm), "old assembly\n") + || !testenv.same(testenv.readfile(iface), "old interface\n") + || !nostaged(td)) { + fail("compiler-write-failure", out.stderr); + }; + i += 1; + }; + testenv.clean(td); +}; + +@test fn test_variant_initialization() void = { + let td: str = testenv.fresh(); + let widget: str = strings.concat(td, "/widget"); + let prodside: str = strings.concat(td, "/prodside"); + let testside: str = strings.concat(td, "/testside"); + let client: str = strings.concat(td, "/client"); + assert(os.mkdir(widget, 493) == 0); + assert(os.mkdir(prodside, 493) == 0); + assert(os.mkdir(testside, 493) == 0); + assert(os.mkdir(client, 493) == 0); + testenv.writefile(strings.concat(prodside, "/prodside.ww"), strings.concat( + "package prodside;\nimport os;\n", + "fn init() void = { os.write(1, \"\".ptr, 3u64); };\n")); + testenv.writefile(strings.concat(testside, "/testside.ww"), strings.concat( + "package testside;\nimport os;\n", + "fn init() void = { os.write(1, \"\".ptr, 3u64); };\n")); + testenv.writefile(strings.concat(widget, "/widget.ww"), strings.concat( + "package widget;\nimport _ prodside;\nimport os;\n", + "fn init() void = { os.write(1, \"

\".ptr, 3u64); };\n", + "export fn value() i32 = { return 1; };\n")); + testenv.writefile(strings.concat(widget, "/a_test.ww"), strings.concat( + "package widget;\nimport _ testside;\nimport os;\n", + "let InternalState: i32 = makeinternal();\n", + "fn makeinternal() i32 = { os.write(1, \"\".ptr, 3u64); return 7; };\n", + "fn init() void = { assert(InternalState == 7); os.write(1, \"\".ptr, 3u64); };\n", + "@test fn internal() void = { os.write(1, \"\".ptr, 3u64); assert(value() == 1); };\n")); + testenv.writefile(strings.concat(widget, "/z_test.ww"), strings.concat( + "package widget_test;\nimport os;\nimport widget;\n", + "let ExternalState: i32 = makeexternal();\n", + "fn makeexternal() i32 = { os.write(1, \"\".ptr, 3u64); return 9; };\n", + "fn init() void = { assert(ExternalState == 9); os.write(1, \"\".ptr, 3u64); };\n", + "@test fn external() void = { os.write(1, \"\".ptr, 3u64); assert(widget.value() == 1); };\n")); + testenv.writefile(strings.concat(client, "/main.ww"), strings.concat( + "package main;\nimport os;\nimport widget;\n", + "fn main() i32 = { os.write(1, \"\".ptr, 3u64); return widget.value() - 1; };\n")); + let stages: []str = ["ww", "ww_ww"]; + let tags: []str = ["c", "ww"]; + let reference: str = ""; + let variantnames: []str = [ + "widget-internal-test-main.init.unit.ww", + "widget-internal-test-main.init.s", + "widget-internal-test-main.init.o", + "widget-internal-test-main.a", + "widget_test-external-test-main.init.unit.ww", + "widget_test-external-test-main.init.s", + "widget_test-external-test-main.init.o", + "widget_test-external-test-main.a", + ]; + let variantrefs: []str = ["", "", "", "", "", "", "", ""]; + let i: i32 = 0; + for (i < 2) { + let work: str = strings.concat(td, "/test-work-", tags[i]); + assert(os.mkdir(work, 493) == 0); + let prog: str = strings.concat(td, "/client-", tags[i]); + let bav: []str = [testenv.driver(stages[i]), "build", "-w", work, + "-I", td, "-o", prog, client]; + let out: testenv.commandout; + run(td, strings.concat("client-build-", tags[i]), bav, &out); + if (exitcode(&out) != 0) { fail("variants", out.stderr); }; + let rav: []str = [prog]; + run(td, strings.concat("client-run-", tags[i]), rav, &out); + if (exitcode(&out) != 0 || !testenv.same(out.stdout, "

")) { + fail("variants", strings.concat("production leaked tests: ", out.stdout)); + }; + let tav: []str = [testenv.driver(stages[i]), "test", "-w", work, + "-I", td, widget]; + run(td, strings.concat("test-run-", tags[i]), tav, &out); + if (exitcode(&out) != 0) { fail("variants", out.stderr); }; + let d: i32 = testenv.pos(out.stdout, ""); + let q: i32 = testenv.pos(out.stdout, ""); + let p: i32 = testenv.pos(out.stdout, "

"); + let rr: i32 = testenv.pos(out.stdout, ""); + let ii: i32 = testenv.pos(out.stdout, ""); + let tt: i32 = testenv.pos(out.stdout, ""); + let tail: str = ""; + if (tt >= 0) { tail = strings.sub(out.stdout, tt + 3, out.stdout.len); }; + let de: i32 = testenv.pos(tail, ""); + let pe: i32 = testenv.pos(tail, "

"); + let w: i32 = testenv.pos(tail, ""); + let e: i32 = testenv.pos(tail, ""); + let x: i32 = testenv.pos(tail, ""); + if (!(d >= 0 && d < q && q < rr && rr < p && p < ii && ii < tt + && de >= 0 && de < pe && pe < w && w < e && e < x)) { + fail("variants", strings.concat("test init order: ", out.stdout)); + }; + if (testenv.occurrences(out.stdout, "") != 1 + || testenv.occurrences(out.stdout, "") != 2 + || testenv.occurrences(out.stdout, "") != 1 + || testenv.occurrences(out.stdout, "") != 1 + || testenv.occurrences(out.stdout, "") != 1 + || testenv.occurrences(out.stdout, "") != 1 + || testenv.occurrences(out.stdout, "

") != 2) { + fail("variants", "variant initialization count"); + }; + let internal: str = testenv.readfile(strings.concat(work, + "/widget-internal-test-main.init.unit.ww")); + if (testenv.occurrences(internal, + "__ww..pkg.p.testside.v0.r0.init") != 1 + || testenv.occurrences(internal, + "__ww..pkg.p.prodside.v0.r0.init") != 1 + || testenv.occurrences(internal, + "__ww..pkg.p.widget.v1.r0.init") != 1 + || testenv.occurrences(internal, + "__ww..pkg.p.test.v0.r0.init") != 1 + || testenv.occurrences(internal, + "//ww:init-call __ww..pkg.p.__wwtestmain.widget.internal.main.v3.r2.init") != 1 + || testenv.pos(internal, "__ww..pkg.p.test.v0.r0.init") + >= testenv.pos(internal, + "//ww:init-call __ww..pkg.p.__wwtestmain.widget.internal.main.v3.r2.init") + || testenv.has(internal, "__ww..pkg.p.widget.v0.r0.init")) { + fail("variants", "internal variant dispatcher ownership"); + }; + let external: str = testenv.readfile(strings.concat(work, + "/widget_test-external-test-main.init.unit.ww")); + if (testenv.occurrences(external, + "__ww..pkg.p.widget.v0.r0.init") != 1 + || testenv.occurrences(external, + "__ww..pkg.p.prodside.v0.r0.init") != 1 + || testenv.occurrences(external, + "__ww..pkg.p.widget_test.v2.r0.init") != 1 + || testenv.occurrences(external, + "__ww..pkg.p.test.v0.r0.init") != 1 + || testenv.occurrences(external, + "//ww:init-call __ww..pkg.p.__wwtestmain.widget_test.external.main.v3.r2.init") != 1 + || testenv.pos(external, "__ww..pkg.p.test.v0.r0.init") + >= testenv.pos(external, + "//ww:init-call __ww..pkg.p.__wwtestmain.widget_test.external.main.v3.r2.init") + || testenv.has(external, "testside")) { + fail("variants", "external variant dispatcher ownership"); + }; + let directstdout: str = strings.dup(out.stdout); + let recursivework: str = strings.concat(td, "/test-recursive-work-", + tags[i]); + assert(os.mkdir(recursivework, 493) == 0); + let recursiveav: []str = [testenv.driver(stages[i]), "test", "-w", + recursivework, "-I", td, strings.concat(widget, "/...")]; + run(td, strings.concat("test-recursive-", tags[i]), recursiveav, &out); + if (exitcode(&out) != 0 || !testenv.same(out.stdout, directstdout)) { + fail("variants", strings.concat("recursive runtime mismatch: ", + out.stdout)); + }; + let vi: i32 = 0; + for (vi < variantnames.len) { + let directpath: str = strings.concat(work, "/", variantnames[vi]); + let recursivepath: str = strings.concat(recursivework, "/", + variantnames[vi]); + comparefile("variant-direct-recursive", directpath, recursivepath); + let bytes: str = testenv.readfile(directpath); + if (i == 0) { variantrefs[vi] = strings.dup(bytes); } + else { if (!testenv.same(variantrefs[vi], bytes)) { + fail("variants", strings.concat("stage artifact mismatch: ", + variantnames[vi])); + }; }; + vi += 1; + }; + if (i == 0) { reference = strings.dup(out.stdout); } + else { if (!testenv.same(reference, out.stdout)) { + fail("variants", "stage runtime output mismatch"); + }; }; + i += 1; + }; + testenv.clean(td); +}; + +@test fn effective_internal_test_cycle_is_pretool() void = { + let td: str = testenv.fresh(); + let p: str = strings.concat(td, "/p"); + let q: str = strings.concat(td, "/q"); + let r: str = strings.concat(td, "/r"); + let tools: str = strings.concat(td, "/tools"); + assert(os.mkdir(p, 493) == 0); + assert(os.mkdir(q, 493) == 0); + assert(os.mkdir(r, 493) == 0); + assert(os.mkdir(tools, 493) == 0); + testenv.writefile(strings.concat(p, "/p.ww"), + "package p;\nexport fn value() i32 = { return 1; };\n"); + testenv.writefile(strings.concat(p, "/p_test.ww"), strings.concat( + "package p;\nimport q;\n", + "@test fn cycle() void = { assert(q.value() == 1); };\n")); + testenv.writefile(strings.concat(q, "/q.ww"), + "package q;\nimport r;\nexport fn value() i32 = { return r.value(); };\n"); + testenv.writefile(strings.concat(r, "/r.ww"), + "package r;\nimport p;\nexport fn value() i32 = { return p.value(); };\n"); + let wrapper: str = strings.concat(tools, "/w6c-wrapper.sh"); + testenv.writeexecutable(wrapper, strings.concat( + "#!/bin/sh\n", + "printf x >> \"$WW_SEPINIT_W6C_TRACE\"\n", + "exec \"$WW_SEPINIT_REAL_W6C\" \"$@\"\n")); + let stages: []str = ["ww", "ww_ww"]; + let compilers: []str = ["w6c", "w6c_ww"]; + let tags: []str = ["c", "ww"]; + let reference: str = ""; + let i: i32 = 0; + for (i < stages.len) { + let work: str = strings.concat(td, "/work-", tags[i]); + let trace: str = strings.concat(td, "/trace-", tags[i]); + assert(os.mkdir(work, 493) == 0); + testenv.writefile(trace, ""); + let env: []str = compilertraceenv(wrapper, + testenv.driver(compilers[i]), trace); + let av: []str = [testenv.driver(stages[i]), "test", "-w", work, + "-I", td, p]; + let out: testenv.commandout; + testenv.runcommandenv(td, td, strings.concat("test-cycle-", tags[i]), + av, env, tmo(), &out); + if (exitcode(&out) == 0 + || !testenv.has(out.stderr, + "ww: dependency cycle in initialization closure") + || testenv.readfile(trace).len != 0 + || testenv.listdir(work).len != 0) { + fail("test-cycle", out.stderr); + }; + let diag: str = normalized(out.stderr, work); + if (i == 0) { reference = strings.dup(diag); } + else { if (!testenv.same(reference, diag)) { + fail("test-cycle", "C/WW diagnostic mismatch"); + }; }; + i += 1; + }; + testenv.clean(td); +}; + +fn nostaged(work: str) bool = { + let names: []str = testenv.listdir(work); + let i: i32 = 0; + for (i < names.len) { + if (strings.hassuffix(names[i], ".new")) { return false; }; + i += 1; + }; + return true; +}; + +fn nofollowexists(path: str) bool = { + let fi: os.filestat; + match (os.lstat(&fi, path)) { + case void => return true; + case let e: os.oserror => return false; + }; + return false; +}; + +fn nofollowregular(path: str) bool = { + let fi: os.filestat; + match (os.lstat(&fi, path)) { + case void => { + return (((fi.mode: u32) & 61440u32) == (os.mode.REG: u32)); + }; + case let e: os.oserror => return false; + }; + return false; +}; + +@test fn persistent_init_and_rejection() void = { + let td: str = testenv.fresh(); + let side: str = strings.concat(td, "/side"); + let extra: str = strings.concat(td, "/extra"); + let root: str = strings.concat(td, "/root"); + let tools: str = strings.concat(td, "/tools"); + assert(os.mkdir(side, 493) == 0); + assert(os.mkdir(extra, 493) == 0); + assert(os.mkdir(root, 493) == 0); + assert(os.mkdir(tools, 493) == 0); + let sidefile: str = strings.concat(side, "/side.ww"); + let rootfile: str = strings.concat(root, "/main.ww"); + let rootbase: str = strings.concat( + "package main;\nimport _ side;\nimport os;\n", + "fn main() i32 = { os.write(1, \"M\".ptr, 1u64); return 0; };\n"); + let rootextra: str = strings.concat( + "package main;\nimport _ side;\nimport _ extra;\nimport os;\n", + "fn main() i32 = { os.write(1, \"M\".ptr, 1u64); return 0; };\n"); + testenv.writefile(sidefile, strings.concat( + "package side;\nimport os;\n", + "fn init() void = { os.write(1, \"S\".ptr, 1u64); };\n")); + testenv.writefile(strings.concat(extra, "/extra.ww"), strings.concat( + "package unbound;\nimport os;\n", + "fn init() void = { os.write(1, \"E\".ptr, 1u64); };\n")); + testenv.writefile(rootfile, rootbase); + let wrapper: str = strings.concat(tools, "/w6c-wrapper.sh"); + testenv.writeexecutable(wrapper, strings.concat( + "#!/bin/sh\n", + "printf 'BEGIN' >> \"$WW_SEPINIT_W6C_TRACE\"\n", + "for arg in \"$@\"; do printf '<%s>' \"$arg\" >> ", + "\"$WW_SEPINIT_W6C_TRACE\"; done\n", + "printf '\\n' >> \"$WW_SEPINIT_W6C_TRACE\"\n", + "exec \"$WW_SEPINIT_REAL_W6C\" \"$@\"\n")); + let stages: []str = ["ww", "ww_ww"]; + let compilers: []str = ["w6c", "w6c_ww"]; + let tags: []str = ["c", "ww"]; + let finalbin: str = ""; + let blankbin: str = ""; + let i: i32 = 0; + for (i < 2) { + if (i != 0) { + replacefile(sidefile, strings.concat( + "package side;\nimport os;\n", + "fn init() void = { os.write(1, \"S\".ptr, 1u64); };\n")); + replacefile(rootfile, rootbase); + }; + let work: str = strings.concat(td, "/persist-", tags[i]); + assert(os.mkdir(work, 493) == 0); + let prog: str = strings.concat(td, "/persist-bin-", tags[i]); + let trace: str = strings.concat(td, "/persist-trace-", tags[i]); + testenv.writefile(trace, ""); + let env: []str = compilertraceenv(wrapper, + testenv.driver(compilers[i]), trace); + let av: []str = [testenv.driver(stages[i]), "build", "-w", work, + "-I", td, "-o", prog, root]; + let out: testenv.commandout; + testenv.runcommandenv(td, td, strings.concat("persist-seed-", tags[i]), + av, env, tmo(), &out); + if (exitcode(&out) != 0) { fail("persistence", out.stderr); }; + let seededbin: str = testenv.readfile(prog); + let sidewwi: str = testenv.readfile(strings.concat(work, "/side.wwi")); + let roots: str = testenv.readfile(strings.concat(work, "/root.s")); + let rootwwi: str = testenv.readfile(strings.concat(work, "/root.wwi")); + let dispatch: str = testenv.readfile(strings.concat(work, + "/root.init.unit.ww")); + let sides: str = testenv.readfile(strings.concat(work, "/side.s")); + + // A committed dispatcher voucher must itself be a regular file. An + // exact-byte symlink target cannot authorize reuse; the driver repairs + // the voucher and archive without recompiling source actions. + let initunit: str = strings.concat(work, "/root.init.unit.ww"); + let inittarget: str = strings.concat(td, "/init-unit-target-", tags[i]); + testenv.writefile(inittarget, dispatch); + assert(os.remove(initunit) == 0); + assert(os.symlink(inittarget, initunit) == 0); + replacefile(trace, ""); + testenv.runcommandenv(td, td, + strings.concat("persist-init-voucher-symlink-", tags[i]), av, env, + tmo(), &out); + if (exitcode(&out) != 0 || testenv.readfile(trace).len != 0 + || !nofollowregular(initunit) + || !testenv.same(dispatch, testenv.readfile(initunit)) + || !testenv.same(seededbin, testenv.readfile(prog))) { + fail("persistence", "symlinked init voucher authorized reuse"); + }; + + // Every reserved warm staging name is a structural input. A dangling + // symlink is occupied, must survive rejection, and must stop the whole + // request before the first compiler action. + let warmstage: str = strings.concat(work, "/side.s.new"); + let warmtarget: str = strings.concat(td, "/missing-warm-", tags[i]); + assert(os.symlink(warmtarget, warmstage) == 0); + replacefile(trace, ""); + testenv.runcommandenv(td, td, + strings.concat("persist-stage-collision-", tags[i]), av, env, + tmo(), &out); + let warmwant: str = strings.concat( + "ww: package staging path already exists: ", warmstage, "\n"); + if (exitcode(&out) != 1 || !testenv.same(out.stderr, warmwant) + || testenv.readfile(trace).len != 0 || !nofollowexists(warmstage) + || testenv.exists(warmtarget) + || !testenv.same(seededbin, testenv.readfile(prog))) { + fail("persistence", "warm dangling staging collision was mutated"); + }; + assert(os.remove(warmstage) == 0); + + replacefile(sidefile, strings.concat( + "package side;\nimport os;\n", + "fn init() void = { os.write(1, \"T\".ptr, 1u64); };\n")); + replacefile(trace, ""); + testenv.runcommandenv(td, td, + strings.concat("persist-init-change-", tags[i]), av, env, tmo(), &out); + if (exitcode(&out) != 0) { fail("persistence", out.stderr); }; + let inittrace: str = testenv.readfile(trace); + if (testenv.occurrences(inittrace, "BEGIN") != 1 + || !testenv.has(inittrace, + "<--package-init-symbol><__ww..pkg.p.side.v0.r0.init>") + || testenv.has(inittrace, + "<--package-init-symbol><__ww..pkg.p.root.v0.r0.init>")) { + fail("persistence", "init-only edit recompiled an importer"); + }; + if (!testenv.same(sidewwi, + testenv.readfile(strings.concat(work, "/side.wwi"))) + || !testenv.same(roots, + testenv.readfile(strings.concat(work, "/root.s"))) + || !testenv.same(rootwwi, + testenv.readfile(strings.concat(work, "/root.wwi"))) + || !testenv.same(dispatch, + testenv.readfile(strings.concat(work, "/root.init.unit.ww"))) + || testenv.same(sides, + testenv.readfile(strings.concat(work, "/side.s")))) { + fail("persistence", "init-only invalidation crossed export boundary"); + }; + let rav: []str = [prog]; + run(td, strings.concat("persist-run-", tags[i]), rav, &out); + if (exitcode(&out) != 0 || !testenv.same(out.stdout, "TM")) { + fail("persistence", "init-only relink did not take effect"); + }; + let names: []str = testenv.listdir(work); + let saved: []str = alloc([], names.len: u64)!; + let k: i32 = 0; + for (k < names.len) { + append(saved, testenv.readfile(strings.concat(work, "/", names[k]))); + k += 1; + }; + let oldbin: str = testenv.readfile(prog); + replacefile(sidefile, strings.concat( + "package side;\nimport os;\n", + "fn init(x: i32) void = { os.write(1, \"X\".ptr, 1u64); };\n")); + replacefile(trace, ""); + testenv.runcommandenv(td, td, strings.concat("persist-reject-", tags[i]), + av, env, tmo(), &out); + if (exitcode(&out) == 0 + || !testenv.has(out.stderr, + "func init must have no arguments and no return values")) { + fail("persistence", "invalid init was accepted"); + }; + let after: []str = testenv.listdir(work); + if (after.len != names.len || !nostaged(work) + || !testenv.same(oldbin, testenv.readfile(prog))) { + fail("persistence", "rejection changed committed shape/product"); + }; + k = 0; + for (k < names.len) { + if (!testenv.same(names[k], after[k]) + || !testenv.same(saved[k], + testenv.readfile(strings.concat(work, "/", names[k])))) { + fail("persistence", strings.concat("rejection changed ", names[k])); + }; + k += 1; + }; + replacefile(sidefile, strings.concat( + "package side;\nimport os;\n", + "fn init() void = { os.write(1, \"T\".ptr, 1u64); };\n")); + replacefile(trace, ""); + testenv.runcommandenv(td, td, strings.concat("persist-recover-", tags[i]), + av, env, tmo(), &out); + if (exitcode(&out) != 0) { fail("persistence", "retry failed"); }; + if (testenv.readfile(trace).len != 0) { + fail("persistence", "rejected source polluted warm reuse"); + }; + if (i == 0) { finalbin = testenv.readfile(prog); } + else { if (!testenv.same(finalbin, testenv.readfile(prog))) { + fail("persistence", "final C/WW binary mismatch"); + }; }; + + // Adding one blank-only edge recompiles exactly the new dependency and + // its owning source action, changes the reachable dispatcher, and keeps + // the owner's semantic export stable. + replacefile(rootfile, rootextra); + replacefile(trace, ""); + testenv.runcommandenv(td, td, strings.concat("persist-blank-add-", tags[i]), + av, env, tmo(), &out); + if (exitcode(&out) != 0) { fail("persistence", out.stderr); }; + let addtrace: str = testenv.readfile(trace); + if (testenv.occurrences(addtrace, "BEGIN") != 2 + || !testenv.has(addtrace, + "<--package-init-symbol><__ww..pkg.p.extra.v0.r0.init>") + || !testenv.has(addtrace, + "<--package-init-symbol><__ww..pkg.p.root.v0.r0.init>") + || !testenv.same(rootwwi, + testenv.readfile(strings.concat(work, "/root.wwi")))) { + fail("persistence", "blank-edge addition invalidation"); + }; + let adddispatch: str = testenv.readfile(strings.concat(work, + "/root.init.unit.ww")); + if (testenv.occurrences(adddispatch, + "//ww:init-call __ww..pkg.p.extra.v0.r0.init") != 1) { + fail("persistence", "blank-edge addition missed dispatcher"); + }; + run(td, strings.concat("persist-blank-add-run-", tags[i]), rav, &out); + if (exitcode(&out) != 0 || !testenv.same(out.stdout, "ETM")) { + fail("persistence", "blank-edge addition runtime"); + }; + if (i == 0) { blankbin = testenv.readfile(prog); } + else { if (!testenv.same(blankbin, testenv.readfile(prog))) { + fail("persistence", "blank-edge C/WW binary mismatch"); + }; }; + + // Removing it recompiles only the owner, restores the exact old + // dispatcher closure, and cannot reuse the now-unreachable init task. + replacefile(rootfile, rootbase); + replacefile(trace, ""); + testenv.runcommandenv(td, td, + strings.concat("persist-blank-remove-", tags[i]), av, env, tmo(), &out); + if (exitcode(&out) != 0) { fail("persistence", out.stderr); }; + let removetrace: str = testenv.readfile(trace); + if (testenv.occurrences(removetrace, "BEGIN") != 1 + || !testenv.has(removetrace, + "<--package-init-symbol><__ww..pkg.p.root.v0.r0.init>") + || testenv.has(removetrace, + "<--package-init-symbol><__ww..pkg.p.extra.v0.r0.init>") + || !testenv.same(dispatch, + testenv.readfile(strings.concat(work, "/root.init.unit.ww")))) { + fail("persistence", "blank-edge removal invalidation"); + }; + run(td, strings.concat("persist-blank-remove-run-", tags[i]), rav, &out); + if (exitcode(&out) != 0 || !testenv.same(out.stdout, "TM")) { + fail("persistence", "blank-edge removal runtime"); + }; + i += 1; + }; + + // Cold caller-visible product staging receives the same no-follow + // preflight and cannot acquire its implicit scratch or invoke a producer. + i = 0; + for (i < 2) { + let prog: str = strings.concat(td, "/collision-bin-", tags[i]); + let stage: str = strings.concat(prog, ".new"); + let target: str = strings.concat(td, "/missing-product-", tags[i]); + let trace: str = strings.concat(td, "/collision-trace-", tags[i]); + testenv.writefile(trace, ""); + assert(os.symlink(target, stage) == 0); + let env: []str = compilertraceenv(wrapper, + testenv.driver(compilers[i]), trace); + let av: []str = [testenv.driver(stages[i]), "build", "-I", td, + "-o", prog, root]; + let out: testenv.commandout; + testenv.runcommandenv(td, td, + strings.concat("cold-stage-collision-", tags[i]), av, env, tmo(), + &out); + let want: str = strings.concat( + "ww: product staging path already exists: ", stage, "\n"); + if (exitcode(&out) != 1 || !testenv.same(out.stderr, want) + || testenv.readfile(trace).len != 0 || !nofollowexists(stage) + || testenv.exists(target) || testenv.exists(prog) + || testenv.exists(strings.concat(prog, ".sepwork"))) { + fail("persistence", "cold dangling staging collision was mutated"); + }; + assert(os.remove(stage) == 0); + i += 1; + }; + testenv.clean(td); +}; diff --git a/test/wcc/200_parse.c b/test/wcc/200_parse.c index 5fab0646..12e6f464 100644 --- a/test/wcc/200_parse.c +++ b/test/wcc/200_parse.c @@ -140,6 +140,7 @@ main(void) "import io;", "import io.bufio;", "import stable io.bufio;", + "import _ io.bufio;", "def MAX: i32 = 4096;", "export def MAX: i32 = 4096;", "type point = struct { x: i32, y: i32 };", @@ -149,6 +150,7 @@ main(void) "type arr = [16]u8;", "fn nop() void = {};", + "fn init() void = {}; fn init() void = {};", "export fn id(x: i32) i32 = { return x; };", "fn add(a: i32, b: i32) i32 = { return a + b; };", "@symbol(\"malloc\") fn cmalloc(n: u64) *void;", diff --git a/test/wcc/300_check.c b/test/wcc/300_check.c index 66183c20..c1493aaa 100644 --- a/test/wcc/300_check.c +++ b/test/wcc/300_check.c @@ -49,6 +49,15 @@ struct row { const char *src, *expect; }; static const struct row rows[] = { { "fn main() void = {};", "ok" }, + { "fn init() void = {}; fn init() void = {};", "ok" }, + { "fn init(x: i32) void = {};", "func init must have no arguments and no return values" }, + { "fn init() i32 = { return 1; };", "func init must have no arguments and no return values" }, + { "fn init() void;", "func init must have a body" }, + { "export fn init() void = {};", "func init cannot be exported" }, + { "fn init() void = {}; fn f() void = { init(); };", "undefined: init" }, + { "fn make() i32 = { return 7; }; let state: i32 = make();", "ok" }, + { "fn left() i32 = { return right; }; let first: i32 = left(); let right: i32 = 3;", "ok" }, + { "fn readb() i32 = { return b; }; let a: i32 = readb(); let b: i32 = a;", "initialization cycle for a" }, { "fn id(x: i32) i32 = { return x; };", "ok" }, { "fn add(a: i32, b: i32) i32 = { return a + b; };", "ok" }, { "def MAX: i32 = 4096;", "ok" }, diff --git a/test/wcc/data/r681_slice_field_ptr_write/case.ww b/test/wcc/data/r681_slice_field_ptr_write/case.ww index 699d7cc4..80dd5a7f 100644 --- a/test/wcc/data/r681_slice_field_ptr_write/case.ww +++ b/test/wcc/data/r681_slice_field_ptr_write/case.ww @@ -1,9 +1,9 @@ //ww:run-exit 42 package main; type bs = struct { rbuf: []u8, mark: i32 }; -fn init(b: *bs, rbuf: []u8) void = { b.rbuf = rbuf; b.mark = 9; }; +fn setup(b: *bs, rbuf: []u8) void = { b.rbuf = rbuf; b.mark = 9; }; fn main() i32 = { - let raw: [8]u8; raw[0] = 0u8; let b: bs; init(&b, raw[0:5]); + let raw: [8]u8; raw[0] = 0u8; let b: bs; setup(&b, raw[0:5]); if (b.rbuf.len != 5) { return 1; }; if (b.rbuf.cap != 8) { return 2; }; if (b.mark != 9) { return 3; }; return 42; }; diff --git a/test/wcc/data/r778_branched_bufio_wrap/case.ww b/test/wcc/data/r778_branched_bufio_wrap/case.ww index 230c6345..e9cc30e8 100644 --- a/test/wcc/data/r778_branched_bufio_wrap/case.ww +++ b/test/wcc/data/r778_branched_bufio_wrap/case.ww @@ -16,7 +16,7 @@ export fn main() i32 = { if (sel == 0) { src = &mstB.vt; }; let rbuf: [4]u8; let wbuf: [4]u8; - let bc: bufio.stream = bufio.init(src, rbuf[0:4], wbuf[0:4]); + let bc: bufio.stream = bufio.newstream(src, rbuf[0:4], wbuf[0:4]); let vs: io.stream = &bc.vt; let out: [4]u8; let rd = io.read(vs, out[0:4]); diff --git a/test/wcc/data/r778_isbuffered_v_boundary/case.ww b/test/wcc/data/r778_isbuffered_v_boundary/case.ww index e91bbc51..aef67fd0 100644 --- a/test/wcc/data/r778_isbuffered_v_boundary/case.ww +++ b/test/wcc/data/r778_isbuffered_v_boundary/case.ww @@ -10,7 +10,7 @@ export fn main() i32 = { let msrc: io.stream = &mst.vt; let rbuf: [4]u8; let wbuf: [4]u8; - let bc: bufio.stream = bufio.init(msrc, rbuf[0:4], wbuf[0:4]); + let bc: bufio.stream = bufio.newstream(msrc, rbuf[0:4], wbuf[0:4]); let vs: io.stream = &bc.vt; if (!bufio.isbuffered(vs)) { return 91; }; if (bufio.isbuffered(msrc)) { return 92; }; diff --git a/test/wcc/data/r778_isbuffered_v_discriminate/case.ww b/test/wcc/data/r778_isbuffered_v_discriminate/case.ww index 1ac45ae9..c07324cb 100644 --- a/test/wcc/data/r778_isbuffered_v_discriminate/case.ww +++ b/test/wcc/data/r778_isbuffered_v_discriminate/case.ww @@ -10,7 +10,7 @@ export fn main() i32 = { let msrc: io.stream = &mst.vt; let rbuf: [4]u8; let wbuf: [4]u8; - let b: bufio.stream = bufio.init(msrc, rbuf[0:4], wbuf[0:4]); + let b: bufio.stream = bufio.newstream(msrc, rbuf[0:4], wbuf[0:4]); let vsbuf: io.stream = &b.vt; let buf2: [4]u8; let st2: memio.stream = memio.fixed(buf2[0:4]); diff --git a/test/wcc/data/r778_stream_read_unread/case.ww b/test/wcc/data/r778_stream_read_unread/case.ww index 75e83c67..d6b66a3e 100644 --- a/test/wcc/data/r778_stream_read_unread/case.ww +++ b/test/wcc/data/r778_stream_read_unread/case.ww @@ -12,7 +12,7 @@ export fn main() i32 = { let msrc: io.stream = &mst.vt; let rbuf: [8]u8; let wbuf: [4]u8; - let b: bufio.stream = bufio.init(msrc, rbuf[0:8], wbuf[0:4]); + let b: bufio.stream = bufio.newstream(msrc, rbuf[0:8], wbuf[0:4]); let vs: io.stream = &b.vt; let out: [4]u8; let rd1 = io.read(vs, out[0:3]); diff --git a/test/wcc/data/r778_stream_setflush/case.ww b/test/wcc/data/r778_stream_setflush/case.ww index 9d99ecda..695cb634 100644 --- a/test/wcc/data/r778_stream_setflush/case.ww +++ b/test/wcc/data/r778_stream_setflush/case.ww @@ -10,7 +10,7 @@ export fn main() i32 = { let sinkvs: io.stream = &sink.vt; let rb: [4]u8; let wb: [8]u8; - let b: bufio.stream = bufio.init(sinkvs, rb[0:4], wb[0:8]); + let b: bufio.stream = bufio.newstream(sinkvs, rb[0:4], wb[0:8]); let bvs: io.stream = &b.vt; let semi: [1]u8; semi[0] = 59u8; diff --git a/test/wcc/data/r778_stream_unread/case.ww b/test/wcc/data/r778_stream_unread/case.ww index bbc9d043..657e7761 100644 --- a/test/wcc/data/r778_stream_unread/case.ww +++ b/test/wcc/data/r778_stream_unread/case.ww @@ -11,7 +11,7 @@ export fn main() i32 = { let vs: io.stream = &mst.vt; let rbuf: [4]u8; let wbuf: [4]u8; - let b: bufio.stream = bufio.init(vs, rbuf[0:4], wbuf[0:4]); + let b: bufio.stream = bufio.newstream(vs, rbuf[0:4], wbuf[0:4]); let bvs: io.stream = &b.vt; let out: [4]u8; let rd = io.read(bvs, out[0:3]); diff --git a/test/wcc/data/r778_stream_write_flush/case.ww b/test/wcc/data/r778_stream_write_flush/case.ww index 2e34612e..49135529 100644 --- a/test/wcc/data/r778_stream_write_flush/case.ww +++ b/test/wcc/data/r778_stream_write_flush/case.ww @@ -10,7 +10,7 @@ export fn main() i32 = { let msrc: io.stream = &mst.vt; let rbuf: [8]u8; let wbuf: [8]u8; - let b: bufio.stream = bufio.init(msrc, rbuf[0:8], wbuf[0:8]); + let b: bufio.stream = bufio.newstream(msrc, rbuf[0:8], wbuf[0:8]); let vs: io.stream = &b.vt; let payload: [5]u8; payload[0] = 104u8; payload[1] = 105u8; diff --git a/test/wcc/data/r989_libprecond_u32n_one/case.ww b/test/wcc/data/r989_libprecond_u32n_one/case.ww index 0807d910..b6b45a86 100644 --- a/test/wcc/data/r989_libprecond_u32n_one/case.ww +++ b/test/wcc/data/r989_libprecond_u32n_one/case.ww @@ -2,7 +2,7 @@ package main; import math.random; export fn main() int = { - let g: random.random = random.init(1u64); + let g: random.random = random.fromseed(1u64); let x: u32 = random.u32n(&g, 1u32); if (x >= 1u32) { return 1; }; return 0; diff --git a/test/wcc/data/r989_libprecond_u32n_valid/case.ww b/test/wcc/data/r989_libprecond_u32n_valid/case.ww index 126d0651..78e7e593 100644 --- a/test/wcc/data/r989_libprecond_u32n_valid/case.ww +++ b/test/wcc/data/r989_libprecond_u32n_valid/case.ww @@ -2,7 +2,7 @@ package main; import math.random; export fn main() int = { - let g: random.random = random.init(1u64); + let g: random.random = random.fromseed(1u64); let x: u32 = random.u32n(&g, 10u32); if (x >= 10u32) { return 1; }; return 0; diff --git a/test/wcc/data/r989_libprecond_u64n_one/case.ww b/test/wcc/data/r989_libprecond_u64n_one/case.ww index 7aeec3a3..9020c5e8 100644 --- a/test/wcc/data/r989_libprecond_u64n_one/case.ww +++ b/test/wcc/data/r989_libprecond_u64n_one/case.ww @@ -2,7 +2,7 @@ package main; import math.random; export fn main() int = { - let g: random.random = random.init(1u64); + let g: random.random = random.fromseed(1u64); let x: u64 = random.u64n(&g, 1u64); if (x >= 1u64) { return 1; }; return 0; diff --git a/test/wcc/data/r989_libprecond_u64n_valid/case.ww b/test/wcc/data/r989_libprecond_u64n_valid/case.ww index 6f81d1b5..bb0a0a20 100644 --- a/test/wcc/data/r989_libprecond_u64n_valid/case.ww +++ b/test/wcc/data/r989_libprecond_u64n_valid/case.ww @@ -2,7 +2,7 @@ package main; import math.random; export fn main() int = { - let g: random.random = random.init(1u64); + let g: random.random = random.fromseed(1u64); let x: u64 = random.u64n(&g, 10u64); if (x >= 10u64) { return 1; }; return 0;