diff --git a/src/tests/modules/AGENTS.md b/src/tests/modules/AGENTS.md index b224dafc..a49206bc 100644 --- a/src/tests/modules/AGENTS.md +++ b/src/tests/modules/AGENTS.md @@ -42,7 +42,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), grepstr characterization (`grepstr.test`: return modes incl summarydict (linemap pinned as always-present - the -help says -n-only, reconciliation deferred to the planned hygiene pass), exact highlight SGR wrapping, -n line numbering, invert + empty-highlight strip, -C context/breaks, capture groups, and the tab deficiency: warns once per call on stderr, single-pass tab line survives - the multi-pass mangling is pinned at consumer level in punk/ns corp.test), and untabify characterization (`untabify.test`: -stops int/list/terminal, -with spaces/unicode/custom-pair, multiline, errors, plus the EXPERIMENTAL -plastic elastic-tabstop mode deliberately pinned-as-interim and retained for possible repl editbuf use). Console queries (get_tabstops/get_size + punk::console::tabwidth) are mocked per the overtype renderline.test pattern - they emit live terminal queries that block/error headless. 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, synopsis display characterization (`synopsis.test`: basic italic argname/`` styling, longopt `--x=` alias forms, literal/literalprefix/stringstartswith/stringendswith type-alternates rendering unitalicised, option alternate parenthesization, multi-element clause display incl `?type?` members and argname tail-word hints, `-typesynopsis` value-element lists and option passthrough incl documenter ANSI, and the small-restricted-choice-set literal rule: 1-3 restricted choices render as unitalicised `|`-joined literals in leader/option/value positions with choicegroups counted, >3 or `-choicerestricted 0` falling back to italics, `-typesynopsis` taking precedence), 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), 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), -parsekey characterization (`parsekey.test`: result/received/solos/multis keying, shared-key required satisfaction and defaults, mash-path and prefix-abbreviation keying, plus GAP pins for last-defined-member default precedence, cross-member -multiple value loss, parsekey/optname collision conflation, and values/leaders parsekey breakage - desired-behaviour pins disabled behind punkargsKnownBug in `testsuites/dev/parsekey-knownbugs.test`), and tclcore doc/interpreter behavioural parity (`tclcoreparity.test`, G-054, gated on have_tclcoredocs: 'string is' class choices equal the live-harvested set, per-class docids exist, error-vs-ok agreement across the probe matrix, version-note labels conditional on class presence - expectations derived from the running interpreter, green on 8.6/8.7/9.0; under 8.6 run the file directly via a plain tclkit + tcltest driver since runtests' harness needs newer infrastructure) - `punk/nav/ns/` — punk::nav::ns tests (`testsuites/nav/navns.test`): the n/ n// n/// navigation state machine (ns/ transitions absolute/relative/glob-no-nav, failed-nav state preservation, quad-colon normalization, v-form content selection, ensemble annotation) and the ::punk::nav::ns::ns_current variable contract the repl/codethread/subshell seeding all consume; display content is covered in punk/ns nslist.test -- `punk/repl/` — punk::repl tests (`testsuites/repl/`): opunk console backend integration (`consolebackends.test`) and repl current-namespace retention (`nscurrent.test`: real codethread via repl::init driven by synchronous runscript sends - inscope evaluation of ns_current, retention across submissions, n/-navigation retained, auto-create-with-notice for missing namespaces, the 2026-07-14 stray-namespace seeding fix pinned behaviourally plus a source-text guard on repl::start's inline template; end-to-end piped subshell carry-over (E3) remains a planned shell-level test) +- `punk/repl/` — punk::repl tests (`testsuites/repl/`): opunk console backend integration (`consolebackends.test`) and repl current-namespace retention (`nscurrent.test`: real codethread via repl::init driven by synchronous runscript sends - inscope evaluation of ns_current, retention across submissions, n/-navigation retained, auto-create-with-notice for missing namespaces, the 2026-07-14 stray-namespace seeding fix pinned behaviourally plus a source-text guard on repl::start's inline template; the end-to-end piped subshell session is covered at shell level by shell/testsuites/punkexe/shellnavns.test - which found the first-subshell shared-code-interp asymmetry and the piped-inscope gap recorded there) - `punk/ns/` — punk::ns tests (`testsuites/ns/`): cmdwhich/cmdinfo/cmd_traverse doc-lookup flow (`cmdflow.test`, G-040 parity), n/ display machinery characterization (`nslist.test`: tier A get_ns_dicts classification buckets as the machine contract for display reworks - incl package tail/prefix derivation, alias edge cases, usageinfo scan-dependence; tier B per-element layout-agnostic marking - underline/underdouble/underdotted namespace package styles, command type tag colours, exported/imported markers, the punkargs doc icon; tier C REWORK-flagged pins of the current hardcoded 2-col/4-col layout and nspath subtables, to flip deliberately with the planned punk-tables/width-responsive rework; plus the KNOWN QUIRK pin that bare nslist without -types errors with a malformed message), corp proc-retrieval and syntax/untabify interplay (`corp.test`: name edge cases, -ranges/-n line handling, basic-highlight ansistrip equivalence, -untabify spaces/unicode tab-free output, the KNOWN-DEFICIENCY pin for default -untabify none on tabbed bodies - grepstr warns per pass and brace overlays mangle tabbed lines, deterministic under mocked console tabstops - and a ::tcl::CopyDirectory -untabify spaces smoke test; precursor coverage for the planned punk::ns hygiene pass), cmdtrace characterization (`cmdtrace.test`: -pause 0 non-interactive runs, linedict line-mark keys for flat and 2-word-form nested switches as correct-mark guards, and GAP pins for the upstream nested-switch mismark - core.tcl-lang.org tktview 5d5b1052280c976ea3d4, arm bodies whose split-list index lands on a literal switch-command word report container-relative lines; mark tests gated on have_tclcoredocs because cmdtrace's arm-offset correction parses against the ::switch argdoc; plus the fixed-canary asserting punk::lib::check::has_tclbug_nestedswitch_tracelines still reports the bug - a live behavioural probe, so a fixed Tcl release fails the canary first and triggers the documented flip workflow), 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), punk::mix::commandset::loadedlib tests (`testsuites/loadedlib/libsearch.test`: 'dev lib.search' match semantics via -return list — wrap-glob default, =exact prefix, case rules, explicit globs, version aggregation — plus the loadedlib 0.2.0 contract: deep discovery by default (deep .tm modules found without -refresh, registration persists), -refresh = genuine re-scan (epoch incr + rediscovery picks up .tm files added to already-scanned dirs), and highlight working without the shell-global a+ alias; shared provisioned child interp sourcing the source-tree libunknown directly — see the file's ORDERING NOTE), 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, G-058 static-baseline seeding (`staticseed.test`: interp_sync_package_paths/snapshot_package_paths propagate a simulated ::punkboot static baseline and seed `load {} ` ifneeded mappings; no-op without a baseline), and the repl command-completeness engine (`commandcomplete.test`: punk::lib::system::incomplete pending-opener stacks - the info-complete quoting quirk progression (`set x "{*}{"` standalone vs in-proc-body), single openers, tabs, escapes, incomplete<->info-complete parity property; pre-repl-refactor characterization, see goals/G-044 detail preserve-list) diff --git a/src/tests/shell/AGENTS.md b/src/tests/shell/AGENTS.md index dd2cf14a..6473d0ca 100644 --- a/src/tests/shell/AGENTS.md +++ b/src/tests/shell/AGENTS.md @@ -17,6 +17,7 @@ Tests for shell-level behavior, command-line execution, and stdin/stdout interac - `testsuites/punkexe/` targets a built punk executable: resolved from `env(PUNK_SHELL_TEST_EXE)`, else `/bin/punk902z.exe`, else `/bin/punkshell902`; tests auto-skip via the `punkexeavailable` constraint when none is found. - Tests that spawn the built executable must be hang-proof: use the event-loop `punk_run` pattern (pipe stdin, half-close for EOF, timeout then force-kill) rather than plain `exec`, because known failure modes leave the child waiting on a reopened console. - `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/shellnavns.test` proves the namespace-navigation session contract end-to-end over a piped 'src'-mode session (dev modules - deliberate, so current punk::repl/nav source is exercised without a rebuild): n/ sets ns_current, the current namespace continues into `subshell punk` and back out (currently via the documented first-subshell shared-code-interp asymmetry - the SHARED marker pins it; the repl::start seeding template for genuinely separate interps is proven in modules/punk/repl nscurrent.test), the punk::repl 0.5.2 stray-namespace fix holds live, and the session exits cleanly. KNOWN GAP pinned there: piped submissions evaluate at :: despite ns_current (interactive and module-level inscope behaviour differ) - investigate alongside G-038. - `testsuites/punkexe/scriptexec.test` covers the `script` subcommand (app-punkscript, G-015): piped execution + honest exit codes, result echo, file-form argv, the `lib:` 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 -confirm 0` (unattended runs must pass the flag - the REPLACE confirmation aborts fast on non-interactive stdin instead of reading a piped `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. diff --git a/src/tests/shell/testsuites/punkexe/shellnavns.test b/src/tests/shell/testsuites/punkexe/shellnavns.test new file mode 100644 index 00000000..99d6c7e2 --- /dev/null +++ b/src/tests/shell/testsuites/punkexe/shellnavns.test @@ -0,0 +1,196 @@ +package require tcltest + +#added 2026-07-14 (agent) - E3 shell-level proof of namespace navigation state and +#the subshell namespace carry-over (companions: punk/nav/ns navns.test for the ns/ +#state machine, punk/repl nscurrent.test for codethread inscope/retention/seeding). +# +#One piped session against the built executable in 'src' launch mode (dev modules - +#required so the punk::repl 0.5.2 stray-namespace seeding fix and current nav code +#are what actually runs; a plain kit launch would use kit-stamped modules). +# +#PROVEN here: +# - n/ sets ::punk::nav::ns::ns_current in the shell's code interp +# - the current namespace CONTINUES into a launched 'subshell punk' (the +# deliberate carry-over contract). Mechanism finding (2026-07-14 probes): the +# FIRST subshell evaluates in the SAME code interp as the parent shell (the +# 'first subshell asymmetry' TODO documented in punk::repl repl::init) - so +# continuity here manifests via shared state, pinned by the SHARED marker +# below. The repl::start seeding template (the carry-over mechanism for a +# genuinely separate code interp) is proven at module level against a real +# codethread in punk/repl nscurrent.test. +# - no stray ::punk::nav::ns::ns_current NAMESPACE exists after subshell launch +# (punk::repl 0.5.2 seeding-template fix) +# - navigation inside the subshell works, and with the shared context the parent +# sees the subshell's final namespace after exit (state continuity BOTH ways - +# a G-002-style non-nested/isolated subshell would change this pin) +# - subshell exit unwinds to the parent which keeps consuming piped input, and the +# session terminates cleanly (exitcode 0, no timeout) +# +#KNOWN GAP pinned (flip deliberately when addressed): +# - piped-session submissions evaluate at :: even after n/ has set ns_current - +# the inscope application observed interactively (and proven at module level +# against a real codethread in punk/repl nscurrent.test) does not take effect +# for subsequent piped submissions in this spawn shape. Root cause undetermined +# (candidates: piped-path evaluation difference, kit/codethread interplay in the +# spawned process) - investigate alongside G-038 session-context work. +namespace eval ::testspace { + namespace import ::tcltest::* + + variable testdir [file dirname [file normalize [info script]]] + variable projectroot [file normalize [file join $testdir .. .. .. .. ..]] + + variable punkexe "" + if {[info exists ::env(PUNK_SHELL_TEST_EXE)] && $::env(PUNK_SHELL_TEST_EXE) ne ""} { + set punkexe [file normalize $::env(PUNK_SHELL_TEST_EXE)] + } else { + foreach candidate [list [file join $projectroot bin punk902z.exe] [file join $projectroot bin punkshell902]] { + if {[file exists $candidate]} { + set punkexe $candidate + break + } + } + } + testConstraint punkexeavailable [expr {$punkexe ne "" && [file exists $punkexe]}] + + variable punk_run_timeout_ms 60000 + + variable runstate + array set runstate {} + + proc punk_run_read {chan} { + variable runstate + append runstate(output) [read $chan] + if {[chan eof $chan]} { + chan event $chan readable {} + set runstate(done) eof + } + } + + #as per shellexit.test punk_run: pipe stdin, half-close for EOF, timeout+kill + proc punk_run {exe cmdargs stdin_data} { + variable runstate + variable punk_run_timeout_ms + array unset runstate + set runstate(output) "" + set runstate(done) "" + + set env_had [info exists ::env(PUNK_PIPE_EOF)] + if {$env_had} { + set env_save $::env(PUNK_PIPE_EOF) + } + set ::env(PUNK_PIPE_EOF) exit + set chan [open |[list $exe {*}$cmdargs 2>@1] r+] + if {$env_had} { + set ::env(PUNK_PIPE_EOF) $env_save + } else { + unset ::env(PUNK_PIPE_EOF) + } + set pids [pid $chan] + chan configure $chan -blocking 0 -translation binary + catch { + puts -nonewline $chan $stdin_data + flush $chan + chan close $chan write + } + set timerid [after $punk_run_timeout_ms [list set [namespace current]::runstate(done) timeout]] + chan event $chan readable [list [namespace current]::punk_run_read $chan] + while {$runstate(done) eq ""} { + vwait [namespace current]::runstate(done) + } + after cancel $timerid + chan event $chan readable {} + + set timedout [expr {$runstate(done) eq "timeout"}] + set exitcode "" + if {$timedout} { + foreach p $pids { + if {$::tcl_platform(platform) eq "windows"} { + catch {exec {*}[auto_execok taskkill] /F /PID $p} + } else { + catch {exec kill -9 $p} + } + } + catch {close $chan} + } else { + catch {chan configure $chan -blocking 1} + if {[catch {close $chan} errmsg erropts]} { + set ecode [dict get $erropts -errorcode] + switch -- [lindex $ecode 0] { + CHILDSTATUS { + set exitcode [lindex $ecode 2] + } + default { + set exitcode $ecode + } + } + } else { + set exitcode 0 + } + } + return [dict create timedout $timedout exitcode $exitcode output $runstate(output)] + } + + #extract MARKER=value occurrences from (ansi-laden) combined output + proc markvals {output marker} { + set vals [list] + foreach {_ v} [regexp -all -inline [string cat $marker {=([^\r\n\x1b]*)}] $output] { + lappend vals $v + } + return $vals + } + + + test shellnavns_session_nav_carryover_and_gaps {piped session: n/ state, subshell namespace carry-over, seeding-fix, exit unwinding - with the piped-inscope and parent-reset gaps pinned}\ + -constraints punkexeavailable\ + -setup { + set result "" + }\ + -body { + variable punkexe + set session "" + append session {namespace eval ::carrye3 {namespace eval sub {}}} \n + append session {n/ ::carrye3} \n + append session {puts PARENTNS=$::punk::nav::ns::ns_current} \n + append session {puts PARENTCUR=[namespace current]} \n + append session {subshell punk} \n + append session {puts SUBNS=$::punk::nav::ns::ns_current} \n + append session {puts PHANTOM=[namespace exists ::punk::nav::ns::ns_current]} \n + append session {n/ ::carrye3::sub} \n + append session {puts SUBNAV=$::punk::nav::ns::ns_current} \n + append session {set ::submarker sub-was-here} \n + append session {exit} \n + append session {puts PARENTAFTER=$::punk::nav::ns::ns_current} \n + append session {puts SHARED=[info exists ::submarker]} \n + append session {exit} \n + + set rd [punk_run $punkexe [list src] $session] + set output [dict get $rd output] + + lappend result [dict get $rd timedout] [dict get $rd exitcode] + #n/ set the parent's ns_current + lappend result [markvals $output PARENTNS] + #KNOWN GAP: subsequent piped submissions still evaluate at :: (see header) + lappend result [markvals $output PARENTCUR] + #the subshell carry-over: new codethread seeded from parent's ns_current + lappend result [markvals $output SUBNS] + #punk::repl 0.5.2 fix: no stray namespace named like the variable + lappend result [markvals $output PHANTOM] + #navigation inside the subshell takes effect + lappend result [markvals $output SUBNAV] + #shared context: parent sees the subshell's final namespace after exit + #(and the subshell's variable - the first-subshell asymmetry) + lappend result [markvals $output PARENTAFTER] + lappend result [markvals $output SHARED] + }\ + -result [list\ + 0 0\ + ::carrye3\ + ::\ + ::carrye3\ + 0\ + ::carrye3::sub\ + ::carrye3::sub\ + 1 + ] +} +tcltest::cleanupTests ;#needed to produce test summary.