From 03306be068d68531a61184b1b9b808fb527dd102 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Tue, 28 Jul 2026 02:23:03 +1000 Subject: [PATCH] argparsingtest 1.3.0: opt, cmdline and tepam are optional, not module-level requires The module hard-required opt, cmdline and tepam at load time, so on a punkshell build without tcllib 'package require argparsingtest' died outright with "can't find package opt". A harness whose whole job is comparing parser libraries is useless precisely on the runtime you wanted to measure, and the module already had the right pattern for this: argp, parse_args and argparse degrade to a not_loaded row rather than taking the module down with them. The three tcllib parsers were the odd ones out. Reproduced before the change with punk module paths only and an emptied auto_path, which is where tcllib's pkgIndex-based packages come from. Every parser this module compares is now optional. Their roster entries become {optional opt}, {optional cmdline} and {optional tepam}, so discover_parsers reports not_loaded when the package is absent and nothing in the harness calls them. punk::args and Tcl 8.6- remain hard requires - the harness itself is built on those. The guard differs by package because the dependency does: cmdline is only ever called INSIDE proc bodies, so the catch-guarded require at the top is the whole story - the wrapper procs stay defined and are simply never invoked. opt and tepam are used at DEFINITION time: tcl::OptProc and tepam::procedure are the commands that create the wrapper procs. Their seven definition sites (four opt, across opts/tkstyle/tclstyle/sandwich; three tepam) are each additionally guarded, so with the package absent the proc does not exist at all. Those sites test 'package present' rather than wrapping the definition in a bare catch. Both tolerate absence, but a presence test still lets a genuine error in a spec raise loudly on a runtime that does have the package, where a catch would swallow it. The pre-existing argp registration site keeps its original catch idiom - working code, not part of this change. Pinned by module_loads_without_optional_parser_packages: a child interp with an emptied auto_path that asserts the module loads, that each absent package's parser reports not_loaded, and that the opt and tepam wrapper procs genuinely do not exist. Where a runtime supplies one of the three as a .tm on a module path the test degrades to asserting the module still loads, which is the main claim anyway. Tests that name parsers explicitly (first_call_library_backed, first_call_positional_styles, sandwich_strictness) now filter through a loaded_parsers helper, and the tclstyle cmdline_typed status assertion derives its expectation from package presence: those parsers can now legitimately be missing. Verification: 26/26 pass under the 32-bit luck-test2 runtime with no skips, 24 pass + 2 skip under native Tcl 9.0.3. With opt and tepam hidden, the built 1.3.0 module loads, reports both as not_loaded, and compare lists them in its "not loaded:" footer instead of the whole module failing to load. Claude-Session: https://claude.ai/code/session_01Q82VuBXzgeKsAwMYHrbpP1 Assisted-by: harness=claude; primary-model=claude-opus-5[1m]; api-location=anthropic.com --- src/modules/argparsingtest-999999.0a1.0.tm | 334 ++++++++++-------- src/modules/argparsingtest-buildversion.txt | 5 +- .../argparsingtest/argparsingtest.test | 88 ++++- 3 files changed, 272 insertions(+), 155 deletions(-) diff --git a/src/modules/argparsingtest-999999.0a1.0.tm b/src/modules/argparsingtest-999999.0a1.0.tm index 767ae26e..ef9d7a57 100644 --- a/src/modules/argparsingtest-999999.0a1.0.tm +++ b/src/modules/argparsingtest-999999.0a1.0.tm @@ -50,15 +50,28 @@ package require Tcl 8.6- package require punk::args -package require opt -package require cmdline -package require tepam + +# Every parser package this module compares is OPTIONAL, including the tcllib ones. +# A comparison harness that cannot load at all because one of the libraries it +# compares is absent is useless on exactly the runtime you most want to measure - +# and punkshell builds do ship without tcllib. A missing library must degrade to a +# not_loaded row (see the {optional } roster markers), never to a load failure. +# Guarded here rather than at the call sites because the guard needed differs: +# cmdline - used only INSIDE proc bodies, so the requires below are the whole story +# opt - tcl::OptProc DEFINES the wrapper procs, so each definition site is +# tepam additionally guarded by an 'is the package present' test. Those sites +# test presence rather than catching, so a genuine error in a spec still +# surfaces loudly on a runtime that does have the package. +# argp, parse_args and argparse are required further down, at their own sites. +catch {package require opt} +catch {package require cmdline} +catch {package require tepam} #*** !doctools #[item] [package {Tcl 8.6}] #[item] [package {punk::args}] -#[item] [package {opt}] -#[item] [package {cmdline}] -#[item] [package {tepam}] +#[item] [package {opt}] (optional - parser under test) +#[item] [package {cmdline}] (optional - parser under test) +#[item] [package {tepam}] (optional - parser under test) # #package require frobz # #*** !doctools @@ -163,13 +176,13 @@ namespace eval argparsingtest { punkargs_by_id always punkargs_parsecache always punkargs_validate_ansistripped always - opt always - cmdline_untyped always - cmdline_typed always + opt {optional opt} + cmdline_untyped {optional cmdline} + cmdline_typed {optional cmdline} argp {optional argp} parse_args {optional parse_args} argparse {optional argparse} - tepam always + tepam {optional tepam} } } tkstyle { @@ -180,8 +193,8 @@ namespace eval argparsingtest { punkargs always punkargs_by_id always punkargs_parsecache always - opt always - tepam always + opt {optional opt} + tepam {optional tepam} argparse {optional argparse} cmdline_untyped unsupported cmdline_typed unsupported @@ -197,10 +210,10 @@ namespace eval argparsingtest { punkargs always punkargs_by_id always punkargs_parsecache always - opt always - cmdline_untyped always - cmdline_typed always - tepam always + opt {optional opt} + cmdline_untyped {optional cmdline} + cmdline_typed {optional cmdline} + tepam {optional tepam} parse_args {optional parse_args} argparse {optional argparse} argp unsupported @@ -214,7 +227,7 @@ namespace eval argparsingtest { punkargs always punkargs_by_id always punkargs_parsecache always - opt always + opt {optional opt} argparse {optional argparse} cmdline_untyped unsupported cmdline_typed unsupported @@ -1494,24 +1507,27 @@ namespace eval argparsingtest::opts { -join -type none -multiple 1 }] } - tcl::OptProc opt { - {-return string "return type"} - {-frametype \uFFEF "type of frame"} - {-show_edge \uFFEF "show table outer borders"} - {-show_seps \uFFEF "show separators"} - {-join "solo option"} - {-x "" "x val"} - {-y b "y val"} - {-z c "z val"} - {-1 1 "1val"} - {-2 -int 2 "2val"} - {-3 -int 3 "3val"} - } { - set opts [dict create] - foreach v [info locals] { - dict set opts $v [set $v] + #tcl::OptProc DEFINES this proc, so skip the definition when opt is absent - see the optional-package requires at the module top + if {![catch {package present opt}]} { + tcl::OptProc opt { + {-return string "return type"} + {-frametype \uFFEF "type of frame"} + {-show_edge \uFFEF "show table outer borders"} + {-show_seps \uFFEF "show separators"} + {-join "solo option"} + {-x "" "x val"} + {-y b "y val"} + {-z c "z val"} + {-1 1 "1val"} + {-2 -int 2 "2val"} + {-3 -int 3 "3val"} + } { + set opts [dict create] + foreach v [info locals] { + dict set opts $v [set $v] + } + return $opts } - return $opts } #cmdline::getoptions is much faster than typedGetoptions @@ -1736,22 +1752,25 @@ namespace eval argparsingtest::opts { -join -type none -multiple 1 }] } - tepam::procedure {tepam} { - -args { - {-return -type string -default string} - {-frametype -type string -default \uFFEF} - {-show_edge -type string -default \uFFEF} - {-show_seps -type string -default \uFFEF} - {-join -type none -multiple} - {-x -type string -default ""} - {-y -type string -default b} - {-z -type string -default c} - {-1 -type boolean -default 1} - {-2 -type integer -default 2} - {-3 -type integer -default 3} - } - } { - return [dict create return $return frametype $frametype show_edge $show_edge show_seps $show_seps x $x y $y z $z 1 $1 2 $2 3 $3 join $join] + #tepam::procedure DEFINES this proc, so skip the definition when tepam is absent - see the optional-package requires at the module top + if {![catch {package present tepam}]} { + tepam::procedure {tepam} { + -args { + {-return -type string -default string} + {-frametype -type string -default \uFFEF} + {-show_edge -type string -default \uFFEF} + {-show_seps -type string -default \uFFEF} + {-join -type none -multiple} + {-x -type string -default ""} + {-y -type string -default b} + {-z -type string -default c} + {-1 -type boolean -default 1} + {-2 -type integer -default 2} + {-3 -type integer -default 3} + } + } { + return [dict create return $return frametype $frametype show_edge $show_edge show_seps $show_seps x $x y $y z $z 1 $1 2 $2 3 $3 join $join] + } } #*** !doctools @@ -1967,28 +1986,31 @@ namespace eval argparsingtest::tkstyle { -join -type none -multiple 1 }] } - tcl::OptProc opt { - {p1 -string "first positional argument"} - {p2 -string "second positional argument"} - {-return string "return type"} - {-frametype \uFFEF "type of frame"} - {-show_edge \uFFEF "show table outer borders"} - {-show_seps \uFFEF "show separators"} - {-join "solo option"} - {-x "" "x val"} - {-y b "y val"} - {-z c "z val"} - {-1 1 "1val"} - {-2 -int 2 "2val"} - {-3 -int 3 "3val"} - } { - set opts [dict create] - foreach v [info locals] { - if {$v ni {p1 p2 opts args Args}} { - dict set opts $v [set $v] + #tcl::OptProc DEFINES this proc, so skip the definition when opt is absent - see the optional-package requires at the module top + if {![catch {package present opt}]} { + tcl::OptProc opt { + {p1 -string "first positional argument"} + {p2 -string "second positional argument"} + {-return string "return type"} + {-frametype \uFFEF "type of frame"} + {-show_edge \uFFEF "show table outer borders"} + {-show_seps \uFFEF "show separators"} + {-join "solo option"} + {-x "" "x val"} + {-y b "y val"} + {-z c "z val"} + {-1 1 "1val"} + {-2 -int 2 "2val"} + {-3 -int 3 "3val"} + } { + set opts [dict create] + foreach v [info locals] { + if {$v ni {p1 p2 opts args Args}} { + dict set opts $v [set $v] + } } + return [list $p1 $p2 $opts] } - return [list $p1 $p2 $opts] } namespace eval argdoc { @@ -2015,25 +2037,28 @@ namespace eval argparsingtest::tkstyle { -join -type none -multiple 1 }] } - tepam::procedure {tepam} { - -named_arguments_first 0 - -args { - {p1 -type string} - {p2 -type string} - {-return -type string -default string} - {-frametype -type string -default \uFFEF} - {-show_edge -type string -default \uFFEF} - {-show_seps -type string -default \uFFEF} - {-join -type none -multiple} - {-x -type string -default ""} - {-y -type string -default b} - {-z -type string -default c} - {-1 -type boolean -default 1} - {-2 -type integer -default 2} - {-3 -type integer -default 3} - } - } { - return [list $p1 $p2 [dict create return $return frametype $frametype show_edge $show_edge show_seps $show_seps x $x y $y z $z 1 $1 2 $2 3 $3 join $join]] + #tepam::procedure DEFINES this proc, so skip the definition when tepam is absent - see the optional-package requires at the module top + if {![catch {package present tepam}]} { + tepam::procedure {tepam} { + -named_arguments_first 0 + -args { + {p1 -type string} + {p2 -type string} + {-return -type string -default string} + {-frametype -type string -default \uFFEF} + {-show_edge -type string -default \uFFEF} + {-show_seps -type string -default \uFFEF} + {-join -type none -multiple} + {-x -type string -default ""} + {-y -type string -default b} + {-z -type string -default c} + {-1 -type boolean -default 1} + {-2 -type integer -default 2} + {-3 -type integer -default 3} + } + } { + return [list $p1 $p2 [dict create return $return frametype $frametype show_edge $show_edge show_seps $show_seps x $x y $y z $z 1 $1 2 $2 3 $3 join $join]] + } } namespace eval argdoc { @@ -2296,28 +2321,31 @@ namespace eval argparsingtest::tclstyle { p2 -type string -optional 0 }] } - tcl::OptProc opt { - {-return string "return type"} - {-frametype \uFFEF "type of frame"} - {-show_edge \uFFEF "show table outer borders"} - {-show_seps \uFFEF "show separators"} - {-join "solo option"} - {-x "" "x val"} - {-y b "y val"} - {-z c "z val"} - {-1 1 "1val"} - {-2 -int 2 "2val"} - {-3 -int 3 "3val"} - {p1 -string "first positional argument"} - {p2 -string "second positional argument"} - } { - set opts [dict create] - foreach v [info locals] { - if {$v ni {p1 p2 opts args Args}} { - dict set opts $v [set $v] + #tcl::OptProc DEFINES this proc, so skip the definition when opt is absent - see the optional-package requires at the module top + if {![catch {package present opt}]} { + tcl::OptProc opt { + {-return string "return type"} + {-frametype \uFFEF "type of frame"} + {-show_edge \uFFEF "show table outer borders"} + {-show_seps \uFFEF "show separators"} + {-join "solo option"} + {-x "" "x val"} + {-y b "y val"} + {-z c "z val"} + {-1 1 "1val"} + {-2 -int 2 "2val"} + {-3 -int 3 "3val"} + {p1 -string "first positional argument"} + {p2 -string "second positional argument"} + } { + set opts [dict create] + foreach v [info locals] { + if {$v ni {p1 p2 opts args Args}} { + dict set opts $v [set $v] + } } + return [list $p1 $p2 $opts] } - return [list $p1 $p2 $opts] } namespace eval argdoc { @@ -2439,24 +2467,27 @@ namespace eval argparsingtest::tclstyle { p2 -type string -optional 0 }] } - tepam::procedure {tepam} { - -args { - {-return -type string -default string} - {-frametype -type string -default \uFFEF} - {-show_edge -type string -default \uFFEF} - {-show_seps -type string -default \uFFEF} - {-join -type none -multiple} - {-x -type string -default ""} - {-y -type string -default b} - {-z -type string -default c} - {-1 -type boolean -default 1} - {-2 -type integer -default 2} - {-3 -type integer -default 3} - {p1 -type string} - {p2 -type string} - } - } { - return [list $p1 $p2 [dict create return $return frametype $frametype show_edge $show_edge show_seps $show_seps x $x y $y z $z 1 $1 2 $2 3 $3 join $join]] + #tepam::procedure DEFINES this proc, so skip the definition when tepam is absent - see the optional-package requires at the module top + if {![catch {package present tepam}]} { + tepam::procedure {tepam} { + -args { + {-return -type string -default string} + {-frametype -type string -default \uFFEF} + {-show_edge -type string -default \uFFEF} + {-show_seps -type string -default \uFFEF} + {-join -type none -multiple} + {-x -type string -default ""} + {-y -type string -default b} + {-z -type string -default c} + {-1 -type boolean -default 1} + {-2 -type integer -default 2} + {-3 -type integer -default 3} + {p1 -type string} + {p2 -type string} + } + } { + return [list $p1 $p2 [dict create return $return frametype $frametype show_edge $show_edge show_seps $show_seps x $x y $y z $z 1 $1 2 $2 3 $3 join $join]] + } } namespace eval argdoc { @@ -2848,31 +2879,34 @@ namespace eval argparsingtest::sandwich { p5 -type string -optional 0 }] } - tcl::OptProc opt { - {p1 -string "first leading positional argument"} - {p2 -string "second leading positional argument"} - {-return string "return type"} - {-frametype \uFFEF "type of frame"} - {-show_edge \uFFEF "show table outer borders"} - {-show_seps \uFFEF "show separators"} - {-join "solo option"} - {-x "" "x val"} - {-y b "y val"} - {-z c "z val"} - {-1 1 "1val"} - {-2 -int 2 "2val"} - {-3 -int 3 "3val"} - {p3 -string "first trailing positional argument"} - {p4 -string "second trailing positional argument"} - {p5 -string "third trailing positional argument"} - } { - set opts [dict create] - foreach v [info locals] { - if {$v ni {p1 p2 p3 p4 p5 opts args Args}} { - dict set opts $v [set $v] + #tcl::OptProc DEFINES this proc, so skip the definition when opt is absent - see the optional-package requires at the module top + if {![catch {package present opt}]} { + tcl::OptProc opt { + {p1 -string "first leading positional argument"} + {p2 -string "second leading positional argument"} + {-return string "return type"} + {-frametype \uFFEF "type of frame"} + {-show_edge \uFFEF "show table outer borders"} + {-show_seps \uFFEF "show separators"} + {-join "solo option"} + {-x "" "x val"} + {-y b "y val"} + {-z c "z val"} + {-1 1 "1val"} + {-2 -int 2 "2val"} + {-3 -int 3 "3val"} + {p3 -string "first trailing positional argument"} + {p4 -string "second trailing positional argument"} + {p5 -string "third trailing positional argument"} + } { + set opts [dict create] + foreach v [info locals] { + if {$v ni {p1 p2 p3 p4 p5 opts args Args}} { + dict set opts $v [set $v] + } } + return [list [list $p1 $p2] [list $p3 $p4 $p5] $opts] } - return [list [list $p1 $p2] [list $p3 $p4 $p5] $opts] } namespace eval argdoc { diff --git a/src/modules/argparsingtest-buildversion.txt b/src/modules/argparsingtest-buildversion.txt index f1daf288..70032245 100644 --- a/src/modules/argparsingtest-buildversion.txt +++ b/src/modules/argparsingtest-buildversion.txt @@ -1,6 +1,9 @@ -1.2.0 +1.3.0 #First line must be a semantic version number #all other lines are ignored. +#1.3.0 - opt, cmdline and tepam are no longer module-level hard requires. A punkshell build without tcllib could not load the module at all ("can't find package opt"), which is useless on exactly the runtime you wanted to measure: every parser this module compares is now optional and a missing one degrades to a not_loaded row like argp/parse_args/argparse already did. Their roster entries become {optional opt} / {optional cmdline} / {optional tepam} +#1.3.0 - the guard differs by package because the dependency does: cmdline is only called inside proc bodies, so the catch-guarded require is the whole story, whereas tcl::OptProc and tepam::procedure DEFINE their wrapper procs - those 7 definition sites are additionally guarded by a 'package present' test, so the proc simply does not exist when the package is absent (and discover_parsers then never calls it). Presence test rather than catch, so a genuine spec error still raises loudly on a runtime that does have the package +#1.3.0 - regression pinned by module_loads_without_optional_parser_packages (child interp with an emptied auto_path); tests that name parsers explicitly now filter through a loaded_parsers helper #1.2.0 - fourth calling style 'sandwich': 2 mandatory leading positionals, THEN the 10 keyed options (argvec now exercises the valueless -join flag alongside -flag value pairs), THEN 3 mandatory trailing positionals. The 2/3 split is asymmetric on purpose - an even split could be satisfied by a parser that just halves the positional block. New namespace argparsingtest::sandwich with manual_switch, punkargs, punkargs_by_id, punkargs_parsecache, opt and argparse wrappers; return contract is {{p1 p2} {p3 p4 p5} optsdict} (grouped rather than the flat {p1 p2 opts} of the other positional styles, since positionals sit on both sides of the options) #1.2.0 - sandwich style: cmdline, tepam, argp and parse_args are 'unsupported' (N/A rows) - each verified to fail on the style's argvec. cmdline is the notable one: cmdline::getoptions returns WITHOUT error having parsed nothing, leaving every option at its default and the argument list untouched #1.2.0 - sandwich style: punk::args, opt and the manual baseline model the arrangement and reject options ahead of the leaders; argparse reaches the style only via -mixed, which permits switches anywhere and therefore parses the argvec correctly WITHOUT enforcing the arrangement (pinned by the sandwich_strictness / sandwich_argparse_is_permissive tests) diff --git a/src/tests/modules/argparsingtest/testsuites/argparsingtest/argparsingtest.test b/src/tests/modules/argparsingtest/testsuites/argparsingtest/argparsingtest.test index aec6d03f..3847b897 100644 --- a/src/tests/modules/argparsingtest/testsuites/argparsingtest/argparsingtest.test +++ b/src/tests/modules/argparsingtest/testsuites/argparsingtest/argparsingtest.test @@ -15,6 +15,20 @@ namespace eval ::testspace { testConstraint parse_args_loaded [expr {![catch {package present parse_args}]}] testConstraint argparse_loaded [expr {![catch {package present argparse}]}] + #added 2026-07-28 (agent) - opt, cmdline and tepam are optional too (module 1.3.0 stopped + #hard-requiring them: a comparison harness must still load on a build without tcllib), so any + #test naming parsers explicitly has to drop the ones this runtime cannot load + proc loaded_parsers {style names} { + set disco [dict get [argparsingtest::discover_parsers -style $style] $style] + set out {} + foreach n $names { + if {[dict exists $disco $n] && [dict get $disco $n] eq "loaded"} { + lappend out $n + } + } + return $out + } + #added 2026-07-18 (agent) - suite rewritten for the argparsingtest 1.0.0 restructure: #parser procs live in per-style namespaces (opts/tkstyle/tclstyle), harness results are keyed by style #Split 2026-07-19 (G-092 - parallel-floor reduction, tests moved verbatim): this file holds @@ -91,7 +105,9 @@ namespace eval ::testspace { if {$ok eq 1 && [dict get $r opts manual_switch] ne "loaded"} { set ok "opts manual_switch status" } if {$ok eq 1 && [dict get $r tkstyle cmdline_typed] ne "unsupported"} { set ok "tkstyle cmdline_typed status" } if {$ok eq 1 && [dict get $r tkstyle argp] ne "unsupported"} { set ok "tkstyle argp status" } - if {$ok eq 1 && [dict get $r tclstyle cmdline_typed] ne "loaded"} { set ok "tclstyle cmdline_typed status" } + #tclstyle CAN express cmdline, so there the status tracks whether the package is installed + set expect_cmdline [expr {[catch {package present cmdline}] ? "not_loaded" : "loaded"}] + if {$ok eq 1 && [dict get $r tclstyle cmdline_typed] ne $expect_cmdline} { set ok "tclstyle cmdline_typed status" } if {$ok eq 1 && [dict get $r tclstyle argp] ne "unsupported"} { set ok "tclstyle argp status" } if {$ok eq 1 && [dict exists $r opts test1_switch]} { set ok "stale test1_switch key present" } set ok @@ -105,6 +121,10 @@ namespace eval ::testspace { foreach {pkg pname styles} { parse_args parse_args {opts tclstyle} argparse argparse {opts tkstyle tclstyle sandwich} + opt opt {opts tkstyle tclstyle sandwich} + cmdline cmdline_untyped {opts tclstyle} + cmdline cmdline_typed {opts tclstyle} + tepam tepam {opts tkstyle tclstyle} } { set expected [expr {[catch {package present $pkg}] ? "not_loaded" : "loaded"}] foreach s $styles { @@ -128,6 +148,66 @@ namespace eval ::testspace { set ok } -result 1 + #added 2026-07-28 (agent) - regression pin for the 1.3.0 change: the module used to hard-require + #opt, cmdline and tepam, so 'package require argparsingtest' died outright with "can't find + #package opt" on a punkshell build without tcllib - a comparison harness unloadable on exactly + #the runtime you wanted to measure. Exercised in a child interp with an emptied auto_path, which + #is where tcllib's pkgIndex-based packages come from. If a runtime provides any of the three as a + #.tm on a module path this degrades to asserting the module still loads, which is the main claim. + test module_loads_without_optional_parser_packages {Test that the module loads when opt, cmdline and tepam are unavailable} -body { + set tm_paths [tcl::tm::list] + #pin to the parent's versions - a plain require prefers a stable copy over the 999999.0a1.0 source module + set pinned "" + foreach pkg {punk::args punk::lib argparsingtest} { + if {![catch {package present $pkg} ver]} { + append pinned "package require -exact [list $pkg] [list $ver]\n" + } + } + set child [interp create] + try { + set child_tm [interp eval $child {tcl::tm::list}] + if {[llength $child_tm]} { + interp eval $child [list tcl::tm::path remove {*}$child_tm] + } + if {[llength $tm_paths]} { + interp eval $child [list tcl::tm::path add {*}$tm_paths] + } + interp eval $child [list set ::auto_path [list]] + set result [interp eval $child [string cat $pinned { + set absent {} + foreach p {opt cmdline tepam} { + if {[catch {package present $p}]} { lappend absent $p } + } + set disco [dict get [argparsingtest::discover_parsers -style opts] opts] + set statuses {} + set stillcommands {} + foreach {pname pkg} {opt opt cmdline_untyped cmdline cmdline_typed cmdline tepam tepam} { + if {$pkg in $absent} { + lappend statuses $pname [dict get $disco $pname] + } + } + #opt and tepam DEFINE their wrapper procs, so those must not exist when absent + foreach {pname pkg} {opt opt tepam tepam} { + if {$pkg in $absent && [llength [info commands ::argparsingtest::opts::$pname]]} { + lappend stillcommands $pname + } + } + list loaded [package present argparsingtest] absent $absent statuses $statuses stillcommands $stillcommands + }]] + } finally { + interp delete $child + } + set ok 1 + if {[dict get $result loaded] eq ""} { set ok "child did not load argparsingtest" } + if {$ok eq 1 && [llength [dict get $result stillcommands]] } { set ok "wrapper procs defined without their package: [dict get $result stillcommands]" } + if {$ok eq 1} { + foreach {pname status} [dict get $result statuses] { + if {$status ne "not_loaded"} { set ok "$pname reported $status with its package absent" ; break } + } + } + set ok + } -result 1 + #added 2026-07-26 (agent) - opts-style external wrappers agree with the hand-rolled baseline's option values test parse_args_opts_values {Test that the parse_args opts wrapper returns the declared option values} -constraints parse_args_loaded -body { set r [argparsingtest::opts::parse_args -return object -x hello -y world -1 1 -2 2 -3 3] @@ -204,7 +284,7 @@ namespace eval ::testspace { test sandwich_strictness {Test that the modelling sandwich parsers reject options before the leaders} -body { set badvec {-x hello -join alpha beta gamma delta epsilon} set ok 1 - foreach pname {manual_switch punkargs punkargs_by_id punkargs_parsecache opt} { + foreach pname [loaded_parsers sandwich {manual_switch punkargs punkargs_by_id punkargs_parsecache opt}] { if {![catch {argparsingtest::sandwich::$pname {*}$badvec} r]} { set ok "$pname accepted options-before-leaders: $r" break @@ -237,7 +317,7 @@ namespace eval ::testspace { #added 2026-07-17 (agent) test first_call_library_backed {Test that first_call returns cold and warm float timings for library-backed parsers (warm-up scripts must run cleanly)} -body { - set r [argparsingtest::first_call -style opts -parsers {punkargs punkargs_by_id opt cmdline_untyped cmdline_typed tepam}] + set r [argparsingtest::first_call -style opts -parsers [loaded_parsers opts {punkargs punkargs_by_id opt cmdline_untyped cmdline_typed tepam}]] set ok 1 foreach {pname sub} [dict get $r opts] { foreach mode {cold warm} { @@ -254,7 +334,7 @@ namespace eval ::testspace { test first_call_positional_styles {Test first_call cold and warm for tkstyle, tclstyle and sandwich parsers} -body { set ok 1 foreach {s plist} {tkstyle {punkargs_by_id tepam} tclstyle {cmdline_untyped} sandwich {manual_switch punkargs_by_id opt}} { - set r [argparsingtest::first_call -style $s -parsers $plist] + set r [argparsingtest::first_call -style $s -parsers [loaded_parsers $s $plist]] foreach {pname sub} [dict get $r $s] { foreach mode {cold warm} { if {![dict exists $sub $mode]} { set ok "missing $mode for $s $pname" ; break }