#!punk902testrunner shellspy #This script uses shellfilter::run calls under the hood - which probably requires a built punkshell binary to function properly. #(plain tclsh may stall - todo - review reasons for this and whether shellfilter can be modified to support ordinary tclsh) #A known working copy of a punk shell executable should be placed on the path and the shebang line updated to reflect this #------------------------------------ lassign [split [info tclversion] .] tcl_major tcl_minor set script_dir [file dirname [file normalize [info script]]] set modules_posn [string first /modules/ $script_dir] if {$modules_posn < 0} { puts stderr "Error: script dir $script_dir does not contain /modules/" #exit 2 ;#don't call exit. If run in a single proc it can cause the hole test suite exit before summary can be printed. return -code error "Error: script dir $script_dir does not contain /modules/" } set modules_base [string range $script_dir 0 $modules_posn-1] if {[file tail $modules_base] eq "src"} { set project_root [file dirname $modules_base] } else { set project_root $modules_base } puts stderr "runtestmodules.tcl project_root: $project_root" #use the unbuilt modules/libraries under development rather than the installed versions. #The unbuilt modules should have a higher version number (such as the magic version number 999999.0a1.0) than any installed versions to ensure they are preferred. tcl::tm::add [file normalize $project_root/src/modules] tcl::tm::add [file normalize $project_root/src/modules_tcl$tcl_major] tcl::tm::add [file normalize $project_root/src/vendormodules] tcl::tm::add [file normalize $project_root/src/vendormodules_tcl$tcl_major] # 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 [file normalize $project_root/src/modules] -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 [file normalize $project_root/src/modules] -exclude {**/_build/** **/_build}] 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 {[globmatchpath "**/#modpod-*-999999.0a1.0" $sub]} { set modname [file tail $sub] set modname [string range $modname 8 end-12] ;#strip off #modpod- and -999999.0a1.0 set modpath [file join $sub "$modname-999999.0a1.0.tm"] #!!!! #todo - calculate fully qualified module name based on path relative to the modules folder we added to the tcl::tm path. if {[file exists $modpath]} { puts stderr "runtestmodules.tcl adding package ifneeded for modpod module $modname at path $modpath" package ifneeded $modname 999999.0a1.0 [list source $modpath] } else { puts stderr "runtestmodules.tcl warning: expected mod.tcl not found for modpod module $modname at path $modpath" } } } set libdir [file normalize $project_root/src/lib] set libvdir [file normalize $project_root/src/lib/tcl$tcl_major] set libvldir [file normalize $project_root/src/vendorlib] set libvlvdir [file normalize $project_root/src/vendorlib_tcl$tcl_major] foreach d [list $libdir $libvdir $libvldir $libvlvdir] { if {$d ni $::auto_path} { lappend ::auto_path $d } } #------------------------------------ puts stderr "runtestmodules.tcl ::auto_path: $::auto_path" puts stderr "runtestmodules.tcl tcl::tm::list: [tcl::tm::list]" package require punk package require punk::args punk::args::define { @id -id (script)::runtestmodules @cmd -name runtestmodules -help\ "Run test:: modules that support the packagetest api (have RUN command)" -tcltestoptions -type list -default "" -help\ "arguments that will be left in ::argv for tcltest to handle" @values -min 0 -max -1 glob -type string -multiple 1 -optional 1 -help\ " names or glob patterns of test modules to run. Note that this script will search for all modules within the test namespace that are known to the current interpreter - not just those within the current project." } set argd [punk::args::parse $::argv withid (script)::runtestmodules] lassign [dict values $argd] leaders opts values received set tcltestoptions [dict get $opts -tcltestoptions] if {![dict exists $received glob]} { set pkg_globs [list *] } else { set pkg_globs [dict get $values glob] } set ::argv $tcltestoptions set ::argc [llength $tcltestoptions] #bogus require to ensure modules within path test have been scanned to be in Tcl's 'package ifneeded' in-memory database catch {package require test::bogus666} set tmlist [tcl::tm::list] foreach tmfolder $tmlist { set tfolder [file join $tmfolder test] if {[file exists $tfolder]} { puts stdout "checking tm test folder $tfolder" set subfolders [glob -nocomplain -dir $tfolder -type d -tail *] foreach sub $subfolders { if {[string match #* $sub]} { continue } puts stdout "bogus require of test::${sub}::bogus666" catch {package require test::${sub}::bogus666} } } } set alltestpkgs [lsearch -all -inline [package names] test::*] if {![llength $alltestpkgs]} { puts stder "No packages matching test::* found" exit 1 } if {[llength $pkg_globs] == 1 && [lindex $pkg_globs 0] eq "*"} { set matchedtestpkgs $alltestpkgs } else { set matchedtestpkgs [list] foreach pkg $alltestpkgs { foreach g $pkg_globs { if {[string match $g $pkg]} { lappend matchedtestpkgs $pkg break } } } } if {![llength $matchedtestpkgs]} { puts stderr "No test packages matched supplied glob patterns" exit 1 } puts "matchedtestpkgs: $matchedtestpkgs" set punktestpkgs [list] foreach pkg $matchedtestpkgs { if {![catch {package require $pkg}]} { if {[info commands ::${pkg}::RUN] ne ""} { lappend punktestpkgs $pkg } } else { puts stderr "failed to load test package $pkg" } } if {![llength $punktestpkgs]} { puts stderr "No test packages with RUN command were able to be loaded" exit 1 } set scriptname [file tail [info script]] set results [dict create] dict set results total 0 dict set results passed 0 dict set results skipped 0 dict set results failed 0 set pkgs_with_fails [list] set pkgs_without_fails [list] package require shellrun puts "running tests in [llength $punktestpkgs] packages $punktestpkgs" flush stderr flush stdout package require punk::ansi foreach pkg $punktestpkgs { puts stdout "running test pkg $pkg" if {[catch { #set result [shellrun::runout -tcl ${pkg}::RUN] set result [shellrun::runx -tcl ${pkg}::RUN] #set result [shellrun::runx ls] } errM]} { puts stderr "error calling 'runout -tcl ${pkg}::RUN' $errM"; flush stderr set result {none ""} } puts stdout "executed ${pkg}::RUN" flush stdout set i 0 dict for {what chunk} $result { set chunk [string map [list \r\n \n] $chunk] switch -- $what { stdout { foreach ln [split $chunk \n] { incr i if {[string match "Tests ended at*" $ln]} { puts stdout "<$pkg> $ln" } elseif {[string match "*:*Total*Passed*Skipped*Failed*" $ln]} { set fields [lrange $ln 1 end] dict for {K v} $fields { set k [string tolower $K] dict incr results $k $v if {$k eq "failed"} { if {$v == 0} { lappend pkgs_without_fails $pkg } elseif {$v > 0} { lappend pkgs_with_fails $pkg } } } puts stdout "<$pkg> $ln" } elseif {[string match "*Sourced * Test Files*" $ln]} { puts stdout "<$pkg> $ln" } else { if {[string trim $ln] ne ""} { puts stdout " $ln" } else { puts -nonewline stdout "\n" } #puts stdout "$i" } } flush stdout } stderr { #puts 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 " $ln" } flush stderr } default { puts stderr "<${what}> $chunk" flush stderr } } } puts stdout "completed pkg test ${pkg}" } puts stdout "packages without failures: $pkgs_without_fails" puts stdout "packages with failures: $pkgs_with_fails" puts stdout "results: Total [dict get $results total] Passed [dict get $results passed] Skipped [dict get $results skipped] Failed [dict get $results failed]" #after 5000 {set ::done true} #vwait ::done puts stdout "DONE" #exit 0