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