From 0eda40af320f84fb9fc0c248ebd7fa6c43922f84 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Tue, 21 Jul 2026 04:38:48 +1000 Subject: [PATCH] runtests: version-floor the runner's requires (fossilized-kit rejection); concurrency child floor Root cause of the punk86-kit runtests failure: a punk-kit interpreter's boot pre-registers its kit-stamped package versions, and an unversioned require satisfiable by such a registration never fires package unknown - the freshly-added bootsupport tm path is never scanned. The old kit's punk::args 0.1.0 won the require, then the versioned punk::path 0.4.0- pulled a modern module whose load-time argdoc needs punk::args::define: invalid command name. Floors added: punk::args 0.12- (runtests.tcl toplevel), punk::lib 0.5.1- (both 8.6 lpop guards; punk::path 0.4.0- already floored in discovery.tcl). Feature-era minimums, not latest pins - their purpose is rejecting fossils so package unknown resolves the bootsupport copy. Verified: the floors alone resurrect even the OLD fossilized punk86 kit (discover-only + a 35/35 suite run); native 9 and 8.6 unaffected. concurrency.test child preamble floors its punkcheck require (0.6-): kit-hosted children boot with kit-stamped punkcheck PRELOADED, and a stale kit (pre-vfscommonupdate _vfscommon) made children fail on 'invalid command name punkcheck::lock::acquire' - the floor turns that into a loud version-conflict in the child log. src/tests/AGENTS.md records the floor rationale and the kit-hosted child-suite requirement (vfscommonupdate-current kit content). Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- src/tests/AGENTS.md | 3 ++- .../testsuites/punkcheck/concurrency.test | 9 +++++++-- src/tests/runtests.tcl | 18 +++++++++++++++--- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/tests/AGENTS.md b/src/tests/AGENTS.md index 1543fcf3..2b3bd654 100644 --- a/src/tests/AGENTS.md +++ b/src/tests/AGENTS.md @@ -41,7 +41,8 @@ Top-level test harness and source-tree tests for ShellSpy/Punk. Tests here exerc ## Work Guidance - Select an appropriate interpreter, such as `tclsh`, `tclsh90`, or a built Punk executable such as `punk902z`. -- Native Tcl 8.6 tclsh is a supported runner interpreter (2026-07-21): `runtests.tcl` loads punk::lib's validated forward-compat command family (lpop etc - builtin parity pinned by `modules/punk/lib/testsuites/lib/compat.test`) at both boot levels (toplevel and testinterp) when the builtins are absent. The guards are no-ops on Tcl 8.7+/9, preserving the G-093 fast two-stage `-discover-only` boot. The punk86-kit route for runtests remains broken (kit/dev module skew loading dev punk::path) - use a native 8.6 tclsh for 8.6 legs. +- Native Tcl 8.6 tclsh is a supported runner interpreter (2026-07-21): `runtests.tcl` loads punk::lib's validated forward-compat command family (lpop etc - builtin parity pinned by `modules/punk/lib/testsuites/lib/compat.test`) at both boot levels (toplevel and testinterp) when the builtins are absent. The guards are no-ops on Tcl 8.7+/9, preserving the G-093 fast two-stage `-discover-only` boot. +- Kit-hosted runner hardening (2026-07-21): the runner's own package requires carry minimum-version floors (punk::args in `runtests.tcl`, punk::path in `testsupport/discovery.tcl`, punk::lib in the 8.6 guards). Rationale: a punk-kit `[interpreter]` boot pre-registers its kit-stamped package versions, and an unversioned require satisfiable by such a registration never fires `package unknown` - the freshly-added bootsupport tm path is never even scanned, so a fossilized kit copy silently wins and then skews against modern modules pulled in by versioned requires (the historic punk86 failure: kit punk::args 0.1.0 loaded, then punk::path 0.4.0- died on `invalid command name "punk::args::define"`). Floored requires reject the fossil and resolve the bootsupport copy. The punk86 kit itself is mapped in `src/runtime/mapvfs.config` (8.6.17 dyn tclkit + punk8win.vfs, remapped 2026-07-21). Suites that spawn children of `[info nameofexecutable]` additionally need kit content to be current: kit children boot with kit-stamped modules preloaded from `_vfscommon.vfs`, so run `make.tcl vfscommonupdate` + `make.tcl vfs` after module changes such suites depend on (punkcheck's concurrency.test floors its child-side punkcheck require so a stale kit fails loud in the child log instead of mysteriously). Native tclsh remains the preferred runner interpreter. - Run the full source-tree suite with ` src/tests/runtests.tcl`. - Use `-include-paths` with directory glob patterns relative to `src/tests/`, forward slashes — `modules/punk/args/***` runs that whole subtree (`X/***` deliberately excludes files directly in `X`; bare `X` is files-directly-in-X only). The flag accepts a space-separated list of patterns and may also be repeated, with all occurrences accumulating (`-multiple 1` as of 2026-07-10; previously the last flag silently won). Single-file targeting: `-include-paths modules/punk/args/testsuites/args dynamic.test`. - Use `-exclude-paths` (same syntax and accumulation) to remove directories from discovery — `**`|`***` tails prune the matched subtree, other matches drop only that directory's files, and exclusion wins for the same file. It gives broad runs a middle ground between full and targeted: e.g `-exclude-paths shell/***` when no built kit is present, or skipping a heavy suite family unrelated to the change. diff --git a/src/tests/modules/punkcheck/testsuites/punkcheck/concurrency.test b/src/tests/modules/punkcheck/testsuites/punkcheck/concurrency.test index bb207c96..82784c28 100644 --- a/src/tests/modules/punkcheck/testsuites/punkcheck/concurrency.test +++ b/src/tests/modules/punkcheck/testsuites/punkcheck/concurrency.test @@ -48,14 +48,19 @@ namespace eval ::testspace { try { puts -nonewline $fd $content } finally { close $fd } return $path } - #script preamble reproducing this interpreter's module search state in a child process + #script preamble reproducing this interpreter's module search state in a child process. + #The punkcheck require is floored: under a punk-kit [interpreter] the child boots with the + #kit-stamped punkcheck PRELOADED, and an unversioned require would silently accept a stale + #copy without the G-095 lock API (seen with a kit whose _vfscommon predated 0.6.0 - children + #failed on 'invalid command name punkcheck::lock::acquire'). The floor turns that into a + #loud version-conflict error in the child log instead. proc child_preamble {} { set preamble "package prefer latest\n" foreach p [lreverse [tcl::tm::path list]] { append preamble "tcl::tm::path add [list $p]\n" } append preamble "set ::auto_path [list $::auto_path]\n" - append preamble "package require punkcheck\n" + append preamble "package require punkcheck 0.6-\n" return $preamble } #poll for a condition script returning true; fail with $what after $deadline_ms diff --git a/src/tests/runtests.tcl b/src/tests/runtests.tcl index c2dfe15a..4fc5fd70 100644 --- a/src/tests/runtests.tcl +++ b/src/tests/runtests.tcl @@ -38,14 +38,24 @@ if {$libdir_arch ni $::auto_path} { #-discover-only early exit below, keeping 'runtests.tcl -discover-only 1' a fast targeting #inspection tool (boot ~1s instead of ~11s) for humans, agents and the runner-targeting #regression suite (runner/testsuites/discovery/pathdiscovery.test). -package require punk::args +#Minimum-version floors on the runner's own requires (punk::path has one in +#testsupport/discovery.tcl already): their purpose is to REJECT fossilized kit-stamped copies. +#Under a punk kit executable, the kit's boot registers its own (possibly ancient) package +#versions - an unversioned require satisfiable by such a registration never fires package +#unknown, so the freshly-added bootsupport tm path is never even scanned (observed: an old +#punk86 kit's punk::args 0.1.0 won the require, then the versioned punk::path 0.4.0- pulled a +#modern module whose load needs punk::args::define - invalid command name). A floored require +#rejects the fossil and resolves the current bootsupport copy instead. The floors are +#feature-era minimums for the runner's argdoc/parse usage, not latest-version pins. +package require punk::args 0.12- if {[info commands ::lpop] eq ""} { #Native Tcl 8.6 interpreter: no builtin lpop, which punk::args::parse (and later runner code) #uses. punk::lib installs its validated forward-compat family (lpop, ledit, lremove, ...) as #global aliases whenever the builtins are absent - behavioural parity with the 8.7/9 builtins #is pinned by modules/punk/lib/testsuites/lib/compat.test. Guarded: on Tcl 8.7+/9 this never #runs, preserving the G-093 fast two-stage boot for -discover-only. - package require punk::lib + #(0.5.1- = the parity-validated compat era; also a kit-fossil rejection floor per above.) + package require punk::lib 0.5.1- } #test-file discovery + -include-paths/-exclude-paths filtering (G-093) - shared with the @@ -1163,7 +1173,9 @@ foreach testfile_relative $testfiles { #native Tcl 8.6 testinterp: shellrun's runx (the runner's capture mechanism) parses via #punk::args::parse, which uses lpop. punk::lib installs the validated compat aliases #(parity pinned by modules/punk/lib compat.test). No-op on Tcl 8.7+/9. - package require punk::lib + #(floored like the toplevel requires - fossil-rejection; prefer-latest selects the dev + # module here anyway) + package require punk::lib 0.5.1- } } testinterp eval {package require shellrun}