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 }