diff --git a/src/tests/modules/punk/args/testsuites/args/allocation.test b/src/tests/modules/punk/args/testsuites/args/allocation.test index 6eb4d263..706817f9 100644 --- a/src/tests/modules/punk/args/testsuites/args/allocation.test +++ b/src/tests/modules/punk/args/testsuites/args/allocation.test @@ -157,6 +157,97 @@ namespace eval ::testspace { -cleanup $cleanup\ -result [list {1 valid} _default 1] + + #added 2026-07-14 (agent) - tail-clause reservation walk in + #private::get_dict_can_assign_value: a LATER optional clause mixing literal(...) + #and stringstartswith(...) members should have its trailing words reserved from a + #greedy -multiple argument when (and only when) they match. The literal-only + #control and the no-match guard hold today and must keep holding. + test allocation_tailclause_literal_reservation_guard {tail-clause walk reserves trailing words for a literal-only optional clause}\ + -setup { + set result "" + punk::args::define { + @id -id ::testspace::alloc_tail_lit + @values -min 0 -max -1 + items -type string -multiple 1 -optional 1 + ender -type {literal(with) literal(now)} -optional 1 + } + }\ + -body { + set argd [punk::args::parse {a b with now} withid ::testspace::alloc_tail_lit] + lappend result [dict get $argd values] + }\ + -cleanup { + punk::args::undefine ::testspace::alloc_tail_lit 1 + }\ + -result [list\ + {items {a b} ender {with now}} + ] + + test allocation_tailclause_ssw_nomatch_guard {tail-clause walk does not reserve trailing words that do not match the stringstartswith member}\ + -setup { + set result "" + punk::args::define { + @id -id ::testspace::alloc_tail_ssw + @values -min 0 -max -1 + items -type string -multiple 1 -optional 1 + ender -type {literal(with) stringstartswith(v)} -optional 1 + } + }\ + -body { + #q99 does not start with v - items correctly consumes everything + set argd [punk::args::parse {a b with q99} withid ::testspace::alloc_tail_ssw] + lappend result [dict get $argd values] + }\ + -cleanup { + punk::args::undefine ::testspace::alloc_tail_ssw 1 + }\ + -result [list\ + {items {a b with q99}} + ] + + #GAP: the stringstartswith(*) arm of the multi-member tail-clause walk matches + #against $tp (the type string) instead of $rv (the candidate value), so: + # - genuinely matching trailing words are NOT reserved (the -multiple argument + # swallows them and the optional tail clause is lost), and + # - a prefix text that happens to prefix the literal type string (e.g 'str' + # prefixing 'stringstartswith(str)') reserves NON-matching words, ending in a + # spurious overflow error. + #Expected post-fix: {a b with v99} -> items {a b} ender {with v99}; + # {a b with zzz} (vs stringstartswith(str)) -> items {a b with zzz} + test allocation_tailclause_ssw_reservation_GAP {GAP: stringstartswith tail-clause member compares the type string, not the value}\ + -setup { + set result "" + punk::args::define { + @id -id ::testspace::alloc_tail_ssw_v + @values -min 0 -max -1 + items -type string -multiple 1 -optional 1 + ender -type {literal(with) stringstartswith(v)} -optional 1 + } + punk::args::define { + @id -id ::testspace::alloc_tail_ssw_str + @values -min 0 -max -1 + items -type string -multiple 1 -optional 1 + ender -type {literal(with) stringstartswith(str)} -optional 1 + } + }\ + -body { + #matching trailing words lost to items + set argd [punk::args::parse {a b with v99} withid ::testspace::alloc_tail_ssw_v] + lappend result [dict get $argd values] + #non-matching trailing words wrongly reserved -> spurious overflow error + set err [catch {punk::args::parse {a b with zzz} withid ::testspace::alloc_tail_ssw_str} msg opts] + lappend result $err [lrange [dict get $opts -errorcode] 0 2] + }\ + -cleanup { + punk::args::undefine ::testspace::alloc_tail_ssw_v 1 + punk::args::undefine ::testspace::alloc_tail_ssw_str 1 + }\ + -result [list\ + {items {a b with v99}}\ + 1 {PUNKARGS VALIDATION {toomanyarguments 4 index 2}} + ] + cleanupTests } namespace delete ::testspace