diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d40f71d..148af5dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to Entries are newest-first; one bullet per notable change. See the root `AGENTS.md` "Project Versioning" section for the bump policy. +## [0.12.33] - 2026-07-14 + +- punk::repl 0.5.2: fixed the codethread seeding template creating a stray namespace named after the ::punk::nav::ns::ns_current variable in every code interp (a phantom 'ns_current' child visible when browsing the nav namespace with n/). Subshell namespace carry-over semantics unchanged. Plus n/ n// n/// display-and-navigation characterization groundwork for the planned punk-tables/width-responsive rework: new punk/ns nslist.test (classification buckets as machine contract, per-element marking pins, REWORK-flagged layout pins, bare -types quirk), new punk/nav/ns navns.test (navigation state machine + ns_current contract), new punk/repl nscurrent.test (real-codethread inscope evaluation, retention, navigation, auto-create, seeding-fix guard). + ## [0.12.32] - 2026-07-14 - punk::lib 0.5.0: new punk::lib::check::has_tclbug_nestedswitch_tracelines (+ pure signature classifier) - live behavioural probe of the upstream nested-switch trace-line mis-attribution (tcl tktview 5d5b1052280c976ea3d4), surfaced in the 'help tcl' warning report (level medium) alongside the existing has_tclbug_* checks. No version gate: the probe matches the characterized wrong/right arm signature, so it flips automatically on a fixed Tcl runtime; a fixed-canary test in ns/cmdtrace.test then fails first, triggering the documented followup (flip GAP pins, update cmdtrace argdoc caveat, record fixed-in version). diff --git a/punkproject.toml b/punkproject.toml index 4d03dc4e..955ab681 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,4 +1,4 @@ [project] name = "punkshell" -version = "0.12.32" +version = "0.12.33" license = "BSD-2-Clause" diff --git a/src/modules/punk/repl-999999.0a1.0.tm b/src/modules/punk/repl-999999.0a1.0.tm index 1f706e77..faee0eda 100644 --- a/src/modules/punk/repl-999999.0a1.0.tm +++ b/src/modules/punk/repl-999999.0a1.0.tm @@ -582,7 +582,12 @@ proc repl::start {args} { interp eval code { namespace eval ::punk::repl::codethread {} set ::punk::repl::codethread::is_running 1 - namespace eval ::punk::nav::ns::ns_current {} + #2026-07-14 Agent-Updated: this previously namespace-eval'd the full + #ns_current VARIABLE name, creating a stray namespace of that name in + #every code interp (visible as a phantom child in n/ listings of the + #nav namespace). Only the parent namespace needs to exist for the + #variable set below. Guarded by tests repl/nscurrent.test. + namespace eval ::punk::nav::ns {} set ::punk::nav::ns::ns_current %ns1% } }] diff --git a/src/modules/punk/repl-buildversion.txt b/src/modules/punk/repl-buildversion.txt index b98fb9ab..967e1021 100644 --- a/src/modules/punk/repl-buildversion.txt +++ b/src/modules/punk/repl-buildversion.txt @@ -1,6 +1,7 @@ -0.5.1 +0.5.2 #First line must be a semantic version number #all other lines are ignored. +#0.5.2 - fixed the codethread seeding template in repl::start: it namespace-eval'd the full ::punk::nav::ns::ns_current VARIABLE name (creating a stray namespace of that name in every code interp - visible as a phantom 'ns_current' child when browsing the nav namespace with n/) instead of just ensuring the parent ::punk::nav::ns exists before setting the variable. Seeding semantics unchanged (the subshell namespace carry-over: a new codethread's code interp starts in the launching context's ns_current when it exists, else ::). Guarded by the new punk/repl testsuite repl/nscurrent.test (real codethread via repl::init driven with synchronous runscript sends: inscope evaluation of ns_current, retention across submissions, n/-navigation retained, auto-create-with-notice for a missing current namespace, stray-namespace absence, and a source-text guard on the template pending an end-to-end piped subshell test). #0.5.1 - G-076: repl::start's dead-console watchdog arming now also requires punk::lib::check::has_tclbug_console_deadspin to report the runtime affected (shared version gate with the 'help tcl' warning; requires punk::lib 0.4.2+). Behaviour today is unchanged (gate variable check::tclbug_console_deadspin_fixed_in is empty = every Tcl 9 windows runtime affected); once a released Tcl containing the verified upstream fix for f10d91c2d3 is recorded there, such runtimes stop arming the watchdog. #0.5.0 - G-039: new repl::console_watchdog - a self-rescheduling liveness poll (default 5s, repl::console_watchdog_ms) armed by repl::start for a tcl9 console input channel (-inputmode present) serving the process-default console on windows. The Tcl 9 windows console driver never delivers a dead console (killed conhost/terminal) to the script level as a fileevent (tclWinConsole.c ConsoleEventProc only notifies on buffered data) and its reader thread busy-loops on the persistent channel error, so an orphaned shell previously spun ~2 cores indefinitely. On a failed probe (chan configure -inputmode = live GetConsoleMode) the watchdog closes the input channel (stopping the driver reader thread) and finishes the repl via the normal eof done-path; app-punkshell's eof handling then finds no console reopenable and exits cleanly. repl::start's post-vwait reader-deregistration now tolerates an inchan closed by the watchdog. Piped/foreign-console/tcl8.6 inputs are unaffected (watchdog not armed). #0.4.0 - G-001: repl::init -console selects the console the repl reads/writes ({in out} pair, anchored opunk::console instance name, or ::opunk::Console object value, resolved via punk::console::console_spec_resolve). repl::start's inchan is now optional (defaults to the selected console's input). New repl-level channel state (conin/conout/conerr) is routed through rputs (stdout/stderr mapped per-repl) and doprompt; for a selected foreign console the code interp's stdout/stderr are diverted via shellfilter 'var' junction stacks and emitted to the console after each run. New helpers repl::console_is_default / console_at_eof / console_get_size - eof and size are answered by the selected console object's (possibly overridden) methods. Process-console behaviours (tcl_interactive prompt gating, stdin reopen on eof, raw-mode re-enable, utf-16be windows line re-decode experiment, mode-line on exit) now apply only to the default console. Default-console (stdin/stdout) behaviour unchanged. Also fixes rputs pseudo-channel mapping in the 3-arg -nonewline form (mapped value previously written over the -nonewline flag). diff --git a/src/tests/modules/AGENTS.md b/src/tests/modules/AGENTS.md index f64557f3..b224dafc 100644 --- a/src/tests/modules/AGENTS.md +++ b/src/tests/modules/AGENTS.md @@ -41,7 +41,9 @@ Unit tests for editable source modules under `src/modules/`, `src/modules_tcl8/` - `punkcheck/` — punkcheck module tests (install, summarize_install_resultdict, installtrack) - `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/ns/` — punk::ns tests (`testsuites/ns/`): cmdwhich/cmdinfo/cmd_traverse doc-lookup flow (`cmdflow.test`, G-040 parity), 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/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/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) - `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) diff --git a/src/tests/modules/punk/nav/ns/testsuites/nav/navns.test b/src/tests/modules/punk/nav/ns/testsuites/nav/navns.test new file mode 100644 index 00000000..8564a8b5 --- /dev/null +++ b/src/tests/modules/punk/nav/ns/testsuites/nav/navns.test @@ -0,0 +1,172 @@ +package require tcltest + +package require punk::ns ;#punk::nav::ns expects punk::ns machinery present +package require punk::ansi +package require punk::nav::ns +package require punk::console + +#added 2026-07-14 (agent) - punk::nav::ns::ns/ navigation semantics (the n/ n// n/// +#shell commands are aliases of this with v inferred). Display content is +#characterized in punk/ns testsuites/ns/nslist.test - this suite covers the +#NAVIGATION state machine and the repl coupling contract. +# +#Repl coupling (pinned below): the repl evaluates every submitted script +#tcl::namespace::inscope $::punk::nav::ns::ns_current (punk::repl::codethread +#runscript), and repl::start seeds a new codethread's code interp from the +#launching context's ns_current when it exists (the subshell namespace carry-over). +#Those integration behaviours are covered at the repl/shell level - here we pin +#the variable contract they all share: navigation means mutating +#::punk::nav::ns::ns_current and nothing else. +# +#Console queries are mocked (renderline.test pattern) - ns/ renders its listing +#via punk::ns::nslist (overtype/textblock pipeline). +namespace eval ::testspace { + namespace import ::tcltest::* + + proc mock_console {} { + if {[llength [info commands ::punk::console::get_tabstops]]} { + rename ::punk::console::get_tabstops ::testspace::__orig_get_tabstops + } + proc ::punk::console::get_tabstops {{inoutchannels {stdin stdout}}} { + set stops {} + for {set c 9} {$c <= 201} {incr c 8} {lappend stops $c} + return $stops + } + if {[llength [info commands ::punk::console::get_size]]} { + rename ::punk::console::get_size ::testspace::__orig_get_size + } + proc ::punk::console::get_size {args} { + return [dict create columns 80 rows 24] + } + } + proc restore_console {} { + catch {rename ::punk::console::get_tabstops {}} + if {[llength [info commands ::testspace::__orig_get_tabstops]]} { + rename ::testspace::__orig_get_tabstops ::punk::console::get_tabstops + } + catch {rename ::punk::console::get_size {}} + if {[llength [info commands ::testspace::__orig_get_size]]} { + rename ::testspace::__orig_get_size ::punk::console::get_size + } + } + variable navsetup { + set result "" + ::testspace::mock_console + #minimal fixture tree for navigation + namespace eval ::navtree { + proc here {} {return here} + namespace eval childa { + proc inchild {} {return inchild} + namespace eval deeper {} + } + namespace eval childb {} + namespace eval enschild { + namespace export dothing + proc dothing {} {return done} + namespace ensemble create + } + } + variable saved_ns_current $::punk::nav::ns::ns_current + } + variable navcleanup { + variable saved_ns_current + set ::punk::nav::ns::ns_current $saved_ns_current + catch {namespace delete ::navtree} + ::testspace::restore_console + } + + + test navns_ns_current_contract {navigation state lives in ::punk::nav::ns::ns_current (the variable the repl and subshell seeding consume)}\ + -setup $navsetup -body { + lappend result [info exists ::punk::nav::ns::ns_current] + set ::punk::nav::ns::ns_current :: + punk::nav::ns::ns/ / ::navtree + lappend result $::punk::nav::ns::ns_current + }\ + -cleanup $navcleanup\ + -result [list\ + 1 ::navtree + ] + + test navns_transitions {absolute and relative non-glob targets navigate; glob targets list without navigating}\ + -setup $navsetup -body { + set ::punk::nav::ns::ns_current :: + punk::nav::ns::ns/ / ::navtree + lappend result $::punk::nav::ns::ns_current + punk::nav::ns::ns/ / childa + lappend result $::punk::nav::ns::ns_current + punk::nav::ns::ns/ / deeper + lappend result $::punk::nav::ns::ns_current + #glob pattern - lists matches, does NOT change ns_current + set ::punk::nav::ns::ns_current ::navtree + punk::nav::ns::ns/ / child* + lappend result $::punk::nav::ns::ns_current + }\ + -cleanup $navcleanup\ + -result [list\ + ::navtree\ + ::navtree::childa\ + ::navtree::childa::deeper\ + ::navtree + ] + + test navns_failed_navigation_preserves_state {navigating to a nonexistent namespace errors and leaves ns_current unchanged}\ + -setup $navsetup -body { + set ::punk::nav::ns::ns_current ::navtree + lappend result [catch {punk::nav::ns::ns/ / ::navtree::nosuchns} msg] + lappend result $msg + lappend result $::punk::nav::ns::ns_current + lappend result [catch {punk::nav::ns::ns/ / alsonosuch}] + lappend result $::punk::nav::ns::ns_current + }\ + -cleanup $navcleanup\ + -result [list\ + 1\ + {cannot change to namespace ::navtree::nosuchns}\ + ::navtree\ + 1\ + ::navtree + ] + + test navns_quadcolon_normalization {:::: sequences in the target normalize to ::}\ + -setup $navsetup -body { + set ::punk::nav::ns::ns_current :: + punk::nav::ns::ns/ / ::navtree::::childa + lappend result $::punk::nav::ns::ns_current + }\ + -cleanup $navcleanup\ + -result [list\ + ::navtree::childa + ] + + test navns_v_forms {v selects listing content: / namespaces only, // adds commands; output ends with the queried namespace line}\ + -setup $navsetup -body { + set ::punk::nav::ns::ns_current ::navtree + set o1 [punk::ansi::ansistrip [punk::nav::ns::ns/ / ""]] + set o2 [punk::ansi::ansistrip [punk::nav::ns::ns/ // ""]] + #namespaces shown in both + lappend result [string match *childa* $o1] [string match *childa* $o2] + #commands only in // + lappend result [string match *here* $o1] [string match *here* $o2] + #trailing current-namespace display line + lappend result [string match "*\n::navtree" $o1] + }\ + -cleanup $navcleanup\ + -result [list\ + 1 1 0 1 1 + ] + + test navns_ensemble_annotation {navigating to a namespace that is also an ensemble command annotates the display}\ + -setup $navsetup -body { + set ::punk::nav::ns::ns_current :: + set out [punk::nav::ns::ns/ / ::navtree::enschild] + lappend result [string match {*(ensemble)*} $out] + set out2 [punk::nav::ns::ns/ / ::navtree::childa] + lappend result [string match {*(ensemble)*} $out2] + }\ + -cleanup $navcleanup\ + -result [list\ + 1 0 + ] +} +tcltest::cleanupTests ;#needed to produce test summary line. diff --git a/src/tests/modules/punk/ns/testsuites/ns/nslist.test b/src/tests/modules/punk/ns/testsuites/ns/nslist.test new file mode 100644 index 00000000..3c3dcf4c --- /dev/null +++ b/src/tests/modules/punk/ns/testsuites/ns/nslist.test @@ -0,0 +1,340 @@ +package require tcltest + +package require punk::ns +package require punk::console + +#added 2026-07-14 (agent) - characterization of the n/ n// n/// display machinery +#(punk::ns::get_ns_dicts classification + get_nslist/nslist rendering), requested as +#groundwork for a future rework to punk tables / terminal-width-responsive layout. +# +#Tier structure (deliberate - see the layout tests' REWORK notes): +# A: classification data (get_ns_dicts buckets) - the machine contract a table +# rework consumes; must survive any display rework untouched. +# B: marking semantics asserted PER ELEMENT and layout-agnostic (SGR fragments +# constructed live via punk::ansi::a+ / punk::ns::Cmark, located by substring - +# never whole-block pins) - also expected to survive a layout rework. +# C: current-layout pins explicitly marked REWORK - 2-column namespaces, +# 4-column commands, hardcoded 6-column namespace-path subtables - these flip +# deliberately when the punk-tables rework lands. +# +#Console queries are mocked (renderline.test pattern) - overtype/textblock sit in +#the render path and a width-responsive rework will consult get_size. +# +#Fixture findings pinned here (2026-07-14 probes): +# - packagetails = child ns whose fq name exactly matches a 'package names' entry; +# packageprefixes = a deeper package exists below the child; both => underdouble. +# - an alias overwritten by a proc of the same name classifies as a proc only (the +# alias registration is invisible to the aliases bucket), and an alias renamed +# within the namespace stays a plain als entry - so get_nslist's red-strike "-R" +# commandless-alias and yellow masked-alias display branches appear unreachable +# from current bucket derivation (rework/hygiene finding - not asserted). +# - usageinfo for an alias to a documented target only appears once the target +# namespace's punk::args definitions have been scanned (update_definitions). +namespace eval ::testspace { + namespace import ::tcltest::* + + proc mock_console {} { + if {[llength [info commands ::punk::console::get_tabstops]]} { + rename ::punk::console::get_tabstops ::testspace::__orig_get_tabstops + } + proc ::punk::console::get_tabstops {{inoutchannels {stdin stdout}}} { + set stops {} + for {set c 9} {$c <= 201} {incr c 8} {lappend stops $c} + return $stops + } + if {[llength [info commands ::punk::console::get_size]]} { + rename ::punk::console::get_size ::testspace::__orig_get_size + } + proc ::punk::console::get_size {args} { + return [dict create columns 80 rows 24] + } + } + proc restore_console {} { + catch {rename ::punk::console::get_tabstops {}} + if {[llength [info commands ::testspace::__orig_get_tabstops]]} { + rename ::testspace::__orig_get_tabstops ::punk::console::get_tabstops + } + catch {rename ::punk::console::get_size {}} + if {[llength [info commands ::testspace::__orig_get_size]]} { + rename ::testspace::__orig_get_size ::punk::console::get_size + } + } + + #fixture: one element of each class relevant to n/ marking, in a throwaway tree + proc make_navfix {} { + namespace eval ::navfix_src { + namespace export importme + proc importme {} {return imported} + } + namespace eval ::navfix { + namespace export expo* + proc plainproc {} {return plain} + proc exposed {} {return e} + proc documented {a} {return $a} + namespace eval plainchild {} + namespace eval tailpkg {} + namespace eval prefixer {} + namespace eval bothy {} + namespace eval enschild { + namespace export dothing + proc dothing {} {return done} + namespace ensemble create + } + namespace import ::navfix_src::importme + } + package provide navfix::tailpkg 1.0 + package provide navfix::prefixer::deeppkg 1.0 + package provide navfix::bothy 1.0 + package provide navfix::bothy::subpkg 1.0 + interp alias {} ::navfix::analias {} ::lindex + interp alias {} ::navfix::dalias {} ::punk::ns::corp + #alias renamed within the namespace (stays a plain als entry - header note) + interp alias {} ::navfix::wasalias {} ::lindex + rename ::navfix::wasalias ::navfix::nowalias + #alias overwritten by a same-name proc (classifies as proc only - header note) + interp alias {} ::navfix::maskme {} ::lindex + proc ::navfix::maskme {} {return masked} + oo::class create ::navfix::Klass + ::oo::object create ::navfix::oobj + coroutine ::navfix::coro apply {{} {yield ok; return done}} + punk::args::define { + @id -id ::navfix::documented + @values -min 1 -max 1 + a -type string + } + } + proc destroy_navfix {} { + catch {namespace delete ::navfix} + catch {namespace delete ::navfix_src} + foreach p {navfix::tailpkg navfix::prefixer::deeppkg navfix::bothy navfix::bothy::subpkg} { + catch {package forget $p} + } + catch {punk::args::undefine ::navfix::documented 1} + } + variable fsetup { + set result "" + ::testspace::mock_console + ::testspace::make_navfix + } + variable fcleanup { + ::testspace::destroy_navfix + ::testspace::restore_console + } + + + #Tier A ------------------------------------------------------------------ + test nslist_classification_buckets {get_ns_dicts classifies each element into its bucket (machine contract for display reworks)}\ + -setup $fsetup -body { + set d [lindex [punk::ns::get_ns_dicts ::navfix::*] 0] + lappend result [dict get $d location] + lappend result [dict get $d children] + lappend result [dict get $d packagetails] + lappend result [dict get $d packageprefixes] + lappend result [lsort [dict get $d commands]] + lappend result [dict get $d exported] + lappend result [dict get $d imported] + lappend result [lsort [dict get $d aliases]] + lappend result [lsort [dict get $d procs]] + lappend result [dict get $d ensembles] + lappend result [dict get $d ooclasses] + lappend result [dict get $d ooobjects] + lappend result [dict get $d coroutines] + lappend result [dict get $d itemcount] + }\ + -cleanup $fcleanup\ + -result [list\ + ::navfix\ + {bothy enschild plainchild prefixer tailpkg}\ + {bothy tailpkg}\ + {bothy prefixer}\ + {Klass analias coro dalias documented enschild exposed importme maskme nowalias oobj plainproc}\ + exposed\ + importme\ + {analias dalias nowalias}\ + {documented exposed importme maskme plainproc}\ + enschild\ + Klass\ + oobj\ + coro\ + 17 + ] + + test nslist_classification_alias_edge_cases {alias overwritten by same-name proc classifies as proc only; alias renamed in-namespace stays a plain als entry}\ + -setup $fsetup -body { + set d [lindex [punk::ns::get_ns_dicts ::navfix::*] 0] + #maskme: alias registration invisible once a proc took the name + lappend result [expr {"maskme" in [dict get $d procs]}] + lappend result [expr {"maskme" in [dict get $d aliases]}] + #nowalias: renamed alias remains a live command AND an aliases entry + lappend result [expr {"nowalias" in [dict get $d aliases]}] + lappend result [expr {"nowalias" in [dict get $d commands]}] + }\ + -cleanup $fcleanup\ + -result [list\ + 1 0 1 1 + ] + + test nslist_classification_usageinfo {usageinfo: directly documented command always present; alias to documented target appears once target ns definitions are scanned}\ + -setup $fsetup -body { + set d [lindex [punk::ns::get_ns_dicts ::navfix::*] 0] + lappend result [expr {"documented" in [dict get $d usageinfo]}] + #dalias -> ::punk::ns::corp (a documented command). Ensure the target + #namespace's definitions are registered, then dalias joins usageinfo. + punk::args::update_definitions [list ::punk::ns] + set d2 [lindex [punk::ns::get_ns_dicts ::navfix::*] 0] + lappend result [expr {"dalias" in [dict get $d2 usageinfo]}] + lappend result [expr {"plainproc" in [dict get $d2 usageinfo]}] + }\ + -cleanup $fcleanup\ + -result [list\ + 1 1 0 + ] + + test nslist_classification_native_builtin_ns {native bucket populates for a builtin namespace (fixture cannot contain native commands)}\ + -setup { + set result "" + }\ + -body { + set d [lindex [punk::ns::get_ns_dicts ::tcl::mathop::*] 0] + set native [dict get $d native] + lappend result [expr {"+" in $native && "-" in $native}] + }\ + -result [list\ + 1 + ] + + + #Tier B ------------------------------------------------------------------ + #Marking semantics located per element - layout agnostic. SGR fragments are + #constructed live from the same punk::ansi::a+ calls get_nslist uses. + proc rawhas {raw fragment} { + return [expr {[string first $fragment $raw] >= 0}] + } + + test nslist_marking_namespace_underlines {package association selects the child-namespace underline style}\ + -setup $fsetup -body { + set raw [punk::ns::nslist -types all ::navfix::*] + #package tail AND prefix -> double underline + lappend result [rawhas $raw "[punk::ansi::a+ underdouble]bothy"] + #package tail only -> single underline + lappend result [rawhas $raw "[punk::ansi::a+ underline]tailpkg"] + #package prefix only -> dotted underline + lappend result [rawhas $raw "[punk::ansi::a+ underdotted]prefixer"] + #plain child -> cyan, no underline style directly before the name + lappend result [rawhas $raw "[punk::ansi::a+ cyan]plainchild"] + lappend result [rawhas $raw "[punk::ansi::a+ underline]plainchild"] + }\ + -cleanup $fcleanup\ + -result [list\ + 1 1 1 1 0 + ] + + test nslist_marking_command_type_tags {each command type renders its tag with its designated colour}\ + -setup $fsetup -body { + set raw [punk::ns::nslist -types all ::navfix::*] + lappend result [rawhas $raw "[punk::ansi::a+ bold white]prc"] + lappend result [rawhas $raw "[punk::ansi::a+ bold yellow]ens"] + lappend result [rawhas $raw "[punk::ansi::a+ bold purple]als"] + lappend result [rawhas $raw "[punk::ansi::a+ term-aqua]ooc"] + lappend result [rawhas $raw "[punk::ansi::a+ bold cyan]ooo"] + lappend result [rawhas $raw "[punk::ansi::a+ term-hotpink]cor"] + }\ + -cleanup $fcleanup\ + -result [list\ + 1 1 1 1 1 1 + ] + + test nslist_marking_exported_imported {exported commands colour green-bold; imported commands carry the -I marker}\ + -setup $fsetup -body { + set raw [punk::ns::nslist -types all ::navfix::*] + #note: cells pass through overtype/ansiwrap merging which canonicalizes + #SGR parameter order to bold-first - fragments constructed accordingly + lappend result [rawhas $raw "[punk::ansi::a+ bold green]exposed"] + #imported marker overtyped onto the prefix: -I + lappend result [rawhas $raw "-[punk::ansi::a+ bold yellow]I"] + #plainproc is neither exported nor imported - plain white name + lappend result [rawhas $raw "[punk::ansi::a+ bold green]plainproc"] + }\ + -cleanup $fcleanup\ + -result [list\ + 1 1 0 + ] + + test nslist_marking_documented_icon {punk::args-documented commands get the usageinfo indicator icon; undocumented do not}\ + -setup $fsetup -body { + set raw [punk::ns::nslist -types all ::navfix::*] + set stripped [punk::ansi::ansistrip $raw] + set icon [format %c 0x24D8] + #brightgreen (92) SGR immediately preceding the icon char (params may be + #merged/reordered by the overtype pipeline - match any 92-bearing SGR) + lappend result [regexp [format {\x1b\[[0-9;]*92m%s} $icon] $raw] + #placement: appended after the documented command's cell + lappend result [string match "*documented $icon*" $stripped] + #undocumented commands carry no icon + lappend result [string match "*plainproc $icon*" $stripped] + }\ + -cleanup $fcleanup\ + -result [list\ + 1 1 0 + ] + + + #Tier C ------------------------------------------------------------------ + #REWORK: these pin the CURRENT hardcoded layout (2-column namespaces, 4-column + #commands, widest+8 padding, 6-column namespace-path subtables). The intended + #future rework to punk tables / terminal-width-responsive layout flips these + #deliberately - update them alongside that work, do not weaken them piecemeal. + test nslist_layout_current_columns_REWORK {current layout: namespaces 2 columns then commands 4 columns per row}\ + -setup $fsetup -body { + set stripped [punk::ansi::ansistrip [punk::ns::nslist -types all ::navfix::*]] + set lines [lsearch -all -inline -not [split $stripped \n] ""] + #3 rows: 5 children ceil-split 3+2 down 2 columns; 12 commands split 3+3+3+3 across 4 columns + lappend result [llength $lines] + #row composition asserted by ordered position (usageinfo icons can push + #into cell separators, so exact-spacing regexps would be icon-sensitive) + set row1 [lindex $lines 0] + lappend result [regexp {^bothy\s+prefixer\s} $row1] + set posns [lmap w {Klass dalias exposed nowalias} {string first $w $row1}] + lappend result [expr {-1 ni $posns && $posns eq [lsort -integer $posns]}] + set row3 [lindex $lines 2] + lappend result [regexp {^plainchild\s} $row3] + set posns3 [lmap w {coro enschild maskme plainproc} {string first $w $row3}] + lappend result [expr {-1 ni $posns3 && $posns3 eq [lsort -integer $posns3]}] + }\ + -cleanup $fcleanup\ + -result [list\ + 3 1 1 1 1 + ] + + test nslist_layout_nspath_subtable_REWORK {current layout: -nspathcommands renders bordered subtables for namespace path entries}\ + -setup $fsetup -body { + namespace eval ::navfix {namespace path ::navfix_src} + set stripped [punk::ansi::ansistrip [punk::ns::nslist -types all -nspathcommands 1 ::navfix::*]] + lappend result [string match "*Also resolving cmds in namespace paths:*" $stripped] + lappend result [string match "*::navfix_src*" $stripped] + lappend result [string match "*importme*" $stripped] + }\ + -cleanup { + catch {namespace eval ::navfix {namespace path {}}} + ::testspace::destroy_navfix + ::testspace::restore_console + }\ + -result [list\ + 1 1 1 + ] + + test nslist_types_default_error_QUIRK {KNOWN QUIRK: nslist without -types raises with a malformed message (literal dollar-known_types)}\ + -setup $fsetup -body { + #the shell pathway (punk::nav::ns::ns/) always passes -types, so this + #latent default is never hit interactively. Pinned for the hygiene pass: + #either give nslist a working -types default or a proper error message. + set err [catch {punk::ns::nslist ::navfix::*} msg] + lappend result $err + lappend result [string match {*Unrecognised namespace member type: $known_types*} $msg] + }\ + -cleanup $fcleanup\ + -result [list\ + 1 1 + ] +} +tcltest::cleanupTests ;#needed to produce test summary line. diff --git a/src/tests/modules/punk/repl/testsuites/repl/nscurrent.test b/src/tests/modules/punk/repl/testsuites/repl/nscurrent.test new file mode 100644 index 00000000..db81ed0e --- /dev/null +++ b/src/tests/modules/punk/repl/testsuites/repl/nscurrent.test @@ -0,0 +1,196 @@ +package require tcltest + +package require punk::ns +package require punk::nav::ns +package require punk::console + +#added 2026-07-14 (agent) - repl current-namespace retention and seeding (E2 tier of +#the n/ navigation coverage - see punk/nav/ns navns.test for the nav state machine +#and punk/ns nslist.test for display characterization). +# +#Contract proven here against a REAL codethread (repl::init -type punk, driven via +#synchronous thread::send of punk::repl::codethread::runscript - no interactive +#loop, no console): +# - every submitted script evaluates tcl::namespace::inscope +# $::punk::nav::ns::ns_current in the code interp (codethread runscript) +# - navigation via punk::nav::ns::ns/ inside the codethread is retained across +# subsequent submissions +# - a deleted current namespace is auto-created on next submission with the +# 'Created namespace:' stderr notice (codethread behaviour) +# - repl::start's seeding of a new codethread from the launching context's +# ns_current (the subshell namespace carry-over) uses a template we mirror here; +# the stray-namespace defect in that template (a namespace literally named +# ::punk::nav::ns::ns_current created in every code interp) was fixed 2026-07-14 +# - pinned via the mirrored template AND a source-text guard on the module, +# pending an end-to-end piped subshell test (E3) at the shell level. +# +#Thread hygiene: one codethread for the whole file, released in the final test's +#cleanup - synchronous thread::send only (the async/result-routing hazards recorded +#in the runtests thread-topology notes don't apply to this shape). +namespace eval ::testspace { + namespace import ::tcltest::* + + proc mock_console {} { + if {[llength [info commands ::punk::console::get_tabstops]]} { + rename ::punk::console::get_tabstops ::testspace::__orig_get_tabstops + } + proc ::punk::console::get_tabstops {{inoutchannels {stdin stdout}}} { + set stops {} + for {set c 9} {$c <= 201} {incr c 8} {lappend stops $c} + return $stops + } + if {[llength [info commands ::punk::console::get_size]]} { + rename ::punk::console::get_size ::testspace::__orig_get_size + } + proc ::punk::console::get_size {args} { + return [dict create columns 80 rows 24] + } + } + proc restore_console {} { + catch {rename ::punk::console::get_tabstops {}} + if {[llength [info commands ::testspace::__orig_get_tabstops]]} { + rename ::testspace::__orig_get_tabstops ::punk::console::get_tabstops + } + catch {rename ::punk::console::get_size {}} + if {[llength [info commands ::testspace::__orig_get_size]]} { + rename ::testspace::__orig_get_size ::punk::console::get_size + } + } + + #one shared codethread for the file (released in the last test's cleanup) + variable codethread "" + proc ensure_codethread {} { + variable codethread + if {$codethread ne ""} { + return $codethread + } + mock_console + package require punk::repl + repl::init -type punk + set codethread $::repl::codethread + restore_console + return $codethread + } + proc release_codethread {} { + variable codethread + if {$codethread ne ""} { + catch {thread::release $codethread} + set codethread "" + } + } + #run a script through the real runscript path; return stripped stdout capture + proc run_in_codethread {script} { + variable codethread + thread::send $codethread [list punk::repl::codethread::runscript $script] + set out [thread::send $codethread {interp eval code {set ::punk::repl::codethread::output_stdout}}] + thread::send $codethread {interp eval code {set ::punk::repl::codethread::output_stdout ""}} + return [string trim [punk::ansi::ansistrip $out]] + } + proc stderr_in_codethread {} { + variable codethread + set out [thread::send $codethread {interp eval code {set ::punk::repl::codethread::output_stderr}}] + thread::send $codethread {interp eval code {set ::punk::repl::codethread::output_stderr ""}} + return [string trim [punk::ansi::ansistrip $out]] + } + #the seeding block mirrors repl::start's %ns1% template (keep in sync - the + #source-text guard test below pins the module side) + proc seed_codethread {ns} { + variable codethread + thread::send $codethread [string map [list %ns1% $ns] { + interp eval code { + namespace eval ::punk::repl::codethread {} + set ::punk::repl::codethread::is_running 1 + namespace eval ::punk::nav::ns {} + set ::punk::nav::ns::ns_current %ns1% + } + }] + } + + testConstraint have_argv0 [info exists ::argv0] + + + test replns_seed_and_inscope {seeding ns_current makes runscript evaluate inscope that namespace (no stray namespace created)}\ + -constraints have_argv0\ + -setup { + set result "" + ::testspace::ensure_codethread + }\ + -body { + ::testspace::run_in_codethread {namespace eval ::carrytree {namespace eval sub {}}} + ::testspace::seed_codethread ::carrytree + lappend result [::testspace::run_in_codethread {puts [namespace current]}] + #retention across submissions + lappend result [::testspace::run_in_codethread {puts [namespace current]}] + #the 2026-07-14 fix: seeding must NOT create a namespace named like the variable + variable codethread + lappend result [thread::send $codethread {interp eval code {namespace exists ::punk::nav::ns::ns_current}}] + }\ + -result [list\ + ::carrytree\ + ::carrytree\ + 0 + ] + + test replns_navigation_retained {punk::nav::ns::ns/ inside the codethread changes the namespace for subsequent submissions}\ + -constraints have_argv0\ + -setup { + set result "" + ::testspace::ensure_codethread + }\ + -body { + ::testspace::seed_codethread ::carrytree + ::testspace::run_in_codethread {package require punk::ns; package require punk::nav::ns} + ::testspace::run_in_codethread {punk::nav::ns::ns/ / ::carrytree::sub} + lappend result [::testspace::run_in_codethread {puts [namespace current]}] + #relative navigation from within + ::testspace::run_in_codethread {namespace eval ::carrytree::sub::deeper {}} + ::testspace::run_in_codethread {punk::nav::ns::ns/ / deeper} + lappend result [::testspace::run_in_codethread {puts [namespace current]}] + }\ + -result [list\ + ::carrytree::sub\ + ::carrytree::sub::deeper + ] + + test replns_autocreate_on_missing {a deleted current namespace is auto-created on the next submission with a stderr notice}\ + -constraints have_argv0\ + -setup { + set result "" + ::testspace::ensure_codethread + }\ + -body { + ::testspace::seed_codethread ::carrytree::ghost + ::testspace::stderr_in_codethread ;#clear + lappend result [::testspace::run_in_codethread {puts [namespace current]}] + lappend result [string match "*Created namespace: ::carrytree::ghost*" [::testspace::stderr_in_codethread]] + }\ + -result [list\ + ::carrytree::ghost\ + 1 + ] + + test replns_start_seed_template_source_guard {repl::start's codethread seeding template targets ::punk::nav::ns (stray-namespace fix guard) - releases the shared codethread}\ + -constraints have_argv0\ + -setup { + set result "" + }\ + -body { + #source-text guard: the seeding template is inline in repl::start (only + #exercised by a live stdin repl) - pin the fixed form until an end-to-end + #piped subshell test (E3) covers it behaviourally. + set ifneeded [package ifneeded punk::repl [package provide punk::repl]] + set srcfile [lindex $ifneeded end] + set fd [open $srcfile r] + set moduletext [read $fd] + close $fd + lappend result [string match {*namespace eval ::punk::nav::ns \{\}*} $moduletext] + lappend result [string match {*namespace eval ::punk::nav::ns::ns_current*} $moduletext] + }\ + -cleanup { + ::testspace::release_codethread + }\ + -result [list\ + 1 0 + ] +} +tcltest::cleanupTests ;#needed to produce test summary.