22 changed files with 3040 additions and 97 deletions
@ -1,38 +1,39 @@
|
||||
# src/tests — Unit Tests for src-Hierarchy Modules |
||||
# src/tests — Source-Tree Test Harness |
||||
|
||||
## Purpose |
||||
|
||||
Unit tests that run against modules in the `src/` source tree, prior to installation. These tests use the `tcltest` package and target the uninstalled module source code. |
||||
Top-level test harness and source-tree tests for ShellSpy/Punk. Tests here exercise source files under `src/` before installation or packaging, including module tests, shell behavior tests, and selected Tcl core compatibility tests. |
||||
|
||||
## Ownership |
||||
|
||||
- Owned by the project maintainer; agents should add new tests here when modifying modules but must not delete or silently skip existing failing tests. |
||||
- This is the primary test-authoring location for module behavior. Installed-module test packages under `src/modules/test/` are a packaging layer, not the default place to add tests. |
||||
- Owned by the project maintainer; agents may update tests here when changing source behavior. |
||||
- Do not delete, skip, or weaken existing tests without explicit user direction. |
||||
- Keep build-output and installed-package tests outside this tree unless a local child AGENTS.md says otherwise. |
||||
|
||||
## Local Contracts |
||||
|
||||
- Test files use `.tcl` extension and must `package require punk` plus any extra packages explicitly. |
||||
- The directory hierarchy under `src/tests/modules/` mirrors namespace paths of the modules being tested. |
||||
- Tests run against source modules in the `src/` tree, not installed packages. |
||||
- New test files go in `src/tests/modules/<namespacepath>/tests/` mirroring the module namespace. |
||||
- Prefer `try { ... } on error {result options} { ... }` for structured error capture in test bodies. |
||||
- Focused verification should mirror production pipelines inside tests using Punk pipeline syntax when that parity matters. |
||||
- Test files may commonly use line-continuation backslashes, and existing Tcl expand-syntax structures in tests should not be refactored into line continuations. |
||||
- `runtests.tcl` is the primary source-tree test entry point. |
||||
- Tests use `tcltest` unless a child AGENTS.md documents a different local harness. |
||||
- `runtests.tcl` excludes `AGENTS.md` and `*.tcl` helper files when discovering `.test` files. |
||||
- Tests should run against source modules and libraries from `src/`, not installed packages or root-level build outputs. |
||||
- Test files must `package require` any extra packages explicitly. |
||||
|
||||
## Work Guidance |
||||
|
||||
- Run a single test: `tclsh src/tests/modules/<namespacepath>/tests/all.tcl`. |
||||
- Run the full suite: `tclsh src/tests/runtests.tcl`. |
||||
- On Windows, an alternate full-suite loop is `for /r src\tests %f in (*.tcl) do tclsh %f`. |
||||
- When adding a test, mirror the namespace path of the module under test. |
||||
- Do not add tests for vendormodules or bootsupport here unless explicitly requested. |
||||
- Capture stderr logs for failure triage and include enough context for the failing command or assertion. |
||||
- Select an appropriate interpreter, such as `tclsh`, `tclsh90`, or a built Punk executable such as `punk902z`. |
||||
- Run the full source-tree suite with `<tcl_interpreter> src/tests/runtests.tcl`. |
||||
- Use `-include-paths` with paths relative to `src/tests/`, using forward slashes. |
||||
- Use `-tcltestoptions {-match <test_name>}` for focused single-test runs. |
||||
- Capture enough stderr or failure context to identify the failing command or assertion. |
||||
|
||||
## Verification |
||||
|
||||
- `tclsh src/tests/<path>/all.tcl` exits cleanly with pass/fail summary. |
||||
- `tclsh src/tests/runtests.tcl` passes when broad test coverage is relevant. |
||||
- `<tcl_interpreter> src/tests/runtests.tcl` passes when broad source-tree test coverage is relevant. |
||||
- Focused checks use `<tcl_interpreter> src/tests/runtests.tcl -include-paths <relative/path/**>` and optional file-tail globs. |
||||
- Documentation-only changes are verified by reviewing the affected DOX chain and diff. |
||||
|
||||
## Child DOX Index |
||||
|
||||
- `modules/` — Per-namespace test directories mirroring `src/modules/` structure |
||||
- `modules/` — Source module test suites mirroring editable module namespaces (see modules/AGENTS.md) |
||||
- `core/` — Selected Tcl core compatibility tests for standard Tcl and built Punk executables (see core/AGENTS.md) |
||||
- `shell/` — Shell behavior tests and command-line interaction suites (see shell/AGENTS.md) |
||||
|
||||
@ -0,0 +1,31 @@
|
||||
# src/tests/core — Tcl Core Compatibility Tests |
||||
|
||||
## Purpose |
||||
|
||||
Selected tests taken from or modeled after Tcl core tests. These tests should produce compatible results under a standard `tclsh` and an appropriate built Punk executable. |
||||
|
||||
## Ownership |
||||
|
||||
- Agents may update this subtree when changing compatibility expectations or test harness behavior for core Tcl commands. |
||||
- Preserve the intent of upstream Tcl compatibility coverage when editing imported or adapted tests. |
||||
|
||||
## Local Contracts |
||||
|
||||
- Tests under this subtree are compatibility tests, not general module behavior tests. |
||||
- Keep command behavior expectations explicit enough to compare standard Tcl and built Punk results. |
||||
- Avoid mixing source-module feature tests into this subtree; use `src/tests/modules/` instead. |
||||
|
||||
## Work Guidance |
||||
|
||||
- Use `src/tests/runtests.tcl` from the repository root for source-tree harness runs. |
||||
- Prefer focused `-include-paths core/**` checks when editing only this subtree. |
||||
- Document any deliberate divergence from standard Tcl behavior in the nearest relevant test file or child AGENTS.md. |
||||
|
||||
## Verification |
||||
|
||||
- `<tcl_interpreter> src/tests/runtests.tcl -include-paths core/**` passes for changes in this subtree. |
||||
- Compare results with standard `tclsh` and the target Punk executable when compatibility behavior is the subject of the change. |
||||
|
||||
## Child DOX Index |
||||
|
||||
- `tcl/testsuites/tests/` — Imported/adapted Tcl core test files (see tcl/testsuites/tests/AGENTS.md) |
||||
@ -0,0 +1,27 @@
|
||||
# src/tests/core/tcl/testsuites/tests — Tcl Core Test Files |
||||
|
||||
## Purpose |
||||
|
||||
A selection of unit tests taken from or modeled after the Tcl core distribution. These tests, designed for a standard `tclsh`, should produce the same results for a suitable built Punk executable. |
||||
|
||||
## Ownership |
||||
|
||||
- Preserve Tcl core compatibility intent when editing tests in this directory. |
||||
- Keep broader core-test rules in `src/tests/core/AGENTS.md`. |
||||
|
||||
## Local Contracts |
||||
|
||||
- Tests here target Tcl command compatibility, not general Punk module behavior. |
||||
- Deliberate Punk divergences from standard Tcl behavior must be documented near the affected test. |
||||
|
||||
## Work Guidance |
||||
|
||||
- Use focused runs through `src/tests/runtests.tcl` with `-include-paths core/tcl/testsuites/tests/**`. |
||||
|
||||
## Verification |
||||
|
||||
- `<tcl_interpreter> src/tests/runtests.tcl -include-paths core/tcl/testsuites/tests/**` passes for changes in this directory. |
||||
|
||||
## Child DOX Index |
||||
|
||||
|
||||
@ -0,0 +1,125 @@
|
||||
#! /usr/bin/env tclsh |
||||
|
||||
# Don't overwrite tcltests facilities already present |
||||
if {[package provide tcltests] ne {}} return |
||||
|
||||
package require tcltest 2.5 |
||||
namespace import ::tcltest::* |
||||
testConstraint exec [llength [info commands exec]] |
||||
testConstraint deprecated [expr {![tcl::build-info no-deprecate]}] |
||||
testConstraint debug [tcl::build-info debug] |
||||
testConstraint purify [tcl::build-info purify] |
||||
testConstraint debugpurify [ |
||||
expr { |
||||
![tcl::build-info memdebug] |
||||
&& [testConstraint debug] |
||||
&& [testConstraint purify] |
||||
}] |
||||
testConstraint bigmem [expr {[ |
||||
info exists ::env(TCL_TESTCONSTRAINT_BIGMEM)] |
||||
? !!$::env(TCL_TESTCONSTRAINT_BIGMEM) |
||||
: 1 |
||||
}] |
||||
testConstraint fcopy [llength [info commands fcopy]] |
||||
testConstraint fileevent [llength [info commands fileevent]] |
||||
testConstraint thread [expr {![catch {package require Thread 2.7-}]}] |
||||
testConstraint notValgrind [expr {![testConstraint valgrind]}] |
||||
|
||||
|
||||
namespace eval ::tcltests { |
||||
|
||||
|
||||
proc init {} { |
||||
if {[namespace which ::tcl::file::tempdir] eq {}} { |
||||
interp alias {} [namespace current]::tempdir {} [ |
||||
namespace current]::tempdir_alternate |
||||
} else { |
||||
interp alias {} [namespace current]::tempdir {} ::tcl::file::tempdir |
||||
} |
||||
} |
||||
|
||||
|
||||
# Stolen from dict.test |
||||
proc scriptmemcheck script { |
||||
set end [lindex [split [memory info] \n] 3 3] |
||||
for {set i 0} {$i < 5} {incr i} { |
||||
uplevel 1 $script |
||||
set tmp $end |
||||
set end [lindex [split [memory info] \n] 3 3] |
||||
} |
||||
expr {$end - $tmp} |
||||
} |
||||
|
||||
|
||||
proc tempdir_alternate {} { |
||||
close [file tempfile tempfile] |
||||
set tmpdir [file dirname $tempfile] |
||||
set execname [info nameofexecutable] |
||||
regsub -all {[^[:alpha:][:digit:]]} $execname _ execname |
||||
for {set i 0} {$i < 10000} {incr i} { |
||||
set time [clock milliseconds] |
||||
set name $tmpdir/${execname}_${time}_$i |
||||
if {![file exists $name]} { |
||||
file mkdir $name |
||||
return $name |
||||
} |
||||
} |
||||
error [list {could not create temporary directory}] |
||||
} |
||||
|
||||
# Generates test cases for 0, min and max number of arguments for a command. |
||||
# Expected result is as generated by Tcl_WrongNumArgs |
||||
# Only works if optional arguments come after fixed arguments |
||||
# E.g. |
||||
# testnumargs "zipfs mount" "" "?mountpoint? ?zipfile? ?password?" |
||||
# testnumargs "lappend" "varName" "?value ...?" |
||||
proc testnumargs {cmd {fixed {}} {optional {}} args} { |
||||
variable count |
||||
set minargs [llength $fixed] |
||||
set maxargs [expr {$minargs + [llength $optional]}] |
||||
if {[regexp {\.\.\.\??$} [lindex $optional end]]} { |
||||
unset maxargs; # No upper limit on num of args |
||||
} |
||||
set message "wrong # args: should be \"$cmd" |
||||
if {[llength $fixed]} { |
||||
append message " $fixed" |
||||
} |
||||
if {[llength $optional]} { |
||||
append message " $optional" |
||||
} |
||||
if {[llength $fixed] == 0 && [llength $optional] == 0} { |
||||
append message " \"" |
||||
} else { |
||||
append message "\"" |
||||
} |
||||
set label [join $cmd -] |
||||
if {$minargs > 0} { |
||||
set arguments [lrepeat [expr {$minargs-1}] x] |
||||
test $label-minargs-[incr count($label-minargs)] \ |
||||
"$label no arguments" \ |
||||
-body "$cmd" \ |
||||
-result $message -returnCodes error \ |
||||
{*}$args |
||||
if {$minargs > 1} { |
||||
test $label-minargs-[incr count($label-minargs)] \ |
||||
"$label missing arguments" \ |
||||
-body "$cmd $arguments" \ |
||||
-result $message -returnCodes error \ |
||||
{*}$args |
||||
} |
||||
} |
||||
if {[info exists maxargs]} { |
||||
set arguments [lrepeat [expr {$maxargs+1}] x] |
||||
test $label-maxargs-[incr count($label-maxargs)] \ |
||||
"$label extra arguments" \ |
||||
-body "$cmd $arguments" \ |
||||
-result $message -returnCodes error \ |
||||
{*}$args |
||||
} |
||||
} |
||||
|
||||
init |
||||
|
||||
package provide tcltests 0.1 |
||||
} |
||||
|
||||
@ -0,0 +1,34 @@
|
||||
# src/tests/modules — Source Module Test Suites |
||||
|
||||
## Purpose |
||||
|
||||
Unit tests for editable source modules under `src/modules/`, `src/modules_tcl8/`, and `src/modules_tcl9/`. These tests use `tcltest` and run against the uninstalled source tree through `src/tests/runtests.tcl`. |
||||
|
||||
## Ownership |
||||
|
||||
- Agents should add or update tests here when changing module behavior in `src/modules/` or Tcl-version-specific module trees. |
||||
- Installed-module test packages under `src/modules/test/` are packaging artifacts and are not the default place for source-tree behavior tests. |
||||
- Do not delete, skip, or weaken existing tests without explicit user direction. |
||||
|
||||
## Local Contracts |
||||
|
||||
- The directory hierarchy mirrors module namespace paths, such as `src/tests/modules/punk/args/` for `punk::args`. |
||||
- Test files use `.test` extension and must `package require` any extra packages explicitly. |
||||
- Test files may commonly use line-continuation backslashes, and existing Tcl expand-syntax structures in tests should not be refactored into line continuations. |
||||
- Tests must exercise source modules as loaded by `src/tests/runtests.tcl`, not installed or built output modules. |
||||
|
||||
## Work Guidance |
||||
|
||||
- Put new module tests under `src/tests/modules/<namespacepath>/testsuites/<suite>/` following nearby layout. |
||||
- Prefer `try { ... } on error {result options} { ... }` for structured error capture in test bodies. |
||||
- Focused verification should mirror production pipelines inside tests using Punk pipeline syntax when that parity matters. |
||||
- Capture enough stderr or failure context to identify the failing command or assertion. |
||||
|
||||
## Verification |
||||
|
||||
- Run targeted module tests with `<tcl_interpreter> src/tests/runtests.tcl -include-paths modules/<namespacepath>/**`. |
||||
- Run a single test by name with `<tcl_interpreter> src/tests/runtests.tcl -tcltestoptions {-match <test_name>} -include-paths modules/<namespacepath>/**`. |
||||
- Run a specific test file by adding its file tail, such as `<tcl_interpreter> src/tests/runtests.tcl -include-paths modules/punk/args/** parsekey.test`. |
||||
|
||||
## Child DOX Index |
||||
|
||||
@ -0,0 +1,43 @@
|
||||
package require tcltest |
||||
|
||||
package require punk::ns |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
variable common { |
||||
set result "" |
||||
} |
||||
|
||||
|
||||
test documented_alias {Test docs from documented alias take preference}\ |
||||
-setup $common -body { |
||||
proc underlying {args} {return underlying-$args} |
||||
interp alias "" ::testspace::doit "" ::testspace::underlying curriedarg1 |
||||
#punk::ns::cmdhelp = 'i doit' |
||||
set helpoutput [punk::ns::cmdhelp -return string doit] |
||||
lappend result [string match "*::testspace::underlying*" $helpoutput] |
||||
set synoutput [punk::ns::synopsis doit] |
||||
#check we see the curried argument in the synopsis |
||||
lappend result [string match "*curriedarg1*" $synoutput] |
||||
|
||||
#define an overiding doc on the alias |
||||
punk::args::define {@id -id ::testspace::doit} {@cmd -help "doit help"} @values {extra -multiple 1 -type any} |
||||
set helpoutput [punk::ns::cmdhelp -return string doit] |
||||
lappend result [string match "*doit help*" $helpoutput] |
||||
set synoutput [punk::ns::synopsis doit] |
||||
puts stderr ------------------- |
||||
puts stderr $synoutput |
||||
puts stderr ------------------- |
||||
lappend result [string match "*extra*" $synoutput] |
||||
|
||||
}\ |
||||
-cleanup { |
||||
interp alias "" ::testspace::doit "" |
||||
punk::args::undefine ::testspace::doit |
||||
}\ |
||||
-result [list\ |
||||
1 1 1 1\ |
||||
] |
||||
|
||||
} |
||||
tcltest::cleanupTests ;#needed to produce test summary. |
||||
@ -0,0 +1,631 @@
|
||||
|
||||
package require tcltest |
||||
|
||||
package require punk::args |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
variable common { |
||||
set result "" |
||||
} |
||||
|
||||
|
||||
test parse_withdef_leaders_ordering_defaults {Test ordering of leaders when some have defaults}\ |
||||
-setup $common -body { |
||||
set argd [punk::args::parse {a b} withdef @leaders x {y -default 1}] |
||||
set docid [dict get $argd id] |
||||
set vals [dict get $argd leaders] |
||||
set result $vals |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
x a y b |
||||
] |
||||
|
||||
|
||||
test parse_withdef_auto_change_argspace_from_options_to_values {test first non dashed argdef after option is treated as a value when @values not explicit}\ |
||||
-setup $common -body { |
||||
#test val1 following -opt is automatically placed in 'values' when @values directive is missing |
||||
set argd [punk::args::parse {-opt 1 b} withdef -opt val1] |
||||
set docid [dict get $argd id] |
||||
set vals [dict get $argd values] |
||||
set result $vals |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
val1 b |
||||
] |
||||
|
||||
|
||||
test parse_withdef_option_ordering_defaults {Test ordering of options when some have defaults}\ |
||||
-setup $common -body { |
||||
#for consistency with leaders and values dicts - try to maintain definition order for options too |
||||
set argd [punk::args::parse {-x a -y b} withdef @opts -x {-y -default 1}] |
||||
set docid [dict get $argd id] |
||||
set vals [dict get $argd opts] |
||||
set result $vals |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
-x a -y b |
||||
] |
||||
|
||||
test parse_withdef_option_ordering_defaults2 {Test ordering of options when some have defaults and -any is true}\ |
||||
-setup $common -body { |
||||
#for consistency with leaders and values dicts - try to maintain definition order for options too |
||||
set argd [punk::args::parse {-blah etc -x a -y b -solo -z c} withdef {@opts -any 1} -x {-y -default 1} {-solo -type none} -z] |
||||
set docid [dict get $argd id] |
||||
set vals [dict get $argd opts] |
||||
set result $vals |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
-x a -y b -solo 1 -z c -blah etc |
||||
] |
||||
|
||||
test parse_withdef_values_ordering_defaults {Test ordering of values when some have defaults}\ |
||||
-setup $common -body { |
||||
set argd [punk::args::parse {a b} withdef @values x {y -default 1}] |
||||
set docid [dict get $argd id] |
||||
set vals [dict get $argd values] |
||||
set result $vals |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
x a y b |
||||
] |
||||
|
||||
test parse_withdef_values_no_phantom_default {Test no phantom default with intermediate optional argument}\ |
||||
-setup $common -body { |
||||
#y was not received, and has no default, so should not appear in 'values' element |
||||
#we don't want to see {x a y {} z b} |
||||
set argd [punk::args::parse {a b} withdef @values x {y -optional 1} z] |
||||
set docid [dict get $argd id] |
||||
set vals [dict get $argd values] |
||||
set result $vals |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
x a z b |
||||
] |
||||
test parse_withdef_value_multiple1 {Test named value with -multiple true and required trailing value}\ |
||||
-setup $common -body { |
||||
set argd [punk::args::parse {a b c} withdef @values {arg -type string -multiple 1} endval] |
||||
set docid [dict get $argd id] |
||||
lappend result [dict get $argd leaders] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
{} {arg {a b} endval c} |
||||
] |
||||
|
||||
test parse_withdef_value_multiple2 {Test named value followed by named value with -multiple true and a default}\ |
||||
-setup $common -body { |
||||
set argd [punk::args::parse {a b c} withdef @values A {arg -type string -multiple 1 -default X}] |
||||
set docid [dict get $argd id] |
||||
lappend result [dict get $argd leaders] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
{} {A a arg {b c}} |
||||
] |
||||
|
||||
test parse_withdef_leader_multiple1 {Test named leader with -multiple true and 1 value required}\ |
||||
-setup $common -body { |
||||
set argd [punk::args::parse {a b c} withdef {@leaders -min 0} {L -multiple 1} {@values -min 1 -max 1} V] |
||||
set docid [dict get $argd id] |
||||
lappend result [dict get $argd leaders] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
{L {a b}} {V c} |
||||
] |
||||
|
||||
test parse_withdef_leader_min_max1 {Test unnamed leaders with @leaders -min and -max}\ |
||||
-setup $common -body { |
||||
set argd [punk::args::parse {a b c d} withdef {@leaders -min 1 -max 3 -unnamed true} {@values -unnamed true} ] |
||||
set docid [dict get $argd id] |
||||
lappend result [dict get $argd leaders] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
{0 a 1 b 2 c} {3 d} |
||||
] |
||||
test parse_withdef_leader_min_max_with_required_value {Test unnamed leaders with -min and -max followed by required unnamed value}\ |
||||
-setup $common -body { |
||||
#should not error - should allocate d to values |
||||
set argd [punk::args::parse {a b c d} withdef {@leaders -min 1 -max 4 -unnamed true} {@values -min 1 -max 1 -unnamed true}] |
||||
set docid [dict get $argd id] |
||||
lappend result [dict get $argd leaders] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
{0 a 1 b 2 c} {3 d} |
||||
] |
||||
|
||||
test parse_withdef_leaderclause_trailing_optional_members_followed_by_value {Test that last leader clause with optional members works with following required value}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
set argd [punk::args::parse {a z} withdef {@leaders} {ldr -type {char ?int? ?int?}} {@values} val] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd leaders] |
||||
set argd [punk::args::parse {a 1 z} withdef {@leaders} {ldr -type {char ?int? ?int?}} {@values} val] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd leaders] |
||||
set argd [punk::args::parse {a 1 2 z} withdef {@leaders} {ldr -type {char ?int? ?int?}} {@values} val] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd leaders] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{ldr {a {} {}}}\ |
||||
{ldr {a 1 {}}}\ |
||||
{ldr {a 1 2}}\ |
||||
] |
||||
test parse_withdef_leaderclause_trailing_optional_members_followed_by_optional_leader_and_value {Test that last leader clause with optional members works with following required value}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
set argd [punk::args::parse {x y z} withdef {@leaders} {ldr -type {char ?int? ?int?}} {ldr2 -type string -optional 1} {@values} val] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd leaders] |
||||
|
||||
set argd [punk::args::parse {x 1 z} withdef {@leaders} {ldr -type {char ?int? ?int?}} {ldr2 -type string -optional 1} {@values} val] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd leaders] |
||||
|
||||
set argd [punk::args::parse {x 1 y z} withdef {@leaders} {ldr -type {char ?int? ?int?}} {ldr2 -type string -optional 1} {@values} val] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd leaders] |
||||
|
||||
set argd [punk::args::parse {x 1 2 y z} withdef {@leaders} {ldr -type {char ?int? ?int?}} {ldr2 -type string -optional 1} {@values} val] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd leaders] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{ldr {x {} {}} ldr2 y}\ |
||||
{ldr {x 1 {}}}\ |
||||
{ldr {x 1 {}} ldr2 y}\ |
||||
{ldr {x 1 2} ldr2 y}\ |
||||
] |
||||
|
||||
test parse_withdef_value_clause_typedefaults {test clause with optional element and -typedefaults specified}\ |
||||
-setup $common -body { |
||||
set argd [punk::args::parse {1} withdef @values {v -type {int ?int?} -typedefaults {"" 12}}] |
||||
set docid [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
{v {1 12}} |
||||
] |
||||
test parse_withdef_value_clause_typedefaults2 {test clause with optional element and -typedefaults specified - entire arg optional -default}\ |
||||
-setup $common -body { |
||||
#-default has deliberate type violations - should still produce result as default is not meant to be subject to validation. |
||||
set argd [punk::args::parse {} withdef @values {v -type {int ?int?} -typedefaults {"" 12} -default {x y} -optional 1}] |
||||
set docid [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
{v {x y}} |
||||
] |
||||
|
||||
test parse_withdef_value_clause_defaulted_optional {test clause with optional element and -typedefaults not matching all types}\ |
||||
-setup $common -body { |
||||
#-typedefaults has deliberate type violations - should still produce result as defaulted value is not meant to be subject to validation. |
||||
#(uses the ?defaulted-<type>? typelist mechanism) |
||||
set argd [punk::args::parse {1} withdef @values {v -type {int ?int?} -typedefaults {"" xxx}}] |
||||
set docid [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
{v {1 xxx}} |
||||
] |
||||
test parse_withdef_value_clause_missing_optional {test clause with optional element and no -typedefaults}\ |
||||
-setup $common -body { |
||||
#an optional clause member will be replaced with empty string when missing if there is no -typedefaults |
||||
#This empty string needs to be in the result, but not be subject to validation |
||||
#(uses the ?ommitted-<type>? typelist mechanism) |
||||
set argd [punk::args::parse {1} withdef @values {v -type {int ?int?}}] |
||||
set docid [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
{v {1 {}}} |
||||
] |
||||
|
||||
test parse_withdef_value_clause_arity1 {Test value clause result with optional member}\ |
||||
-setup $common -body { |
||||
#default for missing optional member ?literal(then)? should be empty string |
||||
set argd [punk::args::parse {elseif 1 x} withdef {@values} {"elseifclause" -type {literal(elseif) expr ?literal(then)? any}}] |
||||
set docid [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
punk::args::undefine $docid 1 |
||||
}\ |
||||
-result [list\ |
||||
{elseifclause {elseif 1 {} x}} |
||||
] |
||||
|
||||
test parse_withdef_value_clause_missing_optional_multiple {test -multiple true clauses with optional members}\ |
||||
-setup $common -body { |
||||
#this test is applicable to situations such as the elseif clause in the ::if definition: |
||||
#e.g literal(elseif) expr ?literal(then)? script |
||||
#the 'then' needs to be omitable arbitrarily in a list of elseif clauses |
||||
|
||||
set docids [list] |
||||
|
||||
#first test with all values supplied |
||||
set argd [punk::args::parse {x 1 y x 2 y} withdef @values {triple -type {literal(x) ?int? literal(y)} -multiple 1}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
|
||||
#missing value in second instance only |
||||
set argd [punk::args::parse {x 1 y x y} withdef @values {triple -type {literal(x) ?int? literal(y)} -multiple 1}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
|
||||
#missing value in first instance only |
||||
#this can trigger a problem whereby the missing value in the first instance (which is empty string) gets processed in validation against 'int' and fails. |
||||
#(updating of required type to a validationless value such as ... ?omitted-int? ... needs to be tied to specific clause instances) |
||||
set argd [punk::args::parse {x y x 2 y} withdef @values {triple -type {literal(x) ?int? literal(y)} -multiple 1}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
|
||||
#for completeness - no optional values supplid |
||||
set argd [punk::args::parse {x y x y} withdef @values {triple -type {literal(x) ?int? literal(y)} -multiple 1}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
|
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{triple {{x 1 y} {x 2 y}}}\ |
||||
{triple {{x 1 y} {x {} y}}}\ |
||||
{triple {{x {} y} {x 2 y}}}\ |
||||
{triple {{x {} y} {x {} y}}}\ |
||||
] |
||||
|
||||
|
||||
|
||||
|
||||
test parse_withdef_value_clause_arity2 {Test value clause result with missing optional member in optional clauses at tail}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
set argd [punk::args::parse {1 2 x 1 y} withdef {@values -unnamed true} {arg -multiple 1} {X -type {literal(x) any} -optional 1} {Y -type {literal(y) ?int?} -optional 1}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{arg {1 2} X {x 1} Y {y {}}} |
||||
] |
||||
|
||||
|
||||
test parse_withdef_value_clause_arity3 {Test value clause result with filled optional member in optional clauses at tail}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
set argd [punk::args::parse {1 2 x 1 y 2} withdef {@values -unnamed true} {arg -multiple 1} {X -type {literal(x) any} -optional 1} {Y -type {literal(y) ?int?} -optional 1}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{arg {1 2} X {x 1} Y {y 2}} |
||||
] |
||||
|
||||
test parse_withdef_value_optionality_type_detection {Test optional values assigned based on type}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
|
||||
set def [list @values {i -type int -optional 1} {d -type dict -optional 1}] |
||||
|
||||
set argd [punk::args::parse {3} withdef {*}$def] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
|
||||
set argd [punk::args::parse {{k v}} withdef {*}$def] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
|
||||
set argd [punk::args::parse {3 {k v}} withdef {*}$def] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
|
||||
set argd [punk::args::parse {} withdef {*}$def] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
} {*}{ |
||||
-cleanup } { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
} {*}{ |
||||
-result } [list {*}{ |
||||
{i 3} |
||||
{d {k v}} |
||||
{i 3 d {k v}} |
||||
{} |
||||
}] |
||||
|
||||
# test L1 parsed to Lit1 not arg |
||||
#punk::args::parse {x y L1} withdef @values (arg -multiple 1) {lit1 -type literal(L1) -optional 1} {lit2 -type literal(L2) -optional 1} |
||||
test parse_withdef_value_leading_multiple_not_greedy_with_trailing_literal {Test value clause with leading -multiple true clause is not greedy when trailing literal can be matched}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
set argd [punk::args::parse {x y L1} withdef @values {arg -multiple 1} {lit1 -type literal(L1) -optional 1} {lit2 -type literal(L2) -optional 1}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{arg {x y} lit1 L1} |
||||
] |
||||
|
||||
#test the same with literalprefix type |
||||
test parse_withdef_value_leading_multiple_not_greedy_with_trailing_literalprefix { |
||||
-setup $common -body { |
||||
set docids [list] |
||||
set argd [punk::args::parse {x y te} withdef @values {arg -multiple 1} {lit1 -type literalprefix(test) -optional 1} {lit2 -type literalprefix(other) -optional 1}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{arg {x y} lit1 test}\ |
||||
] |
||||
} |
||||
|
||||
#todo |
||||
#see i -form 1 file copy -- x |
||||
#fix end-of-opts handling |
||||
#see also file copy -force x |
||||
#(not handled by punk::args as the command does..) |
||||
|
||||
test parse_withdef_leader_clause {Test leader clause with multiple}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
#see for example ::tcl::dict::create which has a clause length of 2 |
||||
set argd [punk::args::parse {k v e k1 v1 k2 v2} withdef {@leaders} {"key val etc" -type {any any any} -multiple 0} {"key val" -type {any any} -multiple 1} {@values -min 0 -max 0}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd leaders] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{{key val etc} {k v e} {key val} {{k1 v1} {k2 v2}}} |
||||
] |
||||
|
||||
test parse_withdef_value_clause_multiple {Test value clause with multiple}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
#see for example ::tcl::dict::create which has a clause length of 2 |
||||
set argd [punk::args::parse {k v e k1 v1 k2 v2} withdef {@values} {"key val etc" -type {any any any} -multiple 0} {"key val" -type {any any} -multiple 1}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{{key val etc} {k v e} {key val} {{k1 v1} {k2 v2}}} |
||||
] |
||||
|
||||
test parse_withdef_value_clause_error {Test value clause with error due to not enough args for clause}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
#see for example ::tcl::dict::create which has a clause length of 2 |
||||
if {[catch {punk::args::parse {k v} withdef {@values} {"key val etc" -type {any any any} -multiple 0}} emsg eopts]} { |
||||
set expected [dict get $eopts -errorcode] |
||||
if {[lindex $expected 0] eq "PUNKARGS" && [lindex $expected 1] eq "VALIDATION" && [lindex $expected 2 0] eq "missingrequiredvalue"} { |
||||
lappend result "RECEIVED_EXPECTED_ERROR" |
||||
} else { |
||||
lappend result "WRONG_ERROR_RECEIVED - $expected (expected PUNKARGS VALIDATION {missingrequiredvalue ...} ..." |
||||
} |
||||
#REVIEW - when an error is raised in parsing the arguments, but this is the 1st time we used this def, an autoid is still generated - but |
||||
#we don't have the id available (unless we scraped the error msg) in order to delete it. |
||||
#perhaps no big deal ? |
||||
#we could provide facility in punk::args to undefine via the deflist. |
||||
} else { |
||||
lappend docids [dict get $emsg id] |
||||
lappend result "MISSING_REQUIRED_ERROR" |
||||
} |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
"RECEIVED_EXPECTED_ERROR" |
||||
] |
||||
|
||||
|
||||
|
||||
test parse_withdef_leader_literalprefix_fullvalue {leaders - ensure supplying a prefix of literalprefix(test) returns full value 'test'}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
set argd [punk::args::parse {t} withdef @leaders {A -type literalprefix(test)}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd leaders] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{A test} |
||||
] |
||||
|
||||
test parse_withdef_value_literalprefix_fullvalue {values - ensure supplying a prefix of literalprefix(test) returns full value 'test'}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
set argd [punk::args::parse {t} withdef @values {A -type literalprefix(test)}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{A test} |
||||
] |
||||
|
||||
test parse_withdef_value_literal_alternates_case {values - ensure literal alternates work and preserve case}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
set argd [punk::args::parse {abc} withdef @values {A -type literal(abc)|literal(DeF)}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
set argd [punk::args::parse {DeF} withdef @values {A -type literal(abc)|literal(DeF)}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{A abc} {A DeF} |
||||
] |
||||
|
||||
test parse_withdef_value_literalprefix_literal_combo {values - ensure literal/literalprefix prefix calculation works}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
set argd [punk::args::parse {test} withdef @values {A -type literalprefix(testinfo)|literal(test)}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
set argd [punk::args::parse {testin} withdef @values {A -type literalprefix(testinfo)|literal(test)}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{A test} {A testinfo} |
||||
] |
||||
|
||||
test parse_withdef_value_alternatetypes {values - ensure alternate types (in simple-syntax) pass validation}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
#both should pass validation |
||||
set argd [punk::args::parse {a} withdef @values {A -type int|char}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
set argd [punk::args::parse {11} withdef @values {A -type char|int}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd values] |
||||
|
||||
#todo RPN? |
||||
#set argd [punk::args::parse {11} withdef @values {A -type {char int OR}}] |
||||
#set argd [punk::args::parse {11} withdef @values {A -type {char int stringstartswith | OR}}] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{A a} {A 11} |
||||
] |
||||
|
||||
test parse_same_autoid {Test that repeat use of same def uses same autoid}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
set argd [punk::args::parse {a b c} withdef @values {arg -type string -multiple 1} endval] |
||||
lappend docids [dict get $argd id] |
||||
set argd [punk::args::parse {x y z} withdef @values {arg -type string -multiple 1} endval] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [string equal [lindex $docids 0] [lindex $docids 1]] |
||||
}\ |
||||
-cleanup { |
||||
puts stderr "Expect output on stderr about punk::args::undefine unable to find id" |
||||
#second undefine warning is expected |
||||
foreach id $docids { |
||||
punk::args::undefine $id 0 ;#0 to ensure non-quiet -(msg on stderr) |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
1 |
||||
] |
||||
} |
||||
tcltest::cleanupTests ;#needed to produce test summary line. |
||||
@ -0,0 +1,197 @@
|
||||
package require tcltest |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
variable common { |
||||
set result "" |
||||
} |
||||
|
||||
|
||||
test choices_typeignored_when_choice_in_list {Test that -type is not validated for a value that matches a choice}\ |
||||
-setup $common -body { |
||||
#1 abbreviated choice |
||||
set argd [punk::args::parse {li} withdef @values {frametype -type dict -choices {heavy light arc}}] |
||||
lappend result [dict get $argd values] |
||||
|
||||
#2 exact match for a choice |
||||
set argd [punk::args::parse {light} withdef @values {frametype -type dict -choices {heavy light arc}}] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{frametype light}\ |
||||
{frametype light}\ |
||||
] |
||||
|
||||
test choices_type_validation_choicerestricted1 {Test that -type is validated for value outside of choicelist based on -choicerestricted}\ |
||||
-setup $common -body { |
||||
|
||||
set argd [punk::args::parse {11} withdef @values {frametype -type int -choicerestricted 0 -choices {heavy light arc}}] |
||||
lappend result [dict get $argd values] |
||||
|
||||
if {[catch { |
||||
punk::args::parse {z} withdef @values {frametype -type int -choicerestricted 0 -choices {heavy light arc}} |
||||
}]} { |
||||
lappend result "ok_got_expected_error1" |
||||
} else { |
||||
lappend result "missing_required_error_when_type_mismatch_for_choice_outside_list" |
||||
} |
||||
|
||||
#when -choicerestricted - value matching -type still shouldn't pass |
||||
if {[catch { |
||||
set argd [punk::args::parse {11} withdef @values {frametype -type int -choicerestricted 1 -choices {heavy light arc}}] |
||||
}]} { |
||||
lappend result "ok_got_expected_error2" |
||||
} else { |
||||
lappend result "missing_required_error_when_choicerestricted_and_choice_outside_list" |
||||
} |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{frametype 11}\ |
||||
ok_got_expected_error1\ |
||||
ok_got_expected_error2\ |
||||
] |
||||
|
||||
test choices_type_validation_choicerestricted2 {Test that -type dict is validated for value outside of choicelist based on -choicerestricted}\ |
||||
-setup $common -body { |
||||
#same as choices_type_validation_choicrestricted1 - but with a more complex type 'dict' - tests list protection is correct |
||||
set argd [punk::args::parse {{hl -}} withdef @values {frametype -type dict -choicerestricted 0 -choices {heavy light arc}}] |
||||
lappend result [dict get $argd values] |
||||
|
||||
if {[catch { |
||||
punk::args::parse {z} withdef @values {frametype -type dict -choicerestricted 0 -choices {heavy light arc}} |
||||
}]} { |
||||
lappend result "ok_got_expected_error1" |
||||
} else { |
||||
lappend result "missing_required_error_when_type_mismatch_for_choice_outside_list" |
||||
} |
||||
|
||||
#when -choicerestricted - value matching -type dict still shouldn't pass |
||||
if {[catch { |
||||
set argd [punk::args::parse {{hl -}} withdef @values {frametype -type dict -choicerestricted 1 -choices {heavy light arc}}] |
||||
}]} { |
||||
lappend result "ok_got_expected_error2" |
||||
} else { |
||||
lappend result "missing_required_error_when_choicerestricted_and_choice_outside_list" |
||||
} |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{frametype {hl -}}\ |
||||
ok_got_expected_error1\ |
||||
ok_got_expected_error2\ |
||||
] |
||||
|
||||
test choice_multiple_with_choiceprefix {test -choices with both -multiple and -choiceprefix}\ |
||||
-setup $common -body { |
||||
#test with full value choices. |
||||
set argd [punk::args::parse {license description} withdef @values {topic -choices {license contributors description} -choiceprefix 1 -multiple 1 }] |
||||
lappend result [dict get $argd values] |
||||
|
||||
#test with prefixes of choice. |
||||
set argd [punk::args::parse {lic desc} withdef @values {topic -choices {license contributors description} -choiceprefix 1 -multiple 1 }] |
||||
lappend result [dict get $argd values] |
||||
|
||||
#test with mixes of full value and prefix of choice. |
||||
set argd [punk::args::parse {license desc} withdef @values {topic -choices {license contributors description} -choiceprefix 1 -multiple 1 }] |
||||
lappend result [dict get $argd values] |
||||
|
||||
set argd [punk::args::parse {desc license} withdef @values {topic -choices {license contributors description} -choiceprefix 1 -multiple 1 }] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{topic {license description}}\ |
||||
{topic {license description}}\ |
||||
{topic {license description}}\ |
||||
{topic {description license}}\ |
||||
] |
||||
#todo -nocase tests |
||||
|
||||
test choice_multiple_multiple {test -choices with both -multiple and -choicemultiple}\ |
||||
-setup $common -body { |
||||
set argd [punk::args::parse {a {c a} {a b c}} withdef @values {X -type string -choices {aa bb cc} -multiple 1 -choicemultiple {1 3} -optional 1}] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{X {aa {cc aa} {aa bb cc}}} |
||||
] |
||||
# -choicemultiple allows duplicates in result by default (default for -choicemultipleunique 0) |
||||
|
||||
test choicemultiple_list {test -choices with both -multiple and -choicemultiple}\ |
||||
-setup $common -body { |
||||
set argd [punk::args::parse {{read write w}} withdef @values {mode -type list -choices {read write} -choicemultiple {1 -1}}] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{mode {read write write}} |
||||
] |
||||
|
||||
test choice_multielement_clause {test -choice with a clause-length greater than 1}\ |
||||
-setup $common -body { |
||||
#The same -choices list always applies to each member of -type - which isn't always ideal for a multi-element clause |
||||
#for a clause where each element has a different choiceset - we would need to introduce a more complex -typechoices option |
||||
#(or use a -parsekey mechanism on leaders/values to group them) |
||||
|
||||
#test all combinations of prefix and complete for 2 entries |
||||
set argd [punk::args::parse {light heavy} withdef @values {leftright -type {any any} -choices {light heavy} -choicerestricted 1}] |
||||
lappend result [dict get $argd values] |
||||
set argd [punk::args::parse {li heavy} withdef @values {leftright -type {any any} -choices {light heavy} -choicerestricted 1}] |
||||
lappend result [dict get $argd values] |
||||
set argd [punk::args::parse {li he} withdef @values {leftright -type {any any} -choices {light heavy} -choicerestricted 1}] |
||||
lappend result [dict get $argd values] |
||||
set argd [punk::args::parse {light he} withdef @values {leftright -type {any any} -choices {light heavy} -choicerestricted 1}] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{leftright {light heavy}}\ |
||||
{leftright {light heavy}}\ |
||||
{leftright {light heavy}}\ |
||||
{leftright {light heavy}}\ |
||||
] |
||||
|
||||
test choice_multielement_clause_unrestricted {test -choice with a clause-length greater than 1 and values outside of choicelist}\ |
||||
-setup $common -body { |
||||
#1 both values outside of -choices |
||||
set argd [punk::args::parse {11 x} withdef @values {leftright -type {int char} -choices {light heavy arc} -choicerestricted 0}] |
||||
lappend result [dict get $argd values] |
||||
# |
||||
set argd [punk::args::parse {11 arc} withdef @values {leftright -type {int char} -choices {light heavy arc} -choicerestricted 0}] |
||||
lappend result [dict get $argd values] |
||||
# |
||||
set argd [punk::args::parse {11 a} withdef @values {leftright -type {int char} -choices {light heavy arc} -choicerestricted 0}] |
||||
lappend result [dict get $argd values] |
||||
# |
||||
set argd [punk::args::parse {heavy x} withdef @values {leftright -type {int char} -choices {light heavy arc} -choicerestricted 0}] |
||||
lappend result [dict get $argd values] |
||||
# |
||||
set argd [punk::args::parse {h x} withdef @values {leftright -type {int char} -choices {light heavy arc} -choicerestricted 0}] |
||||
lappend result [dict get $argd values] |
||||
# |
||||
set argd [punk::args::parse {a h} withdef @values {leftright -type {int char} -choices {light heavy arc} -choicerestricted 0}] |
||||
lappend result [dict get $argd values] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{leftright {11 x}}\ |
||||
{leftright {11 arc}}\ |
||||
{leftright {11 arc}}\ |
||||
{leftright {heavy x}}\ |
||||
{leftright {heavy x}}\ |
||||
{leftright {arc heavy}}\ |
||||
] |
||||
} |
||||
tcltest::cleanupTests ;#needed to produce test summary line. |
||||
|
||||
@ -0,0 +1,129 @@
|
||||
package require tcltest |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
variable common { |
||||
set result "" |
||||
} |
||||
|
||||
test define_tstr_template1 {Test basic tstr substitution finds vars in namespace in which define was called}\ |
||||
-setup $common -body { |
||||
namespace eval whatever { |
||||
set plus +++ |
||||
set minus --- |
||||
|
||||
punk::args::define { |
||||
@id -id ::testspace::test1 |
||||
@values |
||||
param -type string -default "${$plus}XXX${$minus}YYY" |
||||
} |
||||
} |
||||
|
||||
set argd [punk::args::parse {} withid ::testspace::test1] |
||||
set vals [dict get $argd values] |
||||
lappend result [dict get $vals param] |
||||
}\ |
||||
-cleanup { |
||||
namespace delete ::testspace::whatever |
||||
punk::args::undefine ::testspace::test1 |
||||
}\ |
||||
-result [list\ |
||||
+++XXX---YYY |
||||
] |
||||
|
||||
test define_tstr_template2 {Test basic tstr substitution when @dynamic}\ |
||||
-setup $common -body { |
||||
namespace eval whatever { |
||||
set plus +++ |
||||
set minus --- |
||||
|
||||
punk::args::define { |
||||
@dynamic |
||||
@id -id ::testspace::test2 |
||||
@values |
||||
param -type string -default "${$plus}XXX${$minus}YYY" |
||||
} |
||||
} |
||||
|
||||
set argd [punk::args::parse {} withid ::testspace::test2] |
||||
puts ">>>>define_tstr_template2 argd:$argd" |
||||
set vals [dict get $argd values] |
||||
lappend result [dict get $vals param] |
||||
}\ |
||||
-cleanup { |
||||
namespace delete ::testspace::whatever |
||||
punk::args::undefine ::testspace::test2 |
||||
}\ |
||||
-result [list\ |
||||
+++XXX---YYY |
||||
] |
||||
|
||||
test define_tstr_template3 {Test double tstr substitution when @dynamic}\ |
||||
-setup $common -body { |
||||
variable test_list |
||||
set test_list {A B C} |
||||
proc ::testspace::get_list {} { |
||||
variable test_list |
||||
return $test_list |
||||
} |
||||
namespace eval whatever { |
||||
set plus +++ |
||||
set minus --- |
||||
set DYN_LIST {${[::testspace::get_list]}} |
||||
set DYN_CLOCKSECONDS {${[clock seconds]}} |
||||
|
||||
punk::args::define { |
||||
@dynamic |
||||
@id -id ::testspace::test2 |
||||
@values |
||||
param1 -type string -default "${$plus}XXX${$minus}YYY" |
||||
param2 -type list -default "${$DYN_LIST}" |
||||
param3 -type string -default "${[clock seconds]}" |
||||
param4 -type string -default "${$DYN_CLOCKSECONDS}" |
||||
} |
||||
} |
||||
|
||||
set argd [punk::args::parse {} withid ::testspace::test2] |
||||
set vals [dict get $argd values] |
||||
lappend result [dict get $vals param1] |
||||
lappend result [dict get $vals param2] |
||||
set c1_at_define [dict get $vals param3] |
||||
set c1_at_resolve [dict get $vals param4] |
||||
|
||||
#update test_list to ensure parse is actually dynamic |
||||
set ::testspace::test_list {X Y Z} |
||||
#update plus - should not affect output as it is resolved at define time |
||||
set ::testspace::whatever::plus "new+" |
||||
#unset minus - should not cause error |
||||
unset ::testspace::whatever::minus |
||||
after 1100 ;#ensure more than 1 sec apart |
||||
|
||||
|
||||
set argd [punk::args::parse {} withid ::testspace::test2] |
||||
set vals [dict get $argd values] |
||||
lappend result [dict get $vals param1] |
||||
lappend result [dict get $vals param2] |
||||
set c2_at_define [dict get $vals param3] |
||||
set c2_at_resolve [dict get $vals param4] |
||||
|
||||
if {$c1_at_define == $c2_at_define} { |
||||
lappend result "OK_define_time_var_match" |
||||
} else { |
||||
lappend result "UNEXPECTED_define_time_var_mismatch" |
||||
} |
||||
if {$c1_at_resolve < $c2_at_resolve} { |
||||
lappend result "OK_resolve_time_2_greater" |
||||
} else { |
||||
lappend result "UNEXPECTED_resolve_time_2_not_greater" |
||||
} |
||||
|
||||
}\ |
||||
-cleanup { |
||||
namespace delete ::testspace::whatever |
||||
punk::args::undefine ::testspace::test2 |
||||
}\ |
||||
-result [list\ |
||||
+++XXX---YYY {A B C} +++XXX---YYY {X Y Z} OK_define_time_var_match OK_resolve_time_2_greater |
||||
] |
||||
} |
||||
tcltest::cleanupTests ;#needed to produce test summary line. |
||||
@ -0,0 +1,226 @@
|
||||
|
||||
package require tcltest |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
variable common { |
||||
set result "" |
||||
} |
||||
|
||||
#test mash opts aka "option clustering" aka "flag stacking" aka "option combining" aka "short flag bundling" etc. |
||||
|
||||
test mashopts_default {Test basic combining of short options when -mash set as default for short flags on @opts directive}\ |
||||
-setup $common -body { |
||||
|
||||
#first test they work individually as normal |
||||
set argd [punk::args::parse {-a -b -c} withdef {@opts -mash 1} {-a -type none} {-b -type none} {-c -type none}] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
#test all combined |
||||
set argd [punk::args::parse {-abc} withdef {@opts -mash 1} {-a -type none} {-b -type none} {-c -type none}] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
#varying order of flags in mash should still work |
||||
set argd [punk::args::parse {-cab} withdef {@opts -mash 1} {-a -type none} {-b -type none} {-c -type none}] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
#repeating flags in mash should still work and be treated as if they were repeated separately (ie -aa should be treated as if it were -a -a) |
||||
#in this case we have not configured any of the flags to be multiple, so the second occurrence of each flag should just override the first occurrence and have no effect |
||||
set argd [punk::args::parse {-caba} withdef {@opts -mash 1} {-a -type none} {-b -type none} {-c -type none}] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
#order of flags in the result should be the same as the order of flags in the definition of the optionset, |
||||
#not the order in which they were supplied in the mash - this is because we want the result to be deterministic and not depend on the order in which the user happened to combine the flags in the mash |
||||
#the actual order should be reflected in the received list. |
||||
set argd [punk::args::parse {-caba} withdef {@opts -mash 1} {-c -type none} {-a -type none} {-b -type none}] |
||||
lappend result [dict get $argd opts] |
||||
#the received list should show the repeated -a even though it's not set for multiple. |
||||
lappend result [dict get $argd received] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{-a 1 -b 1 -c 1}\ |
||||
{-a 1 -b 1 -c 1}\ |
||||
{-a 1 -b 1 -c 1}\ |
||||
{-a 1 -b 1 -c 1}\ |
||||
{-c 1 -a 1 -b 1}\ |
||||
{-c 0 -a 1 -b 2 -a 3}\ |
||||
] |
||||
|
||||
test mashopts_default_with_multiple {Test combining of short options when -mash set as default for short flags on @opts directive and a flag is set to -multiple}\ |
||||
-setup $common -body { |
||||
|
||||
#first test they work individually as normal |
||||
set argd [punk::args::parse {-a -b -c} withdef {@opts -mash 1} {-a -type none} {-b -type none -multiple true} {-c -type none}] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
set argd [punk::args::parse {-cba} withdef {@opts -mash 1} {-a -type none} {-b -type none -multiple true} {-c -type none}] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
#test a repeated flag within the mash |
||||
set argd [punk::args::parse {-cbba} withdef {@opts -mash 1} {-a -type none} {-b -type none -multiple true} {-c -type none}] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
#test a repeated flag after the mash |
||||
set argd [punk::args::parse {-cba -b} withdef {@opts -mash 1} {-a -type none} {-b -type none -multiple true} {-c -type none}] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
#test a repeated flag before the mash |
||||
set argd [punk::args::parse {-b -cba} withdef {@opts -mash 1} {-a -type none} {-b -type none -multiple true} {-c -type none}] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
#test a repeated flag before and after the mash |
||||
set argd [punk::args::parse {-b -cba -b} withdef {@opts -mash 1} {-a -type none} {-b -type none -multiple true} {-c -type none}] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
#test a repeated flag before, within and after the mash |
||||
set argd [punk::args::parse {-b -cbab -b} withdef {@opts -mash 1} {-a -type none} {-b -type none -multiple true} {-c -type none}] |
||||
lappend result [dict get $argd opts] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{-a 1 -b 1 -c 1}\ |
||||
{-a 1 -b 1 -c 1}\ |
||||
{-a 1 -b {1 1} -c 1}\ |
||||
{-a 1 -b {1 1} -c 1}\ |
||||
{-a 1 -b {1 1} -c 1}\ |
||||
{-a 1 -b {1 1 1} -c 1}\ |
||||
{-a 1 -b {1 1 1 1} -c 1}\ |
||||
] |
||||
|
||||
test mashopts_default_with_typed_shortflag {Test combining of short options when -mash set as default for short flags on @opts directive and a shortopt accepts a value}\ |
||||
-setup $common -body { |
||||
|
||||
#test individually |
||||
set argd [punk::args::parse {-a -b -f fff -c} withdef {@opts -mash 1} {-a -type none} {-b -type none} {-c -type none} {-f -type string}] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
#test with mash - the flag that accepts a value must be at the end of the mash. |
||||
set argd [punk::args::parse {-bacf fff} withdef {@opts -mash 1} {-a -type none} {-b -type none} {-c -type none} {-f -type string}] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
#should error if the flag that accepts a value is not at the end of the mash, because that would be ambiguous - we would not know which flag the value belongs to |
||||
if {[catch {punk::args::parse {-bafc fff} withdef {@opts -mash 1} {-a -type none} {-b -type none} {-c -type none} {-f -type string}} err]} { |
||||
lappend result "expected-error" |
||||
} else { |
||||
lappend result "missing-expected-error" |
||||
} |
||||
|
||||
#failing to provide a value for -f should raise an error. |
||||
if {[catch {punk::args::parse {-bacf} withdef {@opts -mash 1} {-a -type none} {-b -type none} {-c -type none} {-f -type string}} err]} { |
||||
lappend result "expected-error" |
||||
} else { |
||||
lappend result "missing-expected-error" |
||||
} |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{-a 1 -b 1 -c 1 -f fff}\ |
||||
{-a 1 -b 1 -c 1 -f fff}\ |
||||
expected-error\ |
||||
expected-error\ |
||||
] |
||||
|
||||
test mashopts_default_with_other_flags {Test combining of short options when -mash set as default for short flags on @opts directive plus a longer value-accepting flag and a value}\ |
||||
-setup $common -body { |
||||
|
||||
#test individually |
||||
set argd [punk::args::parse {-a -b -f fff -c -cabinet ccc ttt} withdef {@opts -mash 1} {-a -type none} {-b -type none} {-c -type none} {-f -type string} {-cabinet -type string} @values tail] |
||||
lappend result [dict get $argd opts] |
||||
lappend result [dict get $argd values] |
||||
|
||||
|
||||
#should error if the non-mash flag that accepts a value is supplied with a prefix shorter than the number of mash flags. |
||||
#(we don't calculate prefixes based on a possibly huge combination of mash flags, so we simply require prefixes for non-mash flags to be at least as long as the number of mash flags) |
||||
if {[catch {punk::args::parse {-bacf fff -cabi ccc ttt} withdef {@opts -mash 1} {-a -type none} {-b -type none} {-c -type none} {-f -type string} {-cabinet -type string} @values tail} err]} { |
||||
lappend result "expected-error" |
||||
} else { |
||||
lappend result "missing-expected-error" |
||||
} |
||||
|
||||
#we have 4 mash flags here, so a unique prefix of cabinet that is 5 long should be accepted. |
||||
set argd [punk::args::parse {-cabf fff -c -cabin ccc ttt} withdef {@opts -mash 1} {-a -type none} {-b -type none} {-c -type none} {-f -type string} {-cabinet -type string} @values {tail -multiple 1 -optional 1}] |
||||
lappend result [dict get $argd opts] |
||||
lappend result [dict get $argd values] |
||||
|
||||
#test it's not confused by a short prefix of cabinet that matches only mash flags. |
||||
#-cab should be processed as match flags - not a prefix of cabinet. |
||||
set argd [punk::args::parse {-cabf fff -c -cab ccc ttt} withdef {@opts -mash 1} {-a -type none} {-b -type none} {-c -type none} {-f -type string} {-cabinet -type string} @values {tail -multiple 1 -optional 1}] |
||||
lappend result [dict get $argd opts] |
||||
lappend result [dict get $argd values] |
||||
|
||||
|
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{-a 1 -b 1 -c 1 -f fff -cabinet ccc}\ |
||||
{tail ttt}\ |
||||
expected-error\ |
||||
{-a 1 -b 1 -c 1 -f fff -cabinet ccc}\ |
||||
{tail ttt}\ |
||||
{-a 1 -b 1 -c 1 -f fff}\ |
||||
{tail {ccc ttt}}\ |
||||
] |
||||
|
||||
test mashopts_mix_default_and_explicit {Test combining of short options when -mash set both on @opts and directly}\ |
||||
-setup $common -body { |
||||
|
||||
#-c no longer allowed in mash |
||||
|
||||
set argd [punk::args::parse {-a -b -f fff -c -cabinet ccc ttt} withdef {@opts -mash 1} {-a -type none} {-b -type none} {-c -type none -mash 0} {-f -type string} {-cabinet -type string} @values tail] |
||||
lappend result [dict get $argd opts] |
||||
lappend result [dict get $argd values] |
||||
|
||||
|
||||
set argd [punk::args::parse {-abf fff -c -cabinet ccc ttt} withdef {@opts -mash 1} {-a -type none} {-b -type none} {-c -type none -mash 0} {-f -type string} {-cabinet -type string} @values tail] |
||||
lappend result [dict get $argd opts] |
||||
lappend result [dict get $argd values] |
||||
|
||||
#attempting to mash -c should raise an error. |
||||
if {[catch {punk::args::parse {-bacf fff -cabinet ccc ttt} withdef {@opts -mash 1} {-a -type none} {-b -type none} {-c -type none -mash 0} {-f -type string} {-cabinet -type string} @values tail} err]} { |
||||
lappend result "expected-error" |
||||
} else { |
||||
lappend result "missing-expected-error" |
||||
} |
||||
|
||||
#test with only explicit -mash 1 on individual flags. |
||||
set argd [punk::args::parse {-abf fff -c -cabinet ccc ttt} withdef @opts {-a -type none -mash 1} {-b -type none -mash 1} {-c -type none} {-f -type string -mash 1} {-cabinet -type string} @values tail] |
||||
lappend result [dict get $argd opts] |
||||
lappend result [dict get $argd values] |
||||
|
||||
#attempting to explicitly apply -mash 1 to -cabinet should raise an error because -cabinet is not a short flag and we only allow -mash 1 to be applied to short flags. |
||||
#(default -mash 1 on @opts is different as it is automatically only propagated to short flags.) |
||||
if {[catch {punk::args::parse {-acbf fff -cabinet ccc ttt} withdef @opts {-a -type none -mash 1} {-b -type none -mash 1} {-c -type none} {-f -type string -mash 1} {-cabinet -type string -mash 1} @values tail} err]} { |
||||
lappend result "expected-error" |
||||
} else { |
||||
lappend result "missing-expected-error" |
||||
} |
||||
|
||||
|
||||
#-c should default to not being mashable, so attempting to mash it should raise an error. |
||||
if {[catch {punk::args::parse {-acbf fff -cabinet ccc ttt} withdef @opts {-a -type none -mash 1} {-b -type none -mash 1} {-c -type none} {-f -type string -mash 1} {-cabinet -type string} @values tail} err]} { |
||||
lappend result "expected-error" |
||||
} else { |
||||
lappend result "missing-expected-error" |
||||
} |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{-a 1 -b 1 -c 1 -f fff -cabinet ccc}\ |
||||
{tail ttt}\ |
||||
{-a 1 -b 1 -c 1 -f fff -cabinet ccc}\ |
||||
{tail ttt}\ |
||||
expected-error\ |
||||
{-a 1 -b 1 -c 1 -f fff -cabinet ccc}\ |
||||
{tail ttt}\ |
||||
expected-error\ |
||||
expected-error\ |
||||
] |
||||
|
||||
} |
||||
tcltest::cleanupTests ;#needed to produce test summary line. |
||||
@ -0,0 +1,77 @@
|
||||
|
||||
package require tcltest |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
variable common { |
||||
set result "" |
||||
} |
||||
|
||||
|
||||
test opts_longoptvalue {Test -alt|--longopt= can accept value as longopt}\ |
||||
-setup $common -body { |
||||
set argd [punk::args::parse {--filename=abc} withdef @opts {-f|--filename= -default spud -type string}] |
||||
lappend result [dict get $argd opts];#name by default should be last flag alternative (stripped of =) ie "--filename" |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{--filename abc}\ |
||||
] |
||||
|
||||
test opts_longoptvalue_alternative {Test -alt|--longopt= can accept value as spaced argument to given alternative}\ |
||||
-setup $common -body { |
||||
#test full name of alt flag |
||||
set argd [punk::args::parse {-fx xyz} withdef @opts {-fx|--filename= -default spud -type string}] |
||||
lappend result [dict get $argd opts] ;#name by default should be last flag alternative (stripped of =) ie "--filename" |
||||
#test prefixed version of flag |
||||
set argd [punk::args::parse {-f xyz} withdef @opts {-fx|--filename= -default spud -type string}] |
||||
lappend result [dict get $argd opts] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{--filename xyz}\ |
||||
{--filename xyz}\ |
||||
] |
||||
|
||||
test opts_longoptvalue_alternative_noninterference {Test -alt|--longopt= can accept longopt values as normal }\ |
||||
-setup $common -body { |
||||
#test full name of longopt |
||||
set argd [punk::args::parse {--filename=xyz} withdef @opts {-fx|--filename= -default spud -type string}] |
||||
lappend result [dict get $argd opts] ;#name by default should be last flag alternative (stripped of =) ie "--filename" |
||||
#test prefixed version of longopt |
||||
set argd [punk::args::parse {--file=xyz} withdef @opts {-fx|--filename= -default spud -type string}] |
||||
lappend result [dict get $argd opts] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{--filename xyz}\ |
||||
{--filename xyz}\ |
||||
] |
||||
|
||||
test opts_longoptvalue_choice {Test --longopt= works wiith -choices}\ |
||||
-setup $common -body { |
||||
#prefixed choice with and without prefixed flagname |
||||
set argd [punk::args::parse {--filename=x} withdef @opts {--filename= -default spud -type string -choices {abc xyz}}] |
||||
lappend result [dict get $argd opts] |
||||
set argd [punk::args::parse {--file=x} withdef @opts {--filename= -default spud -type string -choices {abc xyz}}] |
||||
lappend result [dict get $argd opts] |
||||
#unprefixed choice with and without prefixed flagname |
||||
set argd [punk::args::parse {--filename=xyz} withdef @opts {--filename= -default spud -type string -choices {abc xyz}}] |
||||
lappend result [dict get $argd opts] |
||||
set argd [punk::args::parse {--file=xyz} withdef @opts {--filename= -default spud -type string -choices {abc xyz}}] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{--filename xyz}\ |
||||
{--filename xyz}\ |
||||
{--filename xyz}\ |
||||
{--filename xyz}\ |
||||
] |
||||
} |
||||
tcltest::cleanupTests ;#needed to produce test summary line. |
||||
@ -0,0 +1,180 @@
|
||||
|
||||
package require tcltest |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
variable common { |
||||
set result "" |
||||
} |
||||
|
||||
|
||||
test parsekey_repeat_ordering {Ensure last flag has precedence}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
#It must always be possible to override earlier (non -multiple) options |
||||
set argd [punk::args::parse {-incr -decr -incr} withdef {@opts -type none -parsekey -direction} {-incr -typedefaults u} {-decr -typedefaults u}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd opts] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{-direction u} |
||||
] |
||||
|
||||
|
||||
test parsekey_opt_result_key_and_received {Ensure -parsekey is used as opt result and received key}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
|
||||
set argd [punk::args::parse {-f example.txt} withdef @opts {-f|--file -parsekey -path -type string}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd opts] |
||||
lappend result [dict exists [dict get $argd received] -path] |
||||
lappend result [dict exists [dict get $argd received] --file] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{-path example.txt} 1 0 |
||||
] |
||||
|
||||
|
||||
test parsekey_opt_default_result_key {Ensure defaults are returned under -parsekey}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
|
||||
set argd [punk::args::parse {} withdef @opts {-f|--file -parsekey -path -type string -default example.txt}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd opts] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{-path example.txt} |
||||
] |
||||
|
||||
|
||||
test parsekey_opt_shared_value_key {Ensure different opts can share a -parsekey while keeping their own validation}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
|
||||
set argd [punk::args::parse {-xy {1 2}} withdef @opts {-xy -parsekey -coord -type list -minsize 2 -maxsize 2} {-xyz -parsekey -coord -type list -minsize 3 -maxsize 3}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
set argd [punk::args::parse {-xyz {1 2 3}} withdef @opts {-xy -parsekey -coord -type list -minsize 2 -maxsize 2} {-xyz -parsekey -coord -type list -minsize 3 -maxsize 3}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd opts] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{-coord {1 2}}\ |
||||
{-coord {1 2 3}} |
||||
] |
||||
|
||||
|
||||
test parsekey_opt_solo_multiple_tracking {Ensure -parsekey is used for solo and multiple tracking}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
|
||||
set argd [punk::args::parse {-v -v} withdef @opts {-v -parsekey -verbose -type none -multiple 1}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd opts] |
||||
lappend result [dict get $argd solos] |
||||
lappend result [dict get $argd multis] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{-verbose {1 1}}\ |
||||
{-verbose -verbose}\ |
||||
{-verbose} |
||||
] |
||||
|
||||
|
||||
|
||||
test parsekey_required_missing_reports_parsekey {Ensure missing required opts report the -parsekey}\ |
||||
-setup $common -body { |
||||
set docid ::testspace::parsekey_required_missing_reports_parsekey |
||||
set docids [list $docid] |
||||
|
||||
punk::args::define [list @id -id $docid] @opts {-xy -parsekey -coord -type list -minsize 2 -maxsize 2 -optional 0} {-xyz -parsekey -coord -type list -minsize 3 -maxsize 3} |
||||
set err [catch {punk::args::parse {} withid $docid} msg opts] |
||||
lappend result $err |
||||
lappend result [lrange [dict get $opts -errorcode] 0 2] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
1\ |
||||
{PUNKARGS VALIDATION {optionmissing -coord received {}}} |
||||
] |
||||
|
||||
|
||||
|
||||
test parsekey_required_shared_key_satisfied_by_other_opt {Ensure any opt with the same -parsekey satisfies the required key}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
|
||||
set argd [punk::args::parse {-xyz {1 2 3}} withdef @opts {-xy -parsekey -coord -type list -minsize 2 -maxsize 2 -optional 0} {-xyz -parsekey -coord -type list -minsize 3 -maxsize 3}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd opts] |
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{-coord {1 2 3}} |
||||
] |
||||
|
||||
|
||||
test parsekey_required {test behaviour of -parsekey with -required true}\ |
||||
-setup $common -body { |
||||
set docids [list] |
||||
|
||||
#first test with no -optional flags so -coords is optional |
||||
set argd [punk::args::parse {-xy {1 2}} withdef @opts {-xy -parsekey -coord -type list -minsize 2 -maxsize 2} {-xyz -parsekey -coord -type list -minsize 3 -maxsize 3}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
#the -optional 0 in the presence of -parsekey means -coord is required (not that the specific arg -xy is required) - so this should work because -xy is present and has a -parsekey of -coord |
||||
set argd [punk::args::parse {-xy {1 2}} withdef @opts {-xy -parsekey -coord -type list -minsize 2 -maxsize 2 -optional 0} {-xyz -parsekey -coord -type list -minsize 3 -maxsize 3}] |
||||
lappend docids [dict get $argd id] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
|
||||
|
||||
}\ |
||||
-cleanup { |
||||
foreach id $docids { |
||||
punk::args::undefine $id 1 |
||||
} |
||||
}\ |
||||
-result [list\ |
||||
{-coord {1 2}}\ |
||||
{-coord {1 2}} |
||||
] |
||||
} |
||||
tcltest::cleanupTests ;#needed to produce test summary line. |
||||
@ -0,0 +1,184 @@
|
||||
|
||||
package require tcltest |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
|
||||
package require punk::args |
||||
package require punk::ansi |
||||
package require punk::ns |
||||
|
||||
|
||||
namespace import ::punk::ansi::a+ ::punk::ansi::a |
||||
variable common { |
||||
set result "" |
||||
} |
||||
test synopsis_basic {test basic synopsis of punkargs definition}\ |
||||
-setup $common -body { |
||||
#no @cmd -summary |
||||
#we still expect and require a leading line "# " in the synopsis |
||||
namespace eval testns { |
||||
punk::args::define { |
||||
@id -id ::testspace::testns::t1 |
||||
@leaders |
||||
a1 -optional 0 |
||||
@opts |
||||
-o1 -type boolean |
||||
@values |
||||
v1 -optional 1 |
||||
} |
||||
} |
||||
set syntext [punk::ns::synopsis ::testspace::testns::t1] |
||||
set remlines [list] |
||||
foreach ln [split $syntext \n] { |
||||
if {[string match "##*" $ln]} {continue} |
||||
lappend remlines $ln |
||||
} |
||||
lappend result [join $remlines \n] |
||||
}\ |
||||
-cleanup { |
||||
namespace delete ::testspace::testns |
||||
}\ |
||||
-result [list\ |
||||
"# \n::testspace::testns::t1 [a+ italic]a1[a+ noitalic] \[-o1 [a+ italic]<bool>[a+ noitalic]\] \[[a+ italic]v1[a+ noitalic]\]" |
||||
] |
||||
|
||||
test synopsis_basic_ensemble-like {test basic synopsis of punkargs ensemble-like definition}\ |
||||
-setup $common -body { |
||||
namespace eval testns { |
||||
punk::args::define { |
||||
@id -id ::testspace::testns::t1 |
||||
@cmd -summary "summary" |
||||
@leaders |
||||
subcmd -default c1 -choices {c1 c2} |
||||
@values -min 0 -max 0 |
||||
} |
||||
punk::args::define { |
||||
@id -id "::testspace::testns::t1 c1" |
||||
@cmd -summary "summary" |
||||
@values -min 0 -max 1 |
||||
v1 -type string |
||||
} |
||||
|
||||
} |
||||
#strip out the secondary form lines starting with ##. |
||||
#lappend result [punk::ansi::grepstr -return matched {^(?!##)} [punk::ns::synopsis ::testspace::testns::t1]] |
||||
lappend result [punk::ansi::grepstr -h + -return matched -v {^##} [punk::ns::synopsis ::testspace::testns::t1]] |
||||
#lappend result [grepstr -return matched {^(?!##)} [punk::ns::synopsis ::testspace::testns::t1 c1]] |
||||
lappend result [punk::ansi::grepstr -h + -return matched -v {^##} [punk::ns::synopsis ::testspace::testns::t1 c1]] |
||||
}\ |
||||
-cleanup { |
||||
namespace delete ::testspace::testns |
||||
}\ |
||||
-result [list\ |
||||
"# summary\n::testspace::testns::t1 \[[a+ italic]subcmd[a+ noitalic]\]"\ |
||||
"# summary\n::testspace::testns::t1 c1 [a+ italic]v1[a+ noitalic]" |
||||
] |
||||
|
||||
test synopsis_alias_longopt_requiredval {}\ |
||||
-setup $common -body { |
||||
namespace eval testns { |
||||
punk::args::define { |
||||
@id -id ::testspace::testns::t1 |
||||
@cmd -summary summary |
||||
--verbose= -type int -default unreceived |
||||
} |
||||
} |
||||
#lappend result [grepstr -return matched {^(?!##)} [punk::ns::synopsis ::testspace::testns::t1]] |
||||
lappend result [punk::ansi::grepstr -h + -return matched -v {^##} [punk::ns::synopsis ::testspace::testns::t1]] |
||||
|
||||
#test that missing flag uses -default value |
||||
set argd [punk::args::parse {} withid ::testspace::testns::t1] |
||||
lappend result [dict get $argd opts] |
||||
#test prefix version of longopt accepts supplied int |
||||
set argd [punk::args::parse {--v=33} withid ::testspace::testns::t1] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
if {[catch { |
||||
set argd [punk::args::parse {--v=} withid ::testspace::testns::t1] |
||||
} eMsg eOpts]} { |
||||
lappend result "expected-error1" |
||||
} else { |
||||
lappend result "missing-required-error1" |
||||
} |
||||
|
||||
if {[catch { |
||||
set argd [punk::args::parse {--v} withid ::testspace::testns::t1] |
||||
} eMsg eOpts]} { |
||||
lappend result "expected-error2" |
||||
} else { |
||||
lappend result "missing-required-error2" |
||||
} |
||||
|
||||
|
||||
}\ |
||||
-cleanup { |
||||
namespace delete ::testspace::testns |
||||
}\ |
||||
-result [list\ |
||||
"# summary\n::testspace::testns::t1 \[--verbose=[a+ italic]<int>[a+ noitalic]\]"\ |
||||
{--verbose unreceived}\ |
||||
{--verbose 33}\ |
||||
expected-error1\ |
||||
expected-error2 |
||||
] |
||||
|
||||
test synopsis_alias_longopt_optionalval {}\ |
||||
-setup $common -body { |
||||
namespace eval testns { |
||||
punk::args::define { |
||||
@id -id ::testspace::testns::t1 |
||||
@cmd -summary summary |
||||
--verbose= -type ?int? -default unreceived -typedefaults received |
||||
} |
||||
} |
||||
|
||||
#test relies heavily on overtype::renderline behaviour within call to grepstr -h + to return the original lines with ANSI intact, |
||||
#We should ensure that overtype tests also reflect this required behaviour. |
||||
|
||||
#this test is also very specific about the required ANSI such as italics for the synopsis. |
||||
#italics differentiate literal strings from variable names and types and are also applied to nested optional values to make the synopsis easier to read. |
||||
#We should ensure that the synopsis tests also reflect this required behaviour. |
||||
#todo - consider referencing some of these tests from the code where it's implemented. |
||||
|
||||
#lappend result [grepstr -return matched {^(?!##)} [punk::ns::synopsis ::testspace::testns::t1]] |
||||
|
||||
#use -highlight + with negated match to return the original lines with ANSI intact |
||||
lappend result [punk::ansi::grepstr -h + -return matched -v {^##} [punk::ns::synopsis ::testspace::testns::t1]] |
||||
|
||||
#test that missing flag uses -default value |
||||
set argd [punk::args::parse {} withid ::testspace::testns::t1] |
||||
lappend result [dict get $argd opts] |
||||
#test prefix version of longopt accepts supplied int |
||||
set argd [punk::args::parse {--v=33} withid ::testspace::testns::t1] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
if {[catch { |
||||
set argd [punk::args::parse {--v=} withid ::testspace::testns::t1] |
||||
} eMsg eOpts]} { |
||||
#expect fail due to received empty string failing <int> |
||||
lappend result "expected-error1" |
||||
} else { |
||||
lappend result "missing-required-error1" |
||||
} |
||||
|
||||
#because the type is optional (?int?) - we expect the longopt to support solo operation. |
||||
#It should pick up the -typedefaults value as a default (not -default, which is for missing flag only) |
||||
set argd [punk::args::parse {--v} withid ::testspace::testns::t1] |
||||
lappend result [dict get $argd opts] |
||||
|
||||
}\ |
||||
-cleanup { |
||||
namespace delete ::testspace::testns |
||||
}\ |
||||
-result [list\ |
||||
"# summary\n::testspace::testns::t1 \[--verbose[a+ italic]\[[a+ noitalic]=[a+ italic]<int>\][a+ noitalic]\]"\ |
||||
{--verbose unreceived}\ |
||||
{--verbose 33}\ |
||||
expected-error1\ |
||||
{--verbose received} |
||||
] |
||||
} |
||||
tcltest::cleanupTests ;#needed to produce test summary line. |
||||
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
# src/tests/shell — Shell Behavior Tests |
||||
|
||||
## Purpose |
||||
|
||||
Tests for shell-level behavior, command-line execution, and stdin/stdout interaction that are not owned by a specific source module namespace. |
||||
|
||||
## Ownership |
||||
|
||||
- Agents may update this subtree when changing shell behavior, command execution, or interactive stream handling. |
||||
- Keep module API behavior tests in `src/tests/modules/` unless shell invocation is the behavior under test. |
||||
|
||||
## Local Contracts |
||||
|
||||
- Tests here may exercise process execution, stdin, stdout, stderr, and shell filtering behavior. |
||||
- Keep command inputs and expected outputs explicit so failures can be reproduced outside the aggregate runner. |
||||
- Avoid relying on installed packages or root-level build outputs unless the test explicitly targets a built executable. |
||||
|
||||
## Work Guidance |
||||
|
||||
- Use focused harness runs with `-include-paths shell/**` when editing this subtree. |
||||
- Include enough failure output to identify the launched command and stream comparison. |
||||
- Keep platform-sensitive assumptions visible in the test body. |
||||
|
||||
## Verification |
||||
|
||||
- `<tcl_interpreter> src/tests/runtests.tcl -include-paths shell/**` passes for changes in this subtree. |
||||
|
||||
## Child DOX Index |
||||
|
||||
@ -0,0 +1,34 @@
|
||||
package require tcltest |
||||
|
||||
package require punk::ns |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
variable common { |
||||
set result "" |
||||
} |
||||
|
||||
|
||||
test bench_clean {test using bench package clean result for: <executable> << exit}\ |
||||
-setup $common -body { |
||||
package require bench |
||||
|
||||
set thisexe [file normalize [info nameofexecutable]] |
||||
set tail [file tail $thisexe] |
||||
set location [file dirname $thisexe] |
||||
set located [bench::locate $tail $location] |
||||
if {$located ne ""} { |
||||
set located [file normalize $located] |
||||
} |
||||
|
||||
lappend result [string equal $located $thisexe] |
||||
|
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
1\ |
||||
] |
||||
|
||||
} |
||||
tcltest::cleanupTests ;#needed to produce test summary. |
||||
Loading…
Reference in new issue