Browse Source

G-059 achieved: WSL capability probing + native-filesystem staging for unix-side tests from windows

- new src/tests/testsupport/wslprobe.tcl (::punktest::wsl): memoized capability probe for the wsllinux constraint - default distro launches and unames as Linux, tools present (bash/mktemp/wslpath + sha1 tool), native tempdir file round trip; any failure -> skip with reason, so wsl.exe-present-but-unusable installs cannot false-positive. Staging helpers (staging_create/copy_in/run_in/cleanup) enforce the design: execution on the distro NATIVE filesystem only, shared /mnt used for one-way copy-in/out, the windows checkout never operated on from inside WSL (DrvFs slowness + cross-boundary stat differences that make git re-hash its index and fossil see phantom changes). Probe uses wsl -e invocations only (wsl --status/-l emit UTF-16)
- multishell.test: scriptwrap_multishell_exec_wsl_sh - the polyglot sh payload produces its marker under the distro bash from native staging (the direct isunix twin still skips on windows)
- new shell/testsuites/binscripts/runtimebash_wsl.test (8 tests): runtime.bash first-ever execution on real unix - multi-candidate run errors listing candidates, use/list/run round trip with argument passing, PUNK_ACTIVE_RUNTIME override, unknown/unselectable use errors, stale-active guidance, single-candidate fallback, offline list -remote (crafted cached sha1sums.txt + PUNKBIN_URL forced fetch failure), and the checkout-untouched guard (git status --porcelain identical before/after per acceptance)
- green on tcl 9.0.3 + 8.7 against Ubuntu-24.04/WSL2; suites skip cleanly when the probe fails; enablement/limitations in src/tests/AGENTS.md (known limitation: a present-but-HANGING wsl can stall the probe)
- GOALS.md G-059 flipped to achieved 2026-07-11; detail file records the outcome incl the UTF-16 discovery and the 512-byte label regression this work surfaced (caught by the guardrail suite as designed)

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 4 days ago
parent
commit
3a5a68ec9b
  1. 2
      GOALS.md
  2. 30
      goals/G-059-wsl-test-driving.md
  3. 1
      src/tests/AGENTS.md
  4. 2
      src/tests/modules/AGENTS.md
  5. 28
      src/tests/modules/punk/mix/testsuites/scriptwrap/multishell.test
  6. 1
      src/tests/shell/AGENTS.md
  7. 228
      src/tests/shell/testsuites/binscripts/runtimebash_wsl.test
  8. 133
      src/tests/testsupport/wslprobe.tcl

2
GOALS.md

@ -398,7 +398,7 @@ Detail: goals/G-058-static-runtime-packages.md
Goal: punkshell running on a runtime with statically-linked/builtin packages (tclsfe-x64: Thread/twapi/sqlite3/tdbc; punkbin runtimes' builtin Thread/tcllibc/vfs/vlerq; the expected shape of future zig-built static runtimes, G-005) keeps those packages resolvable in every interp and thread punkshell fabricates: boot captures the static baseline (info loaded entries with empty filename, plus their provided versions) before replacing package search paths, code interps and punkshell-created threads are seeded with ifneeded scripts mapping each static package to 'load {} <pkg>', and punk::packagepreference resolves static-vs-bundled by a documented version-aware policy instead of blindly loading a bundled dll over an already-provided static package.
Acceptance: a punk91-style kit (tclsfe-x64 + punk9win.vfs, no thread dll in the vfs) boots to a working repl with no "can't find package Thread" - punk::console loads in the code interp, and package require Thread succeeds there and in a punkshell-created worker thread, resolving to the static version; in the same kit, twapi resolves per the documented policy (no repeat of the observed static-Twapi-masked-by-older-vfs-twapi-5.0.2 double load; a genuinely newer bundled copy remains reachable by that policy); dll-based kits (punk902z) boot and pass their existing shell test baseline unchanged, as does a plain tclsh dev launch; the seeding mechanism is generic - driven by the captured baseline, no runtime-specific package naming - and the boot-time static baseline is introspectable at the repl; the seeding/preference logic is covered by un-gated unit tests against simulated baselines (runnable under plain tclsh), while kit-boot integration tests are gated behind a capability-probed tcltest constraint (a built kit whose baseline shows static entries including Thread) that skips cleanly when no such kit is present; the runtimes used for verification (tclsfe-x64.exe at minimum) are added to the punkbin artifact repository under win32-x86_64 with sha1sums.txt updated, so the constraint is satisfiable on other machines via the existing runtime-retrieval path; the punk91 code-interp vfs/vfs::zip load failure is re-diagnosed after the fix and either resolved or recorded as a distinct issue/candidate goal.
### G-059 [proposed] WSL detection and suitability probing for driving unix-side tests from Windows
### G-059 [achieved 2026-07-11] WSL detection and suitability probing for driving unix-side tests from Windows
Scope: src/tests/ (capability probe helpers + constraint-gated cases in existing suites, e.g the unix sh-payload execution test in modules/punk/mix/testsuites/scriptwrap/multishell.test and a runtime.bash behaviour test), src/tests/AGENTS.md (enablement notes)
Detail: goals/G-059-wsl-test-driving.md
Goal: test runs on a Windows dev machine can exercise selected unix-side behaviour through WSL when it is present AND suitable - a capability-probed tcltest constraint (not mere wsl.exe existence) verifies a launchable distro, required tools (bash, coreutils/sha1 tooling), and working one-way staging into the guest's NATIVE filesystem - with all guest-side execution happening on that native filesystem: per-test artifacts are staged into a WSL-native tempdir and results collected back, the Windows checkout is never operated on via the shared /mnt path (DrvFs is slow and cross-boundary stat differences make git re-hash its index and fossil see phantom changes), and any future full-suite mode uses a separate native clone rather than the shared working tree.

30
goals/G-059-wsl-test-driving.md

@ -1,6 +1,6 @@
# G-059 WSL detection and suitability probing for driving unix-side tests from Windows
Status: proposed
Status: achieved 2026-07-11
Scope: src/tests/ (capability probe helpers + constraint-gated cases in existing suites, e.g the unix sh-payload execution test in modules/punk/mix/testsuites/scriptwrap/multishell.test and a runtime.bash behaviour test), src/tests/AGENTS.md (enablement notes)
Acceptance: see GOALS.md index entry (canonical).
@ -62,3 +62,31 @@ a staged file. Any probe failure -> skip cleanly (never fail the suite).
cheapest provider of that guest-driving contract.
- WSL itself is a Windows feature (no licensing entanglement); distro choice is the
user's - the probe should not assume a specific distro, only capabilities.
## Outcome (achieved 2026-07-11)
- `src/tests/testsupport/wslprobe.tcl` (`::punktest::wsl`): memoized capability probe
(wsl.exe present -> default distro launches and unames as Linux -> tools present:
bash/mktemp/wslpath + a sha1 tool -> native tempdir file round trip; any failure
yields available=0 with a reason and consumers skip) plus staging helpers
(staging_create/staging_copy_in/run_in/staging_cleanup - cleanup refuses paths it
didn't create). Probe uses `wsl -e <cmd>` invocations only: management commands
(`wsl --status`, `-l`) emit UTF-16 - discovered during implementation.
- Consumers: `scriptwrap_multishell_exec_wsl_sh` in multishell.test (the polyglot's sh
payload produces its marker under the distro's bash from native staging - the direct
isunix twin still skips on windows) and the new
`src/tests/shell/testsuites/binscripts/runtimebash_wsl.test` (8 tests): runtime.bash's
first-ever execution on real unix - multi-candidate run errors with candidates,
use/list/run round trip with argument passing, PUNK_ACTIVE_RUNTIME override,
unknown/unselectable use errors, stale-active guidance, single-candidate fallback,
offline `list -remote` (crafted cached sha1sums.txt + PUNKBIN_URL forced-failure), and
the checkout-untouched guard (git status --porcelain identical before/after the
WSL-gated run - the native-staging design made verifiable).
- All green on Tcl 9.0.3 and 8.7 against Ubuntu-24.04/WSL2; suites skip cleanly when the
probe fails (constraint gating; helper absence also degrades to skip).
- Enablement/limitations recorded in src/tests/AGENTS.md (incl the known limitation: a
present-but-hanging WSL can stall the probe - erroring installs are handled).
- Bonus finding: the WSL list -remote test work triggered the polyglot's first real
512-byte label regression (payload growth pushed the template's :exit_multishell
across a boundary); fixed via a documented spacer comment in runtime.bash - the G-05x
guardrail suite caught it exactly as designed.

1
src/tests/AGENTS.md

@ -27,6 +27,7 @@ Top-level test harness and source-tree tests for ShellSpy/Punk. Tests here exerc
- `-report json` emits a machine-readable final summary, but package-load warnings may still precede it on stdout/stderr.
- ERROR-status failures now surface `errorInfo` (full Tcl error message/stack trace) in markdown, compact, and json reports. Compact mode truncates to a single line.
- FAILED-status failures (result mismatch, not error) now surface `result_was` (actual) and `result_expected` (expected) in markdown, compact, and json reports. Compact mode truncates each to a single line.
- `testsupport/` holds helper `.tcl` files sourced by `.test` files (not discovered as suites). `testsupport/wslprobe.tcl` (G-059) provides `::punktest::wsl`: a memoized capability probe yielding the `wsllinux` constraint (default distro launches, answers uname/tool probes, and round-trips a file through a native tempdir - NOT mere wsl.exe existence) plus native-filesystem staging helpers (`staging_create`/`staging_copy_in`/`run_in`/`staging_cleanup`). WSL-gated tests must execute from a staging dir on the distro's native filesystem with the shared `/mnt` path used only for one-way copy-in/out - never operate on the Windows checkout from inside WSL (DrvFs is slow and cross-boundary stat differences make git re-hash its index and fossil see phantom changes). Probe invocations use `wsl -e <cmd>` only (`wsl --status`/`-l` emit UTF-16). Known limitation: a broken-but-present WSL that hangs (rather than errors) on `wsl -e` can stall the probe.
## Work Guidance

2
src/tests/modules/AGENTS.md

@ -41,7 +41,7 @@ Unit tests for editable source modules under `src/modules/`, `src/modules_tcl8/`
- `punk/ansi/` — punk::ansi tests (`testsuites/ansi/`): ansistrip/ansimerge, plus characterization of the ANSI-at-position mechanisms (`ansistring.test`: INDEX/INDEXCODE/INDEXCHAR/RANGE/INSERT grapheme indexing with SGR-prefix merging, INDEXCOLUMNS/COLUMNINDEX double-wide column mapping, trim/VIEW), code splitting invariants (`ta.test`: detect/detectcode distinction, split_codes/split_codes_single/split_at_codes shapes and round-trip) and single-code/effective-state semantics (`codetype.test`: is_sgr_reset/has_sgr_leadingreset, has_any/all_effective, sgr_merge, sequence_type classify). ANSI codes in these tests are literal escape strings so results are colour-state independent
- `punk/args/` — punk::args tests (`testsuites/args/`): parsing, choices/choicegroups, forms, rendering/indentation characterization, usage-marking characterization (`usagemarking.test`: -parsedargs/-badarg/-parsestatus/-scheme marking primitives plus goodchoice highlighting of selected/default-in-effect choice words, asserted by SGR-parameter subset against the live colour arrays; the G-049 nocolour/colour-leak GAP pins flipped 2026-07-10 to scheme-statelessness assertions), and the G-049 parse-status structure (`parsestatus.test`: punk::args::parse_status overall/per-argument statuses, badarg for type/allocation failures, -caller attribution, errorcode -argspecs stripping)
- `punk/ns/` — punk::ns tests (`testsuites/ns/`): cmdwhich/cmdinfo/cmd_traverse doc-lookup flow (`cmdflow.test`, G-040 parity) and cmdhelp usage-rendering integration (`cmdhelp.test`: scheme selection, goodarg/badarg marking incl type/allocation failures, goodchoice highlighting of supplied/default choice words, alias path, cmdinfo result shape, queried-command failure attribution, and `-return dict` parse-status returns (G-049 - its GAP pins flipped 2026-07-10); remaining GAP pins for pseudo-command cmdtype + space-form docid prefixes (G-051, real `string is` pins behind the have_tclcoredocs constraint), TclOO undocumented-method fallback (G-052), and synopsis marking absence (G-050))
- `punk/mix/` — punk::mix::cli tests (prune helpers, punkcheck virtual sources), punk::mix::commandset::repo fossil move/rename characterization tests (`testsuites/repo/`, FOSSIL_HOME-isolated; GAP-marked tests pin behaviour G-022 will change), and the MULTISHELL polyglot build machinery (`testsuites/scriptwrap/multishell.test`: scriptset wrap via the punk.multishell.cmd template - structure/LF-only/determinism, checkfile 512-byte label validation of fresh wraps AND the committed bin/runtime.cmd, the runtime scriptset round-trip byte-identity pin, and platform-gated execution smoke: cmd.exe→powershell payload on windows, sh payload on unix)
- `punk/mix/` — punk::mix::cli tests (prune helpers, punkcheck virtual sources), punk::mix::commandset::repo fossil move/rename characterization tests (`testsuites/repo/`, FOSSIL_HOME-isolated; GAP-marked tests pin behaviour G-022 will change), and the MULTISHELL polyglot build machinery (`testsuites/scriptwrap/multishell.test`: scriptset wrap via the punk.multishell.cmd template - structure/LF-only/determinism, checkfile 512-byte label validation of fresh wraps AND the committed bin/runtime.cmd, the runtime scriptset round-trip byte-identity pin, and platform-gated execution smoke: cmd.exe→powershell payload on windows, sh payload on unix or via the `wsllinux` capability constraint from `src/tests/testsupport/wslprobe.tcl` - staged to the WSL distro's native filesystem, G-059)
- `punk/lib/` — punk::lib tests (`testsuites/lib/`): range/index/parse/compat/interp_sync utilities, and G-058 static-baseline seeding (`staticseed.test`: interp_sync_package_paths/snapshot_package_paths propagate a simulated ::punkboot static baseline and seed `load {} <prefix>` ifneeded mappings; no-op without a baseline)
- `punk/packagepreference/` — punk::packagepreference tests (`testsuites/packagepreference/`): G-058 static-vs-bundled policy (`staticpolicy.test`: require of a baseline package triggers the index scan before resolution so a newer bundled copy wins, static beats older bundled, exact requires of bundled versions stay reachable, missing static mappings get seeded)
- `punk/libunknown/` — .tm same-version shadowing pin-tests (`testsuites/shadowing/`): tcl::tm::add prepend rule, head-of-tm-list wins exact-version ties, version beats order, punk::libunknown parity — shipped behaviour depends on these (runtests tm ordering, punk_main package-mode precedence, G-033); mixed .tm/pkgIndex.tcl characterization is goal G-035

28
src/tests/modules/punk/mix/testsuites/scriptwrap/multishell.test

@ -232,5 +232,33 @@ namespace eval ::testspace {
-cleanup {
}\
-result [list 1]
#--- G-059: drive the sh payload under WSL from windows ----------------------------------
#Staged into the distro's NATIVE filesystem (never executed from the shared /mnt path)
#per the G-059 design - see src/tests/testsupport/wslprobe.tcl.
if {[file exists [file join $testdir .. .. .. .. .. testsupport wslprobe.tcl]]} {
source [file join $testdir .. .. .. .. .. testsupport wslprobe.tcl]
testConstraint wsllinux [expr {$::tcl_platform(platform) eq "windows" && [dict get [punktest::wsl::probe] available]}]
} else {
testConstraint wsllinux 0
}
test scriptwrap_multishell_exec_wsl_sh {a suitable WSL distro runs the polyglot's shell payload from a native-filesystem staging dir}\
-constraints {wrapok wsllinux}\
-setup {
set result ""
variable outfile
set stagingdir [punktest::wsl::staging_create]
punktest::wsl::staging_copy_in $stagingdir $outfile
}\
-body {
set r [punktest::wsl::run_in $stagingdir "bash ./[file tail $outfile] </dev/null"]
lappend result [dict get $r ok]
lappend result [string match "*MARKER_SHELL_OK*" [dict get $r output]]
}\
-cleanup {
punktest::wsl::staging_cleanup $stagingdir
}\
-result [list 1 1]
}
tcltest::cleanupTests ;#needed to produce test summary line.

1
src/tests/shell/AGENTS.md

@ -19,6 +19,7 @@ Tests for shell-level behavior, command-line execution, and stdin/stdout interac
- `testsuites/punkexe/shellexit.test` guards piped-stdin termination behaviour of the built executable (no `invalid thread handle` on shutdown races, no console-reopen hang on eof/error paths, exitcode propagation); do not weaken these tests - a red result means a regression in punk::repl shutdown or app-punkshell eof handling.
- `testsuites/punkexe/scriptexec.test` covers the `script` subcommand (app-punkscript, G-015): piped execution + honest exit codes, result echo, file-form argv, the `lib:<name>` scriptlib-resolution mechanism, and the tclsh-matching Tk main-loop behaviour. The `lib:` and Tk cases resolve committed fixtures under `scriptlib/_punktest/` (test-owned; a `lib:`-resolvable fixture cannot live in a tcltest temp dir since resolution only searches scriptlib locations relative to the exe). Tk cases are gated by the `punktk` constraint, auto-detected by probing whether the kit can load Tk.
- punkexe tests run against the built binary: after changing punk::repl or app-punkshell source, rebuild via `make.tcl packages`, `make.tcl vfscommonupdate` (requires interactive `y`), then `make.tcl project` before trusting results; `make.tcl project` alone does not refresh `_vfscommon.vfs`.
- `testsuites/binscripts/runtimebash_wsl.test` (G-059) exercises the runtime.cmd unix payload (`src/scriptapps/bin/runtime.bash`) through WSL: active/use/run resolution, env override, stale-active guidance, single-candidate fallback, and `list -remote` local-vs-server comparison (offline via a crafted cached sha1sums.txt + `PUNKBIN_URL` pointed at an unreachable address). Gated by the `wsllinux` capability constraint from `src/tests/testsupport/wslprobe.tcl`; all execution happens in a WSL-native staging dir, and a final test asserts the Windows checkout's git status is unchanged by the run.
- `testsuites/punkexe/staticruntime.test` (G-058) targets a kit built on a runtime with statically-linked packages: resolved from `env(PUNK_STATICKIT_TEST_EXE)`, else `<projectroot>/bin/punk91.exe`, gated by the capability-probed `statickitavailable` constraint (a probe run must show a captured `::punkboot::static_packages` baseline including Thread - not mere file existence). It asserts repl boot without the codethread Thread failure, Thread resolution in the code interp / fabricated interps / snapshot-seeded threads, and twapi resolution per the static-vs-bundled policy. The verification runtime (tclsfe-x64.exe) is pinned in the punkbin artifact repo so the constraint is satisfiable off the original dev machine. Kit script-exec paths must be passed with forward slashes.
- GOAL tests (constraint `punkgoals`, enabled by env `PUNK_TEST_GOALS=1`) cover the piped-stdin-then-interactive-shell drop-in (`shellpipe_eof_then_interactive_shell`, `shellpipe_script_and_eof_then_interactive_shell`); they are excluded from normal runs because they require an openable console in the test environment and briefly attach a live shell to it - they pass in console-attached environments against a current build.
- The eof-policy discriminator is `env(PUNK_PIPE_EOF)` (`exit`|`interactive`|unset=console heuristic) implemented in app-punkshell; termination-guard tests spawn children with `exit`, goal tests spawn with it unset.

228
src/tests/shell/testsuites/binscripts/runtimebash_wsl.test

@ -0,0 +1,228 @@
package require tcltest
#G-059: behaviour tests for the runtime.cmd unix payload (src/scriptapps/bin/runtime.bash)
#driven through WSL from a windows dev machine. The payload had never been executed on
#real unix before this suite - only syntax-checked (bash -n / zsh -n).
#
#Staging design (goals/G-059-wsl-test-driving.md): runtime.bash plus a fixture runtime
#folder (fake executable "runtimes" that just echo markers) are staged ONE-WAY into a
#tempdir on the distro's NATIVE filesystem and all execution happens there. The windows
#checkout is never operated on from inside WSL, so git/fossil state cannot be disturbed
#(asserted by the final test via a before/after git status comparison).
#
#Covered (offline - no network/fetch cases; those are future env-gated work):
# - run with multiple runtimes and no active selection errors listing candidates
# - use <name> records active.toml; list marks the active entry; run launches it
# - PUNK_ACTIVE_RUNTIME env override wins over active.toml
# - use with an unknown/unselectable name errors
# - stale active.toml (file removed) errors with reselect guidance
# - single-candidate fallback when no active is recorded
namespace eval ::testspace {
namespace import ::tcltest::*
variable common {
set result ""
}
variable testdir [file dirname [file normalize [info script]]]
#<projectroot>/src/tests/shell/testsuites/binscripts -> src/tests is 3 levels up
variable testsroot [file normalize [file join $testdir .. .. ..]]
variable projectroot [file normalize [file join $testsroot .. ..]]
if {[file exists [file join $testsroot testsupport wslprobe.tcl]]} {
source [file join $testsroot testsupport wslprobe.tcl]
testConstraint wsllinux [expr {$::tcl_platform(platform) eq "windows" && [dict get [punktest::wsl::probe] available]}]
} else {
testConstraint wsllinux 0
}
variable runtimebash [file join $projectroot src scriptapps bin runtime.bash]
testConstraint havesource [file exists $runtimebash]
#vcs-state guard (G-059 acceptance): snapshot before any WSL activity in this file
variable git_status_before ""
catch {
variable git_status_before [exec {*}[auto_execok git] -C $projectroot status --porcelain]
}
#one staging area for the whole file: runtime.bash + runtime/linux-x86_64 fixture
#with two fake "runtimes" (echo scripts). Rebuilt fresh via reset_fixture per test.
variable stagingdir ""
if {[testConstraint wsllinux] && [testConstraint havesource]} {
set stagingdir [punktest::wsl::staging_create]
punktest::wsl::staging_copy_in $stagingdir $runtimebash
punktest::wsl::run_in $stagingdir {
mkdir -p runtime/linux-x86_64 &&
printf '#!/bin/sh\necho FAKERT_ALPHA_RAN "$@"\n' > runtime/linux-x86_64/fakert_alpha &&
printf '#!/bin/sh\necho FAKERT_BETA_RAN "$@"\n' > runtime/linux-x86_64/fakert_beta &&
chmod +x runtime/linux-x86_64/fakert_alpha runtime/linux-x86_64/fakert_beta
}
}
#run runtime.bash inside the staging dir with args; returns dict ok/output
proc rtbash {args} {
variable stagingdir
return [punktest::wsl::run_in $stagingdir "bash ./runtime.bash $args"]
}
proc reset_fixture {} {
variable stagingdir
punktest::wsl::run_in $stagingdir {rm -f runtime/linux-x86_64/active.toml runtime/linux-x86_64/fakert_gamma}
}
test runtimebash_run_multi_no_active_errors {run with multiple runtimes and no active selection errors and lists candidates}\
-constraints {wsllinux havesource}\
-setup {
set result ""
reset_fixture
}\
-body {
set r [rtbash run]
lappend result [dict get $r ok]
lappend result [string match "*Multiple runtimes installed and no active runtime selected*" [dict get $r output]]
lappend result [string match "*fakert_alpha*" [dict get $r output]]
lappend result [string match "*fakert_beta*" [dict get $r output]]
}\
-cleanup {
}\
-result [list 0 1 1 1]
test runtimebash_use_records_and_run_launches {use records active.toml, list marks it, run launches the selected runtime with arguments}\
-constraints {wsllinux havesource}\
-setup {
set result ""
reset_fixture
}\
-body {
set r [rtbash use fakert_beta]
lappend result [dict get $r ok]
set r [punktest::wsl::run_in $::testspace::stagingdir {cat runtime/linux-x86_64/active.toml}]
lappend result [string trim [dict get $r output]]
set r [rtbash list]
lappend result [string match {*\* fakert_beta (active)*} [dict get $r output]]
set r [rtbash run hello world]
lappend result [dict get $r ok]
lappend result [string match "*FAKERT_BETA_RAN hello world*" [dict get $r output]]
}\
-cleanup {
}\
-result [list 1 {active = "fakert_beta"} 1 1 1]
test runtimebash_env_override_wins {PUNK_ACTIVE_RUNTIME env override wins over active.toml}\
-constraints {wsllinux havesource}\
-setup {
set result ""
reset_fixture
}\
-body {
rtbash use fakert_beta
variable stagingdir
set r [punktest::wsl::run_in $stagingdir "PUNK_ACTIVE_RUNTIME=fakert_alpha bash ./runtime.bash run"]
lappend result [dict get $r ok]
lappend result [string match "*FAKERT_ALPHA_RAN*" [dict get $r output]]
}\
-cleanup {
}\
-result [list 1 1]
test runtimebash_use_unknown_errors {use with an unknown name errors and lists candidates; non-runtime extensions are unselectable}\
-constraints {wsllinux havesource}\
-setup {
set result ""
reset_fixture
}\
-body {
set r [rtbash use no_such_runtime]
lappend result [dict get $r ok]
lappend result [string match "*No runtime named 'no_such_runtime'*" [dict get $r output]]
set r [rtbash use sha1sums.txt]
lappend result [dict get $r ok]
lappend result [string match "*not a selectable runtime*" [dict get $r output]]
}\
-cleanup {
}\
-result [list 0 1 0 1]
test runtimebash_stale_active_errors {a recorded active runtime that is no longer present errors with reselect guidance}\
-constraints {wsllinux havesource}\
-setup {
set result ""
reset_fixture
}\
-body {
variable stagingdir
punktest::wsl::run_in $stagingdir {printf 'active = "gone_runtime"\n' > runtime/linux-x86_64/active.toml}
set r [rtbash run]
lappend result [dict get $r ok]
lappend result [string match "*active runtime 'gone_runtime'*is not present*" [dict get $r output]]
lappend result [string match "*use <name>*" [dict get $r output]]
}\
-cleanup {
}\
-result [list 0 1 1]
test runtimebash_single_candidate_fallback {with exactly one installed runtime and no active recorded, run uses it}\
-constraints {wsllinux havesource}\
-setup {
set result ""
reset_fixture
}\
-body {
variable stagingdir
punktest::wsl::run_in $stagingdir {mkdir -p solo/runtime/linux-x86_64 && cp runtime.bash solo/ && cp runtime/linux-x86_64/fakert_alpha solo/runtime/linux-x86_64/}
set r [punktest::wsl::run_in $stagingdir "cd solo && bash ./runtime.bash run"]
lappend result [dict get $r ok]
lappend result [string match "*FAKERT_ALPHA_RAN*" [dict get $r output]]
}\
-cleanup {
}\
-result [list 1 1]
test runtimebash_list_remote_cached_compare {list -remote compares local runtimes against sha1sums.txt (offline: forced fetch failure falls back to the cached copy)}\
-constraints {wsllinux havesource}\
-setup {
set result ""
reset_fixture
}\
-body {
variable stagingdir
#craft a cached sha1sums.txt: correct hash for alpha, wrong hash for beta,
#plus a remote-only entry; PUNKBIN_URL points nowhere so curl fails fast
punktest::wsl::run_in $stagingdir {
a=$(sha1sum runtime/linux-x86_64/fakert_alpha | cut -d' ' -f1) &&
printf '%s *fakert_alpha\n0000000000000000000000000000000000000000 *fakert_beta\n1111111111111111111111111111111111111111 *fakert_remote_only\n' "$a" > runtime/linux-x86_64/sha1sums.txt
}
set r [punktest::wsl::run_in $stagingdir "PUNKBIN_URL=http://127.0.0.1:9/nowhere bash ./runtime.bash list -remote"]
lappend result [dict get $r ok]
set out [dict get $r output]
lappend result [string match "*using cached copy*" $out]
lappend result [regexp {fakert_alpha\s+Same version} $out]
lappend result [regexp {fakert_beta\s+UPDATE AVAILABLE} $out]
lappend result [regexp -- {-\s+fakert_remote_only} $out]
}\
-cleanup {
punktest::wsl::run_in $::testspace::stagingdir {rm -f runtime/linux-x86_64/sha1sums.txt}
}\
-result [list 1 1 1 1 1]
test runtimebash_wsl_leaves_checkout_untouched {WSL-gated runs do not disturb the windows checkout's git state (G-059 native-staging design)}\
-constraints {wsllinux havesource}\
-setup {
set result ""
}\
-body {
variable git_status_before
variable projectroot
set git_status_after ""
catch {
set git_status_after [exec {*}[auto_execok git] -C $projectroot status --porcelain]
}
lappend result [expr {$git_status_after eq $git_status_before}]
}\
-cleanup {
}\
-result [list 1]
#whole-file staging cleanup
if {[testConstraint wsllinux] && [testConstraint havesource] && $stagingdir ne ""} {
punktest::wsl::staging_cleanup $stagingdir
}
}
tcltest::cleanupTests ;#needed to produce test summary line.

133
src/tests/testsupport/wslprobe.tcl

@ -0,0 +1,133 @@
#wslprobe.tcl - G-059 test support: WSL capability probing + native-filesystem staging
#
#Sourced by .test files (runtests.tcl only discovers *.test - helper .tcl files are not
#run as suites). Provides ::punktest::wsl with:
# probe - memoized capability probe; dict: available 0|1, reason, uname
# winpath_to_wsl - translate a windows path for use inside the distro (wslpath -u)
# staging_create - mktemp -d on the distro's NATIVE filesystem
# staging_copy_in - one-way copy of windows files into a staging dir
# run_in - run a bash command with cwd set to a staging dir; returns
# dict: ok 0|1 output <combined stdout+stderr>
# staging_cleanup - rm -rf a staging dir (guarded: only paths staging_create made)
#
#DESIGN (see goals/G-059-wsl-test-driving.md): all guest-side execution happens on the
#distro's native filesystem. The shared /mnt path is used only for one-way copy-in of
#artifacts - the windows checkout is never operated on from inside WSL (DrvFs is slow
#and cross-boundary stat differences make git re-hash its index and fossil see phantom
#changes; two VCS clients must never share a working tree).
#
#ENCODING: wsl.exe management commands (--status, -l) emit UTF-16 - the probe only ever
#uses 'wsl -e <cmd>' invocations, whose output is the command's own (UTF-8).
#
#CAVEAT: a broken-but-present WSL install that HANGS (rather than erroring) on first
#'wsl -e' can stall the probe - known limitation, documented in src/tests/AGENTS.md.
namespace eval ::punktest::wsl {
variable probe_result ""
variable created_stagingdirs [list]
proc wslexe {} {
return [auto_execok wsl]
}
#memoized capability probe - capability-based, not wsl.exe-existence-based:
#the distro must launch, answer uname, have the required tools, and round-trip a
#file through a native tempdir. Any failure -> available 0 with a reason (skip, not fail).
proc probe {} {
variable probe_result
if {$probe_result ne ""} {
return $probe_result
}
set result [dict create available 0 reason "" uname ""]
set wsl [wslexe]
if {![llength $wsl]} {
dict set result reason "wsl.exe not found"
set probe_result $result
return $result
}
#distro launches and is linux
if {[catch {exec {*}$wsl -e uname -sm} unamesm]} {
dict set result reason "wsl -e uname failed (no usable default distro?): [lindex [split $unamesm \n] 0]"
set probe_result $result
return $result
}
set unamesm [string trim $unamesm]
dict set result uname $unamesm
if {![string match "Linux*" $unamesm]} {
dict set result reason "default distro uname '$unamesm' is not Linux"
set probe_result $result
return $result
}
#required tools
if {[catch {exec {*}$wsl -e bash -c {command -v bash mktemp wslpath >/dev/null && (command -v sha1sum >/dev/null || command -v shasum >/dev/null) && echo TOOLS_OK}} toolsout]
|| ![string match "*TOOLS_OK*" $toolsout]} {
dict set result reason "required tools missing in distro (bash/mktemp/wslpath + sha1 tool)"
set probe_result $result
return $result
}
#native staging round trip
if {[catch {
set tmpd [string trim [exec {*}$wsl -e mktemp -d]]
if {![string match "/*" $tmpd]} {
error "unexpected mktemp result '$tmpd'"
}
set echoed [string trim [exec {*}$wsl -e bash -c "echo punkprobe > $tmpd/probe.txt && cat $tmpd/probe.txt && rm -rf $tmpd"]]
if {$echoed ne "punkprobe"} {
error "staging round trip mismatch '$echoed'"
}
} errM]} {
dict set result reason "native tempdir staging round trip failed: [lindex [split $errM \n] 0]"
set probe_result $result
return $result
}
dict set result available 1
dict set result reason "ok"
set probe_result $result
return $result
}
proc winpath_to_wsl {winpath} {
set wsl [wslexe]
return [string trim [exec {*}$wsl -e wslpath -u [file normalize $winpath]]]
}
proc staging_create {} {
variable created_stagingdirs
set wsl [wslexe]
set tmpd [string trim [exec {*}$wsl -e mktemp -d -t punktest.XXXXXX]]
if {![string match "/tmp/*" $tmpd]} {
error "wsl staging_create: unexpected tempdir '$tmpd'"
}
lappend created_stagingdirs $tmpd
return $tmpd
}
#one-way copy-in: read each windows file over the shared path ONCE, into the native dir
proc staging_copy_in {stagingdir args} {
set wsl [wslexe]
foreach winfile $args {
set src [winpath_to_wsl $winfile]
exec {*}$wsl -e cp $src $stagingdir/
}
}
#run a bash command line with cwd at the staging dir. Returns dict: ok, output.
proc run_in {stagingdir cmdline} {
set wsl [wslexe]
set script "cd $stagingdir && $cmdline"
if {[catch {exec {*}$wsl -e bash -c $script 2>@1} output]} {
return [dict create ok 0 output $output]
}
return [dict create ok 1 output $output]
}
proc staging_cleanup {stagingdir} {
variable created_stagingdirs
if {$stagingdir ni $created_stagingdirs} {
error "wsl staging_cleanup: refusing to remove '$stagingdir' (not created by staging_create)"
}
set wsl [wslexe]
catch {exec {*}$wsl -e rm -rf $stagingdir}
set created_stagingdirs [lsearch -all -inline -not -exact $created_stagingdirs $stagingdir]
}
}
Loading…
Cancel
Save