Browse Source
Gap-fill from a coverage review of long-option and mashopt parsing: the module's comment blocks specify style distinctions the suite never pinned. All behaviour was live-probed before pinning (characterisation, not aspiration). New testsuites/args/longopts.test (6 tests): - mixed optset -f|--file|--file= accepts inline/spaced/short-alias forms incl unique-prefix abbreviation of the longopt itself (--fi=abc / --fi abc) - gnu-only --flag= splits at the first = only (--filename=a=b -> a=b), accepts an empty inline value, rejects spaced and solo usage (badoptionformat) - plain --flag accepts spaced only; inline =value is invalidoption - the two =-member resolve-time definition errors (-type none with an = member; optional ?type? member without one) surface at first parse - single-dash -flag=value is never split at the = (invalidoption) - @opts -any 1 adhoc lane: undefined --flag=value splits to --flag + value, but single-dash -zz=5 stays a WHOLE adhoc flagname expecting a following value mashopts.test additions (2 tests): a mash containing an undefined letter is rejected whole (invalidoption naming the mashable set); under -any 1 -mash 1 defined flags still mash while undefined mash-lookalike tokens (-xy, -az) are adhoc flags taking the next word as value - never mash attempts. Error-shape pins use the first three -errorcode elements (PUNKARGS VALIDATION + failclass) rather than the ANSI-rendered messages; the two resolve errors are pinned by distinctive message substrings (plain text, no ANSI). Verified: runtests -include-paths modules/punk/args/testsuites/args longopts.test mashopts.test via tclsh90s - 13/13 pass, no warnings. src/tests/modules/AGENTS.md punk/args index entry updated (DOX pass). Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.commaster
3 changed files with 210 additions and 1 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,163 @@ |
|||||||
|
|
||||||
|
package require tcltest |
||||||
|
package require punk::args |
||||||
|
|
||||||
|
#Longopt (--flag / --flag=value) parsing characterisation - added 2026-08-07 as part of |
||||||
|
#the longopt/mash coverage gap review. The gnu-style inline-value lane (--flag=value with |
||||||
|
#choices, prefixes and optional ?type? members) is covered in opts.test and synopsis.test; |
||||||
|
#this file pins the style DISTINCTIONS the definition grammar draws: |
||||||
|
# --flag= gnu style - inline =value only; spaced and solo usage rejected |
||||||
|
# --flag plain style - spaced value only; inline =value rejected |
||||||
|
# -f|--file|--file= mixed - both forms accepted (the fossil-style optset) |
||||||
|
#plus the resolve-time definition errors for = members, the rule that single-dash |
||||||
|
#-flag=value tokens are never split at the =, and adhoc (@opts -any 1) passthrough of |
||||||
|
#=value tokens. |
||||||
|
#Error-shape pins use the first three -errorcode elements: PUNKARGS VALIDATION followed |
||||||
|
#by the failclass element, e.g. (badoptionformat --filename= index -1). |
||||||
|
|
||||||
|
namespace eval ::testspace { |
||||||
|
namespace import ::tcltest::* |
||||||
|
variable common { |
||||||
|
set result "" |
||||||
|
} |
||||||
|
|
||||||
|
#added 2026-08-07 (agent) - longopt/mash characterisation gap fill |
||||||
|
test longopts_mixed_optset_forms {a mixed optset -f|--file|--file= accepts inline, spaced and short-alias forms - storing under the last alternative stripped of =}\ |
||||||
|
-setup $common -body { |
||||||
|
#gnu-style inline value |
||||||
|
lappend result [dict get [punk::args::parse {--file=abc} withdef @opts {-f|--file|--file= -type string}] opts] |
||||||
|
#spaced value via the longopt itself - allowed because the plain --file member is present |
||||||
|
lappend result [dict get [punk::args::parse {--file abc} withdef @opts {-f|--file|--file= -type string}] opts] |
||||||
|
#short alias spaced value |
||||||
|
lappend result [dict get [punk::args::parse {-f abc} withdef @opts {-f|--file|--file= -type string}] opts] |
||||||
|
#a unique prefix of the longopt works for both inline and spaced forms |
||||||
|
lappend result [dict get [punk::args::parse {--fi=abc} withdef @opts {-f|--file|--file= -type string}] opts] |
||||||
|
lappend result [dict get [punk::args::parse {--fi abc} withdef @opts {-f|--file|--file= -type string}] opts] |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
}\ |
||||||
|
-result [list\ |
||||||
|
{--file abc}\ |
||||||
|
{--file abc}\ |
||||||
|
{--file abc}\ |
||||||
|
{--file abc}\ |
||||||
|
{--file abc}\ |
||||||
|
] |
||||||
|
|
||||||
|
test longopts_gnu_only_forms {a gnu-only --flag= optset takes inline =value split at the first = only; spaced and solo usage are badoptionformat errors}\ |
||||||
|
-setup $common -body { |
||||||
|
#a value containing = - the split happens at the first = only |
||||||
|
lappend result [dict get [punk::args::parse {--filename=a=b} withdef @opts {--filename= -type string}] opts] |
||||||
|
#an empty inline value is accepted for -type string |
||||||
|
lappend result [dict get [punk::args::parse {--filename=} withdef @opts {--filename= -type string}] opts] |
||||||
|
#a spaced value is not allowed when only the = member exists |
||||||
|
try { |
||||||
|
punk::args::parse {--filename abc} withdef @opts {--filename= -type string} |
||||||
|
lappend result "UNEXPECTED-accepted-spaced-value" |
||||||
|
} on error {emsg eopts} { |
||||||
|
lappend result [lrange [dict get $eopts -errorcode] 0 2] |
||||||
|
} |
||||||
|
#solo usage is not allowed either - the type is not an optional ?type? member |
||||||
|
try { |
||||||
|
punk::args::parse {--filename} withdef @opts {--filename= -type string} |
||||||
|
lappend result "UNEXPECTED-accepted-solo" |
||||||
|
} on error {emsg eopts} { |
||||||
|
lappend result [lrange [dict get $eopts -errorcode] 0 2] |
||||||
|
} |
||||||
|
set result |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
}\ |
||||||
|
-result [list\ |
||||||
|
{--filename a=b}\ |
||||||
|
{--filename {}}\ |
||||||
|
{PUNKARGS VALIDATION {badoptionformat --filename= index -1}}\ |
||||||
|
{PUNKARGS VALIDATION {badoptionformat --filename= index -1}}\ |
||||||
|
] |
||||||
|
|
||||||
|
test longopts_plain_doubledash_forms {a plain --flag optset accepts a spaced value but rejects inline =value as invalidoption}\ |
||||||
|
-setup $common -body { |
||||||
|
lappend result [dict get [punk::args::parse {--filename abc} withdef @opts {--filename -type string}] opts] |
||||||
|
try { |
||||||
|
punk::args::parse {--filename=abc} withdef @opts {--filename -type string} |
||||||
|
lappend result "UNEXPECTED-accepted-inline-value" |
||||||
|
} on error {emsg eopts} { |
||||||
|
lappend result [lrange [dict get $eopts -errorcode] 0 2] |
||||||
|
} |
||||||
|
set result |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
}\ |
||||||
|
-result [list\ |
||||||
|
{--filename abc}\ |
||||||
|
{PUNKARGS VALIDATION {invalidoption --filename=abc options --filename}}\ |
||||||
|
] |
||||||
|
|
||||||
|
test longopts_resolve_definition_errors {-type none with an = member and an optional ?type? without an = member are both resolve-time definition errors}\ |
||||||
|
-setup $common -body { |
||||||
|
#definitions are lazy - the resolve error surfaces at first parse |
||||||
|
try { |
||||||
|
punk::args::parse {--flag} withdef @opts {--flag= -type none} |
||||||
|
lappend result "UNEXPECTED-resolved-none-with-eq" |
||||||
|
} on error {emsg eopts} { |
||||||
|
lappend result [string match "*flag type 'none'*not supported when any flag member ends with =*" $emsg] |
||||||
|
} |
||||||
|
try { |
||||||
|
punk::args::parse {-f 3} withdef @opts {-f -type ?int?} |
||||||
|
lappend result "UNEXPECTED-resolved-optional-without-eq" |
||||||
|
} on error {emsg eopts} { |
||||||
|
lappend result [string match "*Optional flag parameter*not supported when no flag member ends with =*" $emsg] |
||||||
|
} |
||||||
|
set result |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
}\ |
||||||
|
-result [list\ |
||||||
|
1\ |
||||||
|
1\ |
||||||
|
] |
||||||
|
|
||||||
|
test longopts_singledash_no_inline_value {inline =value splitting applies only to double-dash longopts - a single-dash -flag=value token is not split}\ |
||||||
|
-setup $common -body { |
||||||
|
#-f=abc is not recognised as -f with value abc - the whole token fails as an unknown option |
||||||
|
try { |
||||||
|
punk::args::parse {-f=abc} withdef @opts {-f|--file= -type string} |
||||||
|
lappend result "UNEXPECTED-accepted-singledash-eq" |
||||||
|
} on error {emsg eopts} { |
||||||
|
lappend result [lrange [dict get $eopts -errorcode] 0 2] |
||||||
|
} |
||||||
|
set result |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
}\ |
||||||
|
-result [list\ |
||||||
|
{PUNKARGS VALIDATION {invalidoption -f=abc options -f|--file=}}\ |
||||||
|
] |
||||||
|
|
||||||
|
test longopts_adhoc_any_passthrough {@opts -any 1 splits an undefined --flag=value at the = but a single-dash -flag=value is a whole adhoc flagname expecting a value}\ |
||||||
|
-setup $common -body { |
||||||
|
#undefined --zz=5 splits to adhoc option --zz with value 5 - and is recorded as received |
||||||
|
set argd [punk::args::parse {--zz=5} withdef {@opts -any 1} {-x -type int} {@values -min 0 -max 0}] |
||||||
|
lappend result [dict get $argd opts] |
||||||
|
lappend result [dict get $argd received] |
||||||
|
#single-dash -zz=5 is NOT split - the whole token is the adhoc flagname and requires a following value |
||||||
|
try { |
||||||
|
punk::args::parse {-zz=5} withdef {@opts -any 1} {-x -type int} {@values -min 0 -max 0} |
||||||
|
lappend result "UNEXPECTED-accepted-bare-singledash-eq" |
||||||
|
} on error {emsg eopts} { |
||||||
|
lappend result [lrange [dict get $eopts -errorcode] 0 2] |
||||||
|
} |
||||||
|
set argd [punk::args::parse {-zz=5 6} withdef {@opts -any 1} {-x -type int} {@values -min 0 -max 0}] |
||||||
|
lappend result [dict get $argd opts] |
||||||
|
}\ |
||||||
|
-cleanup { |
||||||
|
}\ |
||||||
|
-result [list\ |
||||||
|
{--zz 5}\ |
||||||
|
{--zz 0}\ |
||||||
|
{PUNKARGS VALIDATION {missingoptionvalue -zz=5 index 0}}\ |
||||||
|
{-zz=5 6}\ |
||||||
|
] |
||||||
|
|
||||||
|
} |
||||||
|
tcltest::cleanupTests ;#needed to produce test summary line. |
||||||
Loading…
Reference in new issue