#child_test_runner.tcl - child-process bootstrap for src/tests/runtests.tcl multi-process mode #(-tcltestoptions {-singleproc 0}). #Invoked as: child_test_runner.tcl ?? #The payload file is generated per run by runtests.tcl and applies the parent-computed test #environment in the singleproc-testinterp order: package prefer latest, tcl::tm test paths, #auto_path, modpod 'package ifneeded' definitions, and the base tcltest options in #::runtests_child_tcltestoptions. #The optional childtmpdir argument overrides tcltest -tmpdir per child (parallel scheduling); #empty/absent means the shared -tmpdir carried in the payload options is used. #Exit codes: 0 = test file ran to completion (tcltest failures are reported via the tcltest #output stream, not the exit code - matching tcltest single-file semantics); nonzero = the test #file (or this bootstrap) died, which runtests.tcl classifies as a file-level failure with the #stderr tail surfaced; 98 = bootstrap usage/environment error. #Not a test suite: runtests.tcl discovery excludes *.tcl under src/tests. #Initialize process-level clock/timezone state while the DEFAULT module paths are still in place. #The first script-level 'clock format' pulls in msgcat (and tzdata) from the runtime's own module #paths; the payload wipes those paths and nothing under the test module paths supplies msgcat. #The singleproc testinterp is shielded from this only because the runtests parent process uses #'clock format' under default paths before running test files (process-wide C-level caches) - a #fresh child process must warm up explicitly or e.g zipper.test fails with #"can't find package msgcat" out of clock format. clock format [clock seconds] lassign $argv payloadfile testfile childtmpdir if {$payloadfile eq "" || ![file exists $payloadfile]} { puts stderr "child_test_runner.tcl: payload file not found: '$payloadfile'" exit 98 } if {$testfile eq ""} { puts stderr "child_test_runner.tcl: no test file supplied" exit 98 } set testfile [file normalize $testfile] if {![file exists $testfile]} { puts stderr "child_test_runner.tcl: test file not found: '$testfile'" exit 98 } source $payloadfile if {![info exists ::runtests_child_tcltestoptions]} { puts stderr "child_test_runner.tcl: payload did not define ::runtests_child_tcltestoptions" exit 98 } set tcltestoptions $::runtests_child_tcltestoptions #-testdir is per test file (mirrors the per-file -testdir the runtests loop sets in singleproc mode) dict set tcltestoptions -testdir [file dirname $testfile] if {$childtmpdir ne ""} { dict set tcltestoptions -tmpdir $childtmpdir } #mirror the singleproc testinterp setup: ::argv holds the tcltest options and tcltest is not #package required until ::argv is in place (see the note at the bottom of runtests.tcl - #tcltest examines ::argv itself). The explicit tcltest::configure call below also disarms #tcltest's argv auto-processing traces, so the options cannot be double-applied. set ::argv0 $testfile set ::argv $tcltestoptions set ::argc [llength $tcltestoptions] package require tcltest tcltest::configure {*}$tcltestoptions #Lean children (2026-07-19 cleanup): the shellrun preload + no-op runx warmup that #previously mirrored the singleproc testinterp here were removed after the suites' #implicit punk::* dependencies were made explicit (17 files gained package require lines #per the existing src/tests/AGENTS.md contract; result parity re-verified with #scriptlib/developer/runtests_parity.tcl - the same tool that originally pinpointed the #implicit-dependency files, first as 17 files / 97 tests without shellrun, then 7 files / #31 tests without the runx-execution punk::lib pull). Children now load only tcltest plus #whatever each test file declares. The singleproc testinterp still preloads shellrun, but #as the RUNNER's capture mechanism (runx -tcl sourcing) - not a dependency any test may #rely on. #UDP watch-mode note (PUNK_TEST_UDPTEE): no interaction with this leanness today - the #parent relays child output at file granularity through its own log workers; children #inherit the env var but nothing here consumes it. If live per-child streaming is ever #added, make it CONDITIONAL: when env(PUNK_TEST_UDPTEE) is set, either reinstate shellrun #here and source via 'shellrun::runx -teelog {...} -tcl' (watched children opt into the #load cost, unwatched children stay lean - symmetric with the watch-gated worker-thread #cleanup in runtests.tcl), or forward lines over a bare tcludp socket without the shellrun #stack. source $testfile #natural end-of-script exit: code 0. An uncaught error from the test file propagates, prints #errorInfo to stderr and exits nonzero - runtests.tcl reports that as a file-level failure.