Browse Source

fcat improvements, test framework work

master
Julian Noble 3 weeks ago
parent
commit
d264c284e4
  1. 2
      src/modules/AGENTS.md
  2. 6
      src/modules/natsort-999999.0a1.0.tm
  3. 8
      src/modules/punk-999999.0a1.0.tm
  4. 18
      src/modules/punk/args-999999.0a1.0.tm
  5. 18
      src/modules/punk/du-999999.0a1.0.tm
  6. 16
      src/modules/punk/lib-999999.0a1.0.tm
  7. 40
      src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/modules/template_module-0.0.4.tm
  8. 6
      src/modules/punk/mix/base-0.1.tm
  9. 6
      src/modules/punk/mix/cli-999999.0a1.0.tm
  10. 2
      src/modules/punk/mix/commandset/project-999999.0a1.0.tm
  11. 77
      src/modules/punk/mix/util-999999.0a1.0.tm
  12. 4
      src/modules/punk/mod-999999.0a1.0.tm
  13. 2
      src/modules/punk/path-999999.0a1.0.tm
  14. 3
      src/modules/punk/repl-999999.0a1.0.tm
  15. 6
      src/modules/punk/safe-999999.0a1.0.tm
  16. 441
      src/modules/punk/tcltestrun-999999.0a1.0.tm
  17. 3
      src/modules/punk/tcltestrun-buildversion.txt
  18. 10
      src/modules/punkcheck-999999.0a1.0.tm
  19. 2
      src/modules/shellrun-999999.0a1.0.tm
  20. 12
      src/modules/test/#modpod-overtype-999999.0a1.0/overtype-999999.0a1.0.tm

2
src/modules/AGENTS.md

@ -21,7 +21,7 @@ Source of truth for all editable Punk project modules. This is where agents shou
- Always declare dependencies explicitly using `package require <name>` near file tops.
- Prefer fully qualified namespaces when referencing external packages, such as `package require tcl::zlib` or `package require TclOO`.
- Organize custom modules as namespaces mirroring directory structure, such as `namespace eval punk::<modulename>` or deeper paths like `punk::lib::util::<somename>`.
- Use semantic versions that `package vcompare` can interpret; strip leading zeros.
- Use semantic versions that `package vcompare` can interpret; strip leading zeros according to tcl package version rules ie in each segment including either side of a or b.
- For optional features, probe with `if {[catch {package require foo}]} { ... }` and degrade gracefully.
- Vendormodules, bootsupport snapshots, VFS modules, and built output modules do not use the magic version scheme unless their own local docs explicitly say otherwise.

6
src/modules/natsort-999999.0a1.0.tm

@ -1659,13 +1659,13 @@ namespace eval natsort {
set allfiles [concat $allfiles $dotfiles $files]
} else {
#file (or link?)
set files [glob -nocomplain -directory [file dirname $item] -tail [file tail $item]]
set files [glob -nocomplain -directory [file dirname $item] -tail -- [file tail $item]]
set allfiles [concat $allfiles $files]
}
} else {
set folders [glob -nocomplain -directory $item -type {d} -tail [file tail $item]]
set folders [glob -nocomplain -directory $item -type {d} -tail -- [file tail $item]]
set allfolders [concat $allfolders $folders]
set files [glob -nocomplain -directory [file dirname $item] -tail [file tail $item]]
set files [glob -nocomplain -directory [file dirname $item] -tail -- [file tail $item]]
set allfiles [concat $allfiles $files]
}
}

8
src/modules/punk-999999.0a1.0.tm

@ -6618,7 +6618,7 @@ namespace eval punk {
}
} else {
#set executables [lsort -unique [glob -nocomplain -directory $p -types {f x} -tail {*}$binglobs]]
#set executables [lsort -unique [glob -nocomplain -directory $p -types {f x} -tail -- {*}$binglobs]]
set executables [lsort -unique [glob -nocomplain -directory $p -types {f x} -tail *]]
}
}
@ -8160,9 +8160,9 @@ namespace eval punk {
(pipeline data inserted at end of each |...> segment is passed as single item unless
inserted with an expanding insertion specifier such as .=>* )
e.g1:
.= list a b c |v1,/1-end,/0>\\
.=>* inspect -label i1 -- |>\\
.=v1> inspect -label i2 -- |>\\
.= list a b c |v1,/1..end,/0>\\
.=>* inspect -label i1 -- |>\\
.=v1> inspect -label i2 -- |>\\
string toupper
(3) i1: {a b c} {b c} a
(1) i2: a b c

18
src/modules/punk/args-999999.0a1.0.tm

@ -3688,14 +3688,20 @@ tcl::namespace::eval punk::args {
@id -id ::punk::args::update_definitions
@cmd -name punk::args::update_definitions\
-summary\
""\
"Update internal caches with new definitions from packages that have been loaded since the last scan."\
-help\
""
"Scans for new definitions in the specified namespaces and updates internal caches.
This is called automatically by punk::args::real_id when an id is not found -
to ensure that any definitions in newly loaded packages are available for retrieval.
It can also be called manually to pre-load definitions from specific namespaces
- e.g to prepare for a series of calls to punk::args::real_id where the namespace origin of the ids is"
@values -min 1
id
arglist -type list -help\
"list containing arguments to be parsed as per the
argument specification identified by the supplied id."
nslist -type list -help\
"list of namespaces to update definitions for.
If a package namespace is included that hasn't been scanned yet,
it will be scanned as part of this call. If the list contains the single element '*',
all registered namespaces will be updated.
(scanned if not already scanned, and any new loaded packages will be included)."
}]
#scanned_packages (list)
#namespace_docpackages (dict)

18
src/modules/punk/du-999999.0a1.0.tm

@ -1879,16 +1879,16 @@ namespace eval punk::du {
#if {![llength $types_entry] || "d" in $types_entry} {}
if {!$skip_dirs} {
#we need to check directories for hidden attribute - as on windows, not all dotfiles are hidden, and not all hidden files are dotfiles
set hdirs [glob -nocomplain -dir $folderpath -types {hidden d} {*}$globs]
set dirs [glob -nocomplain -dir $folderpath -types d {*}$globs]
set hdirs [glob -nocomplain -dir $folderpath -types {hidden d} -- {*}$globs]
set dirs [glob -nocomplain -dir $folderpath -types d -- {*}$globs]
}
if {!$skip_links} {
set hlinks [glob -nocomplain -dir $folderpath -types {hidden l} {*}$globs]
set links [glob -nocomplain -dir $folderpath -types l {*}$globs] ;#links may have dupes - we don't care. punk::lib::struct_set_diff_unique
set hlinks [glob -nocomplain -dir $folderpath -types {hidden l} -- {*}$globs]
set links [glob -nocomplain -dir $folderpath -types l -- {*}$globs] ;#links may have dupes - we don't care. punk::lib::struct_set_diff_unique
}
if {!$skip_files} {
set hfiles [glob -nocomplain -dir $folderpath -types {hidden f} {*}$globs]
set files [glob -nocomplain -dir $folderpath -types f {*}$globs]
set hfiles [glob -nocomplain -dir $folderpath -types {hidden f} -- {*}$globs]
set files [glob -nocomplain -dir $folderpath -types f -- {*}$globs]
}
} else {
if {$opt_glob eq "*"} {
@ -1901,13 +1901,13 @@ namespace eval punk::du {
#however - these are still significantly slower than a single glob with no restrictions - so we want to minimize the number of globs we do
#- but we also want to avoid doing extra work classifying entries that we will end up ignoring based on types/attributes/permissions filters.
if {!$skip_dirs} {
set dirs [glob -nocomplain -dir $folderpath -types d {*}$globs]
set dirs [glob -nocomplain -dir $folderpath -types d -- {*}$globs]
}
if {!$skip_links} {
set links [glob -nocomplain -dir $folderpath -types l {*}$globs] ;#links may have dupes - we don't care. punk::lib::struct_set_diff_unique
set links [glob -nocomplain -dir $folderpath -types l -- {*}$globs] ;#links may have dupes - we don't care. punk::lib::struct_set_diff_unique
}
if {!$skip_files} {
set files [glob -nocomplain -dir $folderpath -types f {*}$globs]
set files [glob -nocomplain -dir $folderpath -types f -- {*}$globs]
}
}

16
src/modules/punk/lib-999999.0a1.0.tm

@ -808,7 +808,7 @@ namespace eval punk::lib {
set argd [punk::args::parse $args withid ::punk::lib::tempdir_newfolder]
set opt_dir [dict get $argd opts -dir]
set opt_prefix [dict get $argd opts -prefix]
puts "opt_prefix: $opt_prefix"
#puts "opt_prefix: $opt_prefix"
if {[llength [file split $opt_prefix]] > 1} {
error "punk::lib::tempdir_newfolder -prefix option should not contain any path separators"
}
@ -962,10 +962,13 @@ namespace eval punk::lib {
return [join $newparts .]
}
proc tm_version_required_canonical {versionspec} {
#also trim leading zero from any dottedpart?
#Tcl *allows* leading zeros in any of the dotted parts - but they are not significant.
#e.g 1.01 is equivalent to 1.1 and 01.001
#also 1b3 == 1b0003
#Tcl *allows* leading zeros in any of the dotted parts, including either side of a or b - but they are not significant.
#an a or b must have a digit either side to be valid.
#e.g 1.1 is canonical and equivalent to 1.01 and 01.001
#e.g 1a1 is canonical and equivalent to 1a01 and 01a001
#e.g 1b3 is canonical and equivalent to 1b0003
#e.g 1.0a3 is canonical and equivalent to 1.0a0003 and 01.00a0003
#e.g 1.10b0 is canonical and equivalent to 1.010b000 and 01.10b000
if {[string trim $versionspec] eq ""} {return ""} ;#unspecified = any version
set errmsg "tm_version_required_canonical - invalid version specification"
@ -1036,7 +1039,8 @@ namespace eval punk::lib {
Returns a two element list - with the first element being the modulename and the second element being the version.
Tcl module version numbers are understood with leading zeros in each dotted part, but leading zeros are not canonical.
Tcl module version numbers are understood with leading zeros in each dotted part, including either side of a or b,
but leading zeros are not canonical.
This split does not canonicalise the version number.
If the last dash-separated segment of the name doesn't look like a valid version number

40
src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/modules/template_module-0.0.4.tm

@ -26,7 +26,45 @@ package require Tcl 8.6-
tcl::namespace::eval %pkg% {
variable PUNKARGS
namespace eval argdoc {
variable PUNKARGS
lappend PUNKARGS [list {
@id -id ::%pkg%::sample
@cmd -name %pkg%::sample\
-summary\
"Sample proc."\
-help\
"This is a sample proc to demonstrate the use of punk::args documentation features.
see punk::args::usage ::punk::args::define for details on the argument definition format and available features."
#leaders are positional arguments that come before any flags/options.
@leaders
arg1 -help\
"The first argument"
@opts
-force -type none -help\
"An optional flag with no value"
-opt1 -type string -help\
"An optional flag for a string value"
@values -min 1 -max 2
arg2 -type string -help\
"The second argument"
arg3 -type string -optional 1 -help\
"The optional third argument"
}]
}
proc sample {args} {
set argd [punk::args::parse $args withid ::%pkg%::sample]
lassign [dict values $argd] leaders opts values received
puts "This is a sample proc which uses punk::args for both documentation and argument parsing."
puts "punk::args may also be used purely for documentation if you have a different argument parsing mechanism or want to parse arguments manually"
puts "- call punk::args::usage with the exact id to get the documentation string for that command."
puts "- or use the 'i' and 's' aliases provided by punk to resolve a command to get information or synopsis"
puts "Raw arguments: $args"
puts "leaders: $leaders"
puts "opts: $opts"
puts "values: $values"
puts "received: $received"
}
}

6
src/modules/punk/mix/base-0.1.tm

@ -705,15 +705,15 @@ namespace eval punk::mix::base {
}
if {[catch {
#{*}$tarpath needed in case spaces in tarpath
exec {*}$tarpath -cf {*}$flags $archivename $target
exec {*}$tarpath {*}$flags -cf $archivename $target
} errMsg]} {
set tsend [clock millis]
set ms [expr {$tsend - $tsstart}]
puts stdout " 'tar -cf $flags' ERROR ($ms ms) - falling back to tar::create\n error info: $errMsg"
puts stdout " 'tar $flags -cf ' ERROR ($ms ms) - falling back to tar::create\n error info: $errMsg"
} else {
set tsend [clock millis]
set ms [expr {$tsend - $tsstart}]
puts stdout " 'tar -cf $flags' done ($ms ms)"
puts stdout " 'tar $flags -cf' done ($ms ms)"
}
}

6
src/modules/punk/mix/cli-999999.0a1.0.tm

@ -551,15 +551,15 @@ namespace eval punk::mix::cli {
set process_modules [dict create]
#put pods first in processing order
set src_pods [glob -nocomplain -dir $current_source_dir -type d -tail $podglob]
set src_pods [glob -nocomplain -dir $current_source_dir -type d -tail -- $podglob]
foreach podpath $src_pods {
dict set process_modules $podpath [dict create -type pod]
}
set src_tarjars [glob -nocomplain -dir $current_source_dir -type d -tail $tarjarglob]
set src_tarjars [glob -nocomplain -dir $current_source_dir -type d -tail -- $tarjarglob]
foreach tarjarpath $src_tarjars {
dict set process_modules $tarjarpath [dict create -type tarjar]
}
set src_modules [glob -nocomplain -dir $current_source_dir -type f -tail $fileglob]
set src_modules [glob -nocomplain -dir $current_source_dir -type f -tail -- $fileglob]
foreach modulepath $src_modules {
dict set process_modules $modulepath [dict create -type file]
}

2
src/modules/punk/mix/commandset/project-999999.0a1.0.tm

@ -118,7 +118,7 @@ namespace eval punk::mix::commandset::project {
@cmd -name "punk::mix::commandset::project::new" -help\
""
@leaders -min 1 -max 1
project -type string -help\
project -type string -help\
"Project name or path.
If just a name is given ... (todo)"
@opts

77
src/modules/punk/mix/util-999999.0a1.0.tm

@ -48,6 +48,14 @@ namespace eval punk::mix::util {
supports the same options (encoding, translation, eofchar) but also includes
some additional handling for windows paths (if punk::winpath is available)."
@opts
-n|--line-number -type none -help\
"Each file line is preceded by its line number, starting at line 1."
-ranges -type indexset -default "1..end" -help\
"comma delimited set of line ranges.
Restrict output to the specified line ranges of the file. Lines are numbered starting at 1.
For example, -ranges 1..5,10 would return lines 1 to 5 and line 10 of the file.
-ranges end-1..end would return the last two lines of the file.
-ranges end-4.. would return the last 5 lines of the file."
-noredirect -type none -help\
"By default, fcat will follow windows shortcuts (.lnk files) and .fauxlink files
if punk::winpath or punk::fauxlink is available.
@ -106,6 +114,12 @@ namespace eval punk::mix::util {
if {$opt_noredirect} {
set opts [dict remove $opts -noredirect]
}
set do_ln [dict exists $received --line-number]
if {$do_ln} {
set opts [dict remove $opts --line-number]
}
set ranges [dict get $opts -ranges]
set opts [dict remove $opts -ranges]
set finalpaths [list]
@ -152,7 +166,68 @@ namespace eval punk::mix::util {
}
set finalpaths $resolved_finalpaths
}
fileutil::cat {*}$opts {*}$eopts {*}$finalpaths
set lnc ""
set lnr ""
if {$ranges ni [list "0..end" "1..end" ".." "0.." "1.." "..end"]} {
set filedata [fileutil::cat {*}$opts {*}$eopts {*}$finalpaths]
set lines [split $filedata \n]
set linecount [llength $lines]
set w [string length $linecount]
set indices [punk::lib::indexset_resolve -base 1 $linecount $ranges]
set filedata ""
if {$do_ln} {
foreach idx $indices {
append filedata "$lnc[format %${w}s $idx]$lnr [lindex $lines $idx-1]" \n
}
} else {
foreach idx $indices {
append filedata [lindex $lines $idx-1] \n
}
}
#no superfluous trailing newline allowed. see test::punk::ns test: corp_linecount_match
if {[string index $filedata end] eq "\n"} {
set filedata [string range $filedata 0 end-1]
}
} else {
#range was specified in a standard way to mean 'all lines'
if {$do_ln} {
set filedata [fileutil::cat {*}$opts {*}$eopts {*}$finalpaths]
set lines [split $filedata \n]
set linecount [llength $lines]
set w [string length $linecount]
set filedata ""
set n 1
foreach ln $lines {
append filedata \n "$lnc[format %${w}s $n]$lnr $ln"
incr n
}
set filedata [string range $filedata 1 end] ;#strip leading newline
return $filedata
} else {
set filedata [fileutil::cat {*}$opts {*}$eopts {*}$finalpaths]
}
}
return $filedata
#if {$opt_linenumber} {
# set filedata [fileutil::cat {*}$opts {*}$eopts {*}$finalpaths]
# set lines [split $filedata \n]
# set linecount [llength $lines]
# set w [string length $linecount]
# set filedata ""
# set n 1
# set lnc ""
# set lnr ""
# foreach ln $lines {
# append filedata \n "$lnc[format %${w}s $n]$lnr $ln"
# incr n
# }
# set filedata [string range $filedata 1 end] ;#strip leading newline
# return $filedata
#}
#fileutil::cat {*}$opts {*}$eopts {*}$finalpaths
}
#----------------------------------------

4
src/modules/punk/mod-999999.0a1.0.tm

@ -48,7 +48,7 @@ namespace eval punk::mod::cli {
set namematches $appname
set parts [split $appname -]
} else {
set namematches [glob -nocomplain -dir $containerfolder -type d -tail ${appname}-*]
set namematches [glob -nocomplain -dir $containerfolder -type d -tail -- ${appname}-*]
set namematches [lsort $namematches] ;#todo - -ascii? -dictionary? natsort?
}
foreach nm $namematches {
@ -95,7 +95,7 @@ namespace eval punk::mod::cli {
if {[llength $apps] == 0} {
if {[string first * $glob] <0 && [string first ? $glob] <0} {
#no glob chars supplied - only launch if exact match for name part
set namematches [glob -nocomplain -dir $apps_folder -type d -tail ${glob}-*]
set namematches [glob -nocomplain -dir $apps_folder -type d -tail -- ${glob}-*]
set namematches [lsort $namematches] ;#todo - -ascii? -dictionary? natsort?
if {[llength $namematches] > 0} {
set latest [lindex $namematches end]

2
src/modules/punk/path-999999.0a1.0.tm

@ -1208,7 +1208,7 @@ namespace eval punk::path {
set files [list]
set dir_state [directory_state $opt_glob_paths $opt_dir_match $callallbelow]
if {[dict get $dir_state include_files]} {
if {[catch {glob -nocomplain -dir $opt_dir -type f {*}$tailglobs} matches]} {
if {[catch {glob -nocomplain -dir $opt_dir -type f -- {*}$tailglobs} matches]} {
puts stderr "treefilenames error while listing files in dir $opt_dir\n $matches"
set dirfiles [list]
} else {

3
src/modules/punk/repl-999999.0a1.0.tm

@ -158,7 +158,8 @@ namespace eval punk::repl::class {
}
}
namespace eval punk::repl {
tsv::set repl runid 0
#tsv::set repl runid 0
tsv::incr repl runid 0 ;#ensure exists
#todo - key on shell/subshell
tsv::set repl runchunks-0 [list] ;#last_run_display

6
src/modules/punk/safe-999999.0a1.0.tm

@ -1419,11 +1419,9 @@ tcl::namespace::eval punk::safe::system {
# after removing any subdir that are not in the access path.
if {($thedir eq "*") && ($thefile eq "pkgIndex.tcl")} {
set mapped 0
foreach d [glob -directory [TranslatePath $child $virtualdir] \
-types d -tails *] {
foreach d [glob -directory [TranslatePath $child $virtualdir] -types d -tails *] {
catch {
DirInAccessPath $child \
[TranslatePath $child [file join $virtualdir $d]]
DirInAccessPath $child [TranslatePath $child [file join $virtualdir $d]]
lappend cmd [file join $d $thefile]
set mapped 1
}

441
src/modules/punk/tcltestrun-999999.0a1.0.tm

@ -0,0 +1,441 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.4.tm
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2026
#
# @@ Meta Begin
# Application punk::tcltestrun 999999.0a1.0
# Meta platform tcl
# Meta license BSD
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
package require Tcl 8.6-
tcl::namespace::eval punk::tcltestrun {
variable PUNKARGS
namespace eval argdoc {
variable PUNKARGS
lappend PUNKARGS [list {
@id -id ::punk::tcltestrun::parse_testrun
@cmd -name punk::tcltestrun::parse_testrun\
-summary\
"Parse a dict containing stderr and stdout keys into testresult summary data."\
-help\
"Parse a dict containing stderr and stdout keys into testresult summary data.
The dict is expected to be in the format returned by shellrun::runx from execution of a tcltest script"
@leaders
@opts
@values -min 1 -max 2
testtextdict -type dict -help\
"Dict containing the keys 'stdout' and 'stderr' with the raw text output from a tcltestrun execution.
This is expected to be output from a single test file ie with only one summary line showing the total
number of tests run, passed, skipped and failed."
pkg -type string -optional 1 -help\
"An optional package name to include in the output lines of the parsed result for easier
identification of which test file the results came from when running multiple test files."
}]
}
proc parse_testrun {testtextdict {pkg ""}} {
if {[llength $testtextdict] % 2 != 0} {
return -code error "punk::tcltestrun::parse_testrun expected even number of elements in outputdict but got [llength $testtextdict]"
}
set results [dict create]
dict set results summaryline_total 0
dict set results summaryline_passed 0
dict set results summaryline_skipped 0
dict set results summaryline_failed 0
dict set results summaryline_detected 0
dict set results out ""
dict set results err ""
dict set results exitcode ""
dict set results test_case_results [list] ;#list of dicts with keys test_name, test_description, test_output
dict for {what chunk} $testtextdict {
set chunk [string map [list \r\n \n] $chunk]
#set test_case_open "" ;#used to track state for parsing test case names and descriptions from tcltest output
set test_case_state [dict create]
dict set test_case_state stage ""; #""|"open"|"contents"
dict set test_case_state test_status ""; #""|"PASSED"|"FAILED"
dict set test_case_state test_openingtext ""; #the text from the opening line of a test case which includes the test name and description, used to match against the closing line of the test case to ensure we are correctly parsing the test name and description even if they contain spaces or other potentially confounding characters.
switch -- $what {
stdout {
foreach ln [split $chunk \n] {
incr i
if {[string match "Tests ended at*" $ln]} {
#puts stdout "<stdout><$pkg> $ln"
dict append results out "<stdout><$pkg> $ln" \n
} elseif {[string match "*:*Total*Passed*Skipped*Failed*" $ln]} {
#summary line - will only be present if the test script contains a tcltest::cleanupTests call at the end of the script.
#we don't expect more than one.
#if we do get more than one, we will just process as normal here, which may increment tally fields incorrectly,
#but the caller should use summaryline_detected to warn or error if more than one summary line is detected,
#as this is a red flag that something is not configured correctly.
dict incr results summaryline_detected 1
set fields [lrange $ln 1 end]
dict for {K v} $fields {
#total passed skipped failed
set k [string tolower $K]
dict incr results summaryline_$k $v
}
#puts stdout "<stdout><$pkg> $ln"
dict append results out "<stdout><$pkg> $ln" \n
} elseif {[string match "*Sourced * Test Files*" $ln]} {
#puts stdout "<stdout><$pkg> $ln"
dict append results out "<stdout><$pkg> $ln" \n
} elseif {[string match "==== *" $ln]} {
#*probable* tcltest structure line.
#structure for failing test that wasn't due to an error (when -verbose contains body):
# ==== <testname_words> <test_description_words> FAILED
# ==== Contents of test case:
#
# <contents of test case>
#
# ---- Result was:
# <result of test case>
# ---- Result should have been (exact matching):
# <expected result of test case>
# ==== <testname_words> FAILED
#depending on whether -verbose contains body(b) pass(p) skip(s) error(e) msec(m) usec(u)
#we may get ---- or ++++ lines.
#e.g
# ---- <testname_words> start
# ++++ <testname_words> took 636 µs
# ++++ <testname_words> took 1 ms
# ++++ <testname_words> PASSED
# ++++ <testname_words> SKIPPED: <constraint/reason>
#Note that -verbose skip only reports a +++... SKIPPED:.. line if the test was actually skipped due to a constraint.
#It will not report if the skip was due to a failure to match a -match value or a match of a -skip value.
#See tcl ticket RFE: https://core.tcl-lang.org/tcl/tktview/1228806fffffffffffff
#unlikely but possible confounding factors.
#Test could include a command named "====" which outputs lines starting with "===="
#or a test could call something that uses puts stdout to output lines starting with "===="
#The first case is certainly possible as "====" can be defined as a command name - but pretty unlikely
#The second case is slightly more likely but still probably uncommon, and it would be more uncommon for the
#last word to be FAILED in that case.
#So it's probably a reasonable heuristic that lines starting with "====" and ending with FAILED
#are tcltest result lines, and we can parse the test name and description from those lines.
#Both cases are probably uncommon/unlikely unless the test is specifically designed to try to break this parser
#or to emulate tcltest output format.
#the tcltest output format is not nicely structured in that the testname and description can both contain spaces,
#so the first ==== * FAILED line can't on it's own be used to extract the test name and description
#we will handle this by maintaining state on encountering the first ==== * FAILED line,
#and not looking for the closing ==== <testname_words> FAILED line until we encounter
#the exact line ==== Contents of test case: which is always between the opening and closing lines for a test case.
#we should also only accept a closing ==== <testname_words> FAILED line
#if the <testname_words> portion is a prefix of the combined <testname_words> <test_description_words>
#portion from the opening line.
#This should allow us to correctly parse the test name and description even if they contain spaces,
#and even if the section between ==== Contents of test case: and the closing ==== <testname_words> FAILED contains
# emulated tcltest output lines or sub-output from some other call to tcltest, we should be able to ignore it properly.
set stage [dict get $test_case_state stage]
switch -exact -- $stage {
"" {
if {[string match "==== * FAILED" $ln]} {
dict set test_case_state stage open
set line_inner [string range $ln 5 end-7] ;#trim leading ====<space> and trailing <space>FAILED
dict set test_case_state test_openingtext $line_inner
}
dict append results out "<stdout><$pkg> $ln" \n
}
open {
if {[string match "==== Contents of test case:" $ln]} {
dict set test_case_state stage contents
} elseif {[string match "==== * FAILED" $ln]} {
set line_inner [string range $ln 5 end-7] ;#trim leading ====<space> and trailing <space>FAILED
if {[string match "$line_inner*" [dict get $test_case_state test_openingtext]]} {
dict set test_case_state stage ""
dict set test_case_state test_status ""
dict set test_case_state test_openingtext ""
# add to test_case_results list
set test_description [string trim [string range [dict get $test_case_state test_openingtext] [string length $line_inner] end]]
dict lappend results test_case_results [dict create {*}{
} test_name $line_inner {*}{
} test_description $test_description {*}{
} test_status FAILED {*}{
} test_output "" {*}{
}
]
} else {
}
}
dict append results out "<stdout><$pkg> $ln" \n
}
contents {
#we are in the contents section for a test case, so we should ignore any lines that look like they might be the closing line until we encounter the actual closing line that matches the test name prefix from the opening line.
#puts stdout "<stdout><$pkg> $ln"
if {[string match "==== * FAILED" $ln]} {
set line_inner [string range $ln 5 end-7] ;#trim leading ====<space> and trailing <space>FAILED
if {[string match "$line_inner*" [dict get $test_case_state test_openingtext]]} {
dict set test_case_state stage ""
dict set test_case_state test_status ""
dict set test_case_state test_openingtext ""
# add to test_case_results list
set test_description [string trim [string range [dict get $test_case_state test_openingtext] [string length $line_inner] end]]
dict lappend results test_case_results [dict create {*}{
} test_name $line_inner {*}{
} test_description $test_description {*}{
} test_status FAILED {*}{
} test_output "" {*}{
}
]
} else {
#we are in the contents section for a test case, but we have not yet encountered the closing line for that section
#so we should ignore any lines that look like they might be the closing line until we encounter the actual closing line that matches the test name prefix from the opening line.
}
}
dict append results out "<stdout><$pkg> $ln" \n
}
}
} else {
if {[string trim $ln] ne ""} {
#puts stdout "<stdout> $ln"
dict append results out "<stdout> $ln" \n
} else {
#puts -nonewline stdout "\n"
dict append results out "\n"
}
#puts stdout "$i"
}
}
#flush stdout
}
stderr {
#puts stderr "<stderr> [punk::ansi::ansistring VIEW -lf 2 -cr 1 $chunk]"
set chunkview [punk::ansi::ansistring VIEW -lf 2 -cr 1 $chunk]
foreach ln [split $chunkview \n] {
#puts stderr "<stderr> $ln"
dict append results err "<stderr> $ln" \n
}
#flush stderr
}
exitcode {
dict set results exitcode $chunk
}
default {
#puts stderr "<${what}> $chunk"
dict append results err "<${what}> $chunk" \n
#flush stderr
}
}
}
return $results
}
namespace eval argdoc {
variable PUNKARGS
lappend PUNKARGS [list {
@id -id ::punk::tcltestrun::tm_path_additional_ifneeded
@cmd -name punk::tcltestrun::tm_path_additional_ifneeded\
-summary\
"Add package ifneeded definitions for any unbuilt #modpod modules found in the specified path."\
-help\
"Add package ifneeded definitions for any unbuilt #modpod modules found in the specified path."
@leaders
@opts
@values -min 1 -max 1
tmpath -type string -help\
"Path to a folder containing unbuilt #modpod modules."
}]
}
proc tm_path_additional_ifneeded {tmpath} {
#If the supplied tmpath contains any unbuilt #modpod modules, we want to add package ifneeded definitions for those modules.
#return a single script containing the package ifneeded definitions.
#when running against unbuilt modules - we want to ensure that the unbuilt versions of any modules are used rather than any installed versions - so we add package ifneeded definitions for the unbuilt versions of any modules that are present.
# add 'package ifneeded' definitions for unbuilt #modpod modules.
#first gather subdirectories of modules that contain #modpod-*-999999.0a1.0 in their name - these should be the unbuilt versions of zip based modules.
#set subfolders [punk::path::subfolders -recursive $tmpath -match */#modpod-*-999999.0a1.0]
#'punk::path::subfolders' currently only supports negative matching with -exclude, so we have to filter for the positive match ourselves.
set subfolders [punk::path::subfolders -recursive -exclude {**/_build/** **/_build} $tmpath]
set script ""
foreach sub $subfolders {
#In most cases we could use string match - but the * within modpod-*-999999.0a1.0 could match a forward slash which could then match some other file under a #modpod- folder structure,
#so we use globmatchpath which treats * as matching any characters except path separators.
if {[punk::path::globmatchpath "**/#modpod-*-999999.0a1.0" $sub]} {
set modname [file tail $sub]
set modname [string range $modname 8 end-13] ;#strip off #modpod- and -999999.0a1.0
set modpath [file join $sub "$modname-999999.0a1.0.tm"]
#calculate fully qualified module name based on path relative to the modules folder we added to the tcl::tm path.
set relpath [punk::path::relative $project_root/src/modules [file dirname $sub]]
if {$relpath eq "."} {
set relpath ""
set fullmodname $modname
} else {
set components [file split $relpath]
set fullmodname [join $components ::]::$modname
}
#!!!!
#todo - review whether we also need to add the path to the module's folder to the auto_path to ensure that any 'package require' calls within the module will find the unbuilt version of any dependencies.
#we probably do need to do this - otherwise if there is an installed version of a dependency it could be loaded instead of the unbuilt version which is likely not what we want when running tests against unbuilt modules.
if {[file exists $modpath]} {
puts stderr "runtestmodules.tcl adding package ifneeded for modpod module $fullmodname at path $modpath"
#package ifneeded $modname 999999.0a1.0 [list source $modpath]
append script [list package ifneeded $fullmodname 999999.0a1.0 [list source $modpath]] { ;} \n
} else {
puts stderr "runtestmodules.tcl warning: expected mod.tcl not found for modpod module $fullmodname at path $modpath"
}
}
}
return $script
}
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::tcltestrun::lib {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
tcl::namespace::path [tcl::namespace::parent]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::tcltestrun::system {
#}
# == === === === === === === === === === === === === === ===
# Sample 'about' function with punk::args documentation
# == === === === === === === === === === === === === === ===
tcl::namespace::eval punk::tcltestrun {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
variable PUNKARGS
variable PUNKARGS_aliases
lappend PUNKARGS [list {
@id -id "(package)punk::tcltestrun"
@package -name "punk::tcltestrun" -help\
"Support package for running tcltest scripts and parsing their output.
This is primarily intended to be used with punk::shellrun when executing
tcltest scripts and parsing their output."
}]
namespace eval argdoc {
#namespace for custom argument documentation
proc package_name {} {
return punk::tcltestrun
}
proc about_topics {} {
#info commands results are returned in an arbitrary order (like array keys)
set topic_funs [info commands [namespace current]::get_topic_*]
set about_topics [list]
foreach f $topic_funs {
set tail [namespace tail $f]
lappend about_topics [string range $tail [string length get_topic_] end]
}
#Adjust this function or 'default_topics' if a different order is required
return [lsort $about_topics]
}
proc default_topics {} {return [list Description *]}
# -------------------------------------------------------------
# get_topic_ functions add more to auto-include in about topics
# -------------------------------------------------------------
proc get_topic_Description {} {
punk::args::lib::tstr [string trim {
package punk::tcltestrun
Support package for running tcltest scripts and parsing their output.
} \n]
}
proc get_topic_License {} {
return "BSD"
}
proc get_topic_Version {} {
return "$::punk::tcltestrun::version"
}
proc get_topic_Contributors {} {
set authors {{"Julian Noble" julian@precisium.com.au}}
set contributors ""
foreach a $authors {
append contributors $a \n
}
if {[string index $contributors end] eq "\n"} {
set contributors [string range $contributors 0 end-1]
}
return $contributors
}
proc get_topic_note {} {
punk::args::lib::tstr -return string {
punk::tcltestrun is primarily intended to be used with punk::shellrun when executing
tcltest scripts and parsing their output.
e.g in src/tests/runtests.tcl
}
}
# -------------------------------------------------------------
}
# we re-use the argument definition from punk::args::standard_about and override some items
set overrides [dict create]
dict set overrides @id -id "::punk::tcltestrun::about"
dict set overrides @cmd -name "punk::tcltestrun::about"
dict set overrides @cmd -help [string trim [punk::args::lib::tstr {
About punk::tcltestrun
}] \n]
dict set overrides topic -choices [list {*}[punk::tcltestrun::argdoc::about_topics] *]
dict set overrides topic -choicerestricted 1
dict set overrides topic -default [punk::tcltestrun::argdoc::default_topics] ;#if -default is present 'topic' will always appear in parsed 'values' dict
set newdef [punk::args::resolved_def -antiglobs -package_about_namespace -override $overrides ::punk::args::package::standard_about *]
lappend PUNKARGS [list $newdef]
proc about {args} {
package require punk::args
#standard_about accepts additional choices for topic - but we need to normalize any abbreviations to full topic name before passing on
set argd [punk::args::parse $args withid ::punk::tcltestrun::about]
lassign [dict values $argd] _leaders opts values _received
punk::args::package::standard_about -package_about_namespace ::punk::tcltestrun::argdoc {*}$opts {*}[dict get $values topic]
}
}
# end of sample 'about' function
# == === === === === === === === === === === === === === ===
# -----------------------------------------------------------------------------
# register namespace(s) to have PUNKARGS,PUNKARGS_aliases variables checked
# -----------------------------------------------------------------------------
# variable PUNKARGS
# variable PUNKARGS_aliases
namespace eval ::punk::args::register {
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::tcltestrun
}
# -----------------------------------------------------------------------------
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::tcltestrun [tcl::namespace::eval punk::tcltestrun {
variable pkg punk::tcltestrun
variable version
set version 999999.0a1.0
}]
return

3
src/modules/punk/tcltestrun-buildversion.txt

@ -0,0 +1,3 @@
0.1.0
#First line must be a semantic version number
#all other lines are ignored.

10
src/modules/punkcheck-999999.0a1.0.tm

@ -948,9 +948,9 @@ namespace eval punkcheck {
#windows: file exist + file type = 2ms vs 500ms for 2x glob
set floc [file dirname $fpath]
set fname [file tail $fpath]
set file_set [glob -nocomplain -dir $floc -type f -tails $fname]
set dir_set [glob -nocomplain -dir $floc -type d -tails $fname]
set link_set [glob -nocomplain -dir $floc -type l -tails $fname]
set file_set [glob -nocomplain -dir $floc -type f -tails -- $fname]
set dir_set [glob -nocomplain -dir $floc -type d -tails -- $fname]
set link_set [glob -nocomplain -dir $floc -type l -tails -- $fname]
if {[llength $file_set] == 0 && [llength $dir_set] == 0 && [llength $link_set] == 0} {
#could also theoretically exist as less common types, b,c,p,s (block,char,pipe,socket)
#- we don't expect them here - REVIEW - ever possible?
@ -1644,8 +1644,8 @@ namespace eval punkcheck {
set sources_unchanged [list]
set candidate_list [glob -nocomplain -dir $current_source_dir -type f -tail $fileglob]
set hidden_candidate_list [glob -nocomplain -dir $current_source_dir -types {hidden f} -tail $fileglob]
set candidate_list [glob -nocomplain -dir $current_source_dir -type f -tail -- $fileglob]
set hidden_candidate_list [glob -nocomplain -dir $current_source_dir -types {hidden f} -tail -- $fileglob]
foreach h $hidden_candidate_list {
if {$h ni $candidate_list} {
lappend candidate_list $h

2
src/modules/shellrun-999999.0a1.0.tm

@ -16,6 +16,8 @@ namespace eval shellrun {
variable runout
variable runerr
tsv::incr repl runid 0 ;#ensure exists
#do we need these?
#variable punkout
#variable punkerr

12
src/modules/test/#modpod-overtype-999999.0a1.0/overtype-999999.0a1.0.tm

@ -38,10 +38,10 @@ tcl::namespace::eval test::overtype {
variable PUNKARGS_aliases
lappend PUNKARGS [list {
@id -id "(package)test::overtype"
@package -name "test::overtype" -help\
"Test suites for overtype module"
}]
@id -id "(package)test::overtype"
@package -name "test::overtype" -help\
"Test suites for overtype module"
}]
namespace eval argdoc {
#namespace for custom argument documentation
@ -54,7 +54,7 @@ tcl::namespace::eval test::overtype {
set about_topics [list]
foreach f $topic_funs {
set tail [namespace tail $f]
lappend about_topics [string range $tail [string length get_topic_] end]
lappend about_topics [string range $tail [string length get_topic_] end]
}
#Adjust this function or 'default_topics' if a different order is required
return [lsort $about_topics]
@ -62,7 +62,7 @@ tcl::namespace::eval test::overtype {
proc default_topics {} {return [list Description *]}
# -------------------------------------------------------------
# get_topic_ functions add more to auto-include in about topics
# get_topic_ functions add more to auto-include in about topics
# -------------------------------------------------------------
proc get_topic_Description {} {
punk::args::lib::tstr [string trim {

Loading…
Cancel
Save