Browse Source
New shell/testsuites/punkexe/shellnavns.test - one hang-proof punk_run piped session against the built executable in src launch mode (dev modules, so current punk::repl/nav source is exercised without a rebuild): PROVEN end-to-end: - n/ sets ::punk::nav::ns::ns_current in the running shell - the current namespace continues into `subshell punk` and back out, and the 0.5.2 stray-namespace seeding fix holds live (no phantom ns_current namespace in the subshell context) - navigation inside the subshell takes effect; the session exits cleanly (exitcode 0, no timeout, exit unwinds subshell -> parent -> terminate) FINDINGS from the E3 probes (pinned in the test with markers): - the FIRST subshell evaluates in the SAME code interp as the parent shell - the "first subshell asymmetry" already TODO-documented in repl::init. Continuity therefore manifests via shared state at shell level (SHARED marker: a variable set in the subshell is visible to the parent after exit, and the parent adopts the subshell''s final namespace). The repl::start seeding template - the carry-over mechanism for genuinely separate code interps - is proven at module level in punk/repl nscurrent.test. A G-002-style isolated subshell would flip these pins. - KNOWN GAP: piped-session submissions evaluate at :: even after n/ sets ns_current - the inscope application proven interactively and at module level does not take effect for subsequent piped submissions in this spawn shape. Root cause undetermined (piped-path evaluation vs kit/codethread interplay); flagged for investigation alongside G-038 session-context work. Verified: shell/** suite 42 total / 40 passed / 0 failed (2 gated skips) under Tcl 9.0.3. AGENTS updated (shell contracts bullet; punk/repl module index E3 reference). Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.commaster
3 changed files with 198 additions and 1 deletions
@ -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. |
||||||
Loading…
Reference in new issue