diff options
Diffstat (limited to 'tcllib/support/devel/sak/test/run.tcl')
-rw-r--r-- | tcllib/support/devel/sak/test/run.tcl | 880 |
1 files changed, 880 insertions, 0 deletions
diff --git a/tcllib/support/devel/sak/test/run.tcl b/tcllib/support/devel/sak/test/run.tcl new file mode 100644 index 0000000..9e0942f --- /dev/null +++ b/tcllib/support/devel/sak/test/run.tcl @@ -0,0 +1,880 @@ +# -*- tcl -*- +# (C) 2006 Andreas Kupries <andreas_kupries@users.sourceforge.net> +## +# ### + +package require sak::test::shell +package require sak::registry +package require sak::animate +package require sak::color +# TODO: Rework this package to use the sak::feedback package + +getpackage textutil::repeat textutil/repeat.tcl +getpackage fileutil fileutil/fileutil.tcl +getpackage struct::matrix struct/matrix.tcl + +namespace eval ::sak::test::run { + namespace import ::textutil::repeat::blank + namespace import ::sak::color::* +} + +# ### + +proc ::sak::test::run {argv} { + variable run::valgrind + array set config { + valgrind 0 raw 0 shells {} stem {} log 0 + } + + while {[string match -* [set opt [lindex $argv 0]]]} { + switch -exact -- $opt { + -s - --shell { + set sh [lindex $argv 1] + if {![fileutil::test $sh efrx msg "Shell"]} { + sak::test::usage $msg + } + lappend config(shells) $sh + set argv [lrange $argv 2 end] + } + -g - --valgrind { + if {![llength $valgrind]} { + sak::test::usage valgrind not found in the PATH + } + incr config(valgrind) + set argv [lrange $argv 1 end] + } + -v { + set config(raw) 1 + set argv [lrange $argv 1 end] + } + -l - --log { + set config(log) 1 + set config(stem) [lindex $argv 1] + set argv [lrange $argv 2 end] + } + default { + sak::test::usage Unknown option "\"$opt\"" + } + } + } + + if {$config(log)} {set config(raw) 0} + + if {![sak::util::checkModules argv]} return + + run::Do config $argv + return +} + +# ### + +proc ::sak::test::run::Do {cv modules} { + upvar 1 $cv config + variable valgrind + variable araw $config(raw) + variable alog $config(log) + # alog => !araw + + set shells $config(shells) + if {![llength $shells]} { + catch {set shells [sak::test::shell::list]} + } + if {![llength $shells]} { + set shells [list [info nameofexecutable]] + } + + if {$alog} { + variable logext [open $config(stem).log w] + variable logsum [open $config(stem).summary w] + variable logfai [open $config(stem).failures w] + variable logski [open $config(stem).skipped w] + variable lognon [open $config(stem).none w] + variable logerd [open $config(stem).errdetails w] + variable logfad [open $config(stem).faildetails w] + variable logtim [open $config(stem).timings w] + } else { + variable logext stdout + } + + # Preprocessing of module names and shell versions to allows + # better formatting of the progress output, i.e. vertically + # aligned columns + + if {!$araw} { + variable maxml 0 + variable maxvl 0 + sak::animate::init + foreach m $modules { + = "M $m" + set l [string length $m] + if {$l > $maxml} {set maxml $l} + } + foreach sh $shells { + = "SH $sh" + set v [exec $sh << {puts [info patchlevel]; exit}] + set l [string length $v] + if {$l > $maxvl} {set maxvl $l} + } + =| "Starting ..." + } + + set total 0 + set pass 0 + set fail 0 + set skip 0 + set err 0 + + foreach sh $shells { + foreach m $modules { + set cmd [Command config $m $sh] + sak::animate::init + if {$alog || $araw} { + puts $logext ============================================================ + flush $logext + } + if {[catch {Close [Process [open |$cmd r+]]} msg]} { + incr err + =| "~~ [mag]ERR ${msg}[rst]" + if {$alog || $araw} { + puts $logext [mag]$msg[rst] + flush $logext + } + } + #sak::animate::last Ok + } + } + + puts $logext "Passed [format %6d $pass] of [format %6d $total]" + puts $logext "Skipped [format %6d $skip] of [format %6d $total]" + + if {$fail} { + puts $logext "Failed [red][format %6d $fail][rst] of [format %6d $total]" + } else { + puts $logext "Failed [format %6d $fail] of [format %6d $total]" + } + if {$err} { + puts $logext "#Errors [mag][format %6d $err][rst]" + } else { + puts $logext "#Errors [format %6d $err]" + } + + if {$alog} { + variable xtimes + array set times $xtimes + + struct::matrix M + M add columns 6 + foreach k [lsort -dict [array names times]] { + #foreach {shell module testfile} $k break + foreach {testnum delta score} $times($k) break + M add row [linsert $k end $testnum $delta $score] + } + M sort rows -decreasing 5 + + M insert row 0 {Shell Module Testsuite Tests Seconds uSec/Test} + M insert row 1 {===== ====== ========= ===== ======= =========} + M add row {===== ====== ========= ===== ======= =========} + + puts $logsum \nTimings... + puts $logsum [M format 2string] + } + + exit [expr {($err || $fail) ? 1 : 0}] + return +} + +# ### + +if {$::tcl_platform(platform) == "windows"} { + + proc ::sak::test::run::Command {cv m sh} { + variable valgrind + upvar 1 $cv config + + # Windows. Construction of the pipe to run a specific + # testsuite against a single shell. There is no valgrind to + # accomodate, and neither can we expect to have unix commands + # like 'echo' and 'cat' available. 'echo' we can go without. A + # 'cat' however is needed to merge stdout and stderr of the + # testsuite for processing here. We use an emuluation written + # in Tcl. + + set catfile cat[pid].tcl + fileutil::writeFile $catfile { + catch {wm withdraw .} + while {![eof stdin]} {puts stdout [gets stdin]} + exit + } + + set cmd "" + lappend cmd $sh + lappend cmd [Driver] -modules [list $m] + lappend cmd |& $sh $catfile + #puts <<$cmd>> + + return $cmd + } + + proc ::sak::test::run::Close {pipe} { + close $pipe + file delete cat[pid].tcl + return + } +} else { + proc ::sak::test::run::Command {cv m sh} { + variable valgrind + upvar 1 $cv config + + # Unix. Construction of the pipe to run a specific testsuite + # against a single shell. The command is constructed to work + # when using valgrind, and works when not using it as well. + + set script {} + lappend script [list set argv [list -modules [list $m]]] + lappend script {set argc 2} + lappend script [list source [Driver]] + lappend script exit + + set cmd "" + lappend cmd echo [join $script \n] + lappend cmd | + + if {$config(valgrind)} { + foreach e $valgrind {lappend cmd $e} + if {$config(valgrind) > 1} { + lappend cmd --num-callers=8 + lappend cmd --leak-resolution=high + lappend cmd -v --leak-check=yes + lappend cmd --show-reachable=yes + } + } + lappend cmd $sh + #lappend cmd >@ stdout 2>@ stderr + lappend cmd |& cat + #puts <<$cmd>> + + return $cmd + } + + proc ::sak::test::run::Close {pipe} { + close $pipe + return + } +} + +# ### + +proc ::sak::test::run::Process {pipe} { + variable araw + variable alog + variable logext + while {1} { + if {[eof $pipe]} break + if {[gets $pipe line] < 0} break + if {$alog || $araw} {puts $logext $line ; flush $logext} + set rline $line + set line [string trim $line] + if {[string equal $line ""]} continue + Host; Platform + Cwd; Shell + Tcl + Start; End ; StartFile ; EndFile + Module; Testsuite + NoTestsuite + Support;Testing;Other + Summary + CaptureFailureSync ; # xcollect 1 => 2 + CaptureFailureCollectBody ; # xcollect 2 => 3 => 5 + CaptureFailureCollectActual ; # xcollect 3 => 4 + CaptureFailureCollectExpected ; # xcollect 4 => 0 + CaptureFailureCollectError ; # xcollect 5 => 0 + CaptureStackStart + CaptureStack + + TestStart + TestSkipped + TestPassed + TestFailed ; # xcollect => 1 + + SetupError + Aborted + AbortCause + + Match||Skip||Sourced + # Unknown lines are printed + if {!$araw} {puts !$line} + } + return $pipe +} + +# ### + +proc ::sak::test::run::Driver {} { + variable base + return [file join $base all.tcl] +} + +# ### + +proc ::sak::test::run::Host {} { + upvar 1 line line ; variable xhost + if {![regexp "^@@ Host (.*)$" $line -> xhost]} return + # += $xhost + set xhost [list Tests Results $xhost] + #sak::registry::local set $xhost + return -code continue +} + +proc ::sak::test::run::Platform {} { + upvar 1 line line ; variable xplatform + if {![regexp "^@@ Platform (.*)$" $line -> xplatform]} return + # += ($xplatform) + variable xhost + #sak::registry::local set $xhost Platform $xplatform + return -code continue +} + +proc ::sak::test::run::Cwd {} { + upvar 1 line line ; variable xcwd + if {![regexp "^@@ CWD (.*)$" $line -> xcwd]} return + variable xhost + set xcwd [linsert $xhost end $xcwd] + #sak::registry::local set $xcwd + return -code continue +} + +proc ::sak::test::run::Shell {} { + upvar 1 line line ; variable xshell + if {![regexp "^@@ Shell (.*)$" $line -> xshell]} return + # += [file tail $xshell] + variable xcwd + set xshell [linsert $xcwd end $xshell] + #sak::registry::local set $xshell + return -code continue +} + +proc ::sak::test::run::Tcl {} { + upvar 1 line line ; variable xtcl + if {![regexp "^@@ Tcl (.*)$" $line -> xtcl]} return + variable xshell + variable maxvl + += \[$xtcl\][blank [expr {$maxvl - [string length $xtcl]}]] + #sak::registry::local set $xshell Tcl $xtcl + return -code continue +} + +proc ::sak::test::run::Match||Skip||Sourced {} { + upvar 1 line line + if {[string match "@@ Skip*" $line]} {return -code continue} + if {[string match "@@ Match*" $line]} {return -code continue} + if {[string match "Sourced * Test Files." $line]} {return -code continue} + if {[string match "Files with failing tests*" $line]} {return -code continue} + if {[string match "Number of tests skipped*" $line]} {return -code continue} + if {[string match "\[0-9\]*" $line]} {return -code continue} + return +} + +proc ::sak::test::run::Start {} { + upvar 1 line line + if {![regexp "^@@ Start (.*)$" $line -> start]} return + variable xshell + #sak::registry::local set $xshell Start $start + return -code continue +} + +proc ::sak::test::run::End {} { + upvar 1 line line + if {![regexp "^@@ End (.*)$" $line -> end]} return + variable xshell + #sak::registry::local set $xshell End $end + return -code continue +} + +proc ::sak::test::run::StartFile {} { + upvar 1 line line + if {![regexp "^@@ StartFile (.*)$" $line -> start]} return + variable xstartfile $start + variable xtestnum 0 + #sak::registry::local set $xshell Start $start + return -code continue +} + +proc ::sak::test::run::EndFile {} { + upvar 1 line line + if {![regexp "^@@ EndFile (.*)$" $line -> end]} return + variable xfile + variable xstartfile + variable xtimes + variable xtestnum + + set k [lreplace $xfile 0 3] + set k [lreplace $k 2 2 [file tail [lindex $k 2]]] + set delta [expr {$end - $xstartfile}] + + if {$xtestnum == 0} { + set score $delta + } else { + # average number of microseconds per test. + set score [expr {int(($delta/double($xtestnum))*1000000)}] + #set score [expr {$delta/double($xtestnum)}] + } + + lappend xtimes $k [list $xtestnum $delta $score] + + variable alog + if {$alog} { + variable logtim + puts $logtim [linsert [linsert $k end $xtestnum $delta $score] 0 TIME] + } + + #sak::registry::local set $xshell End $end + return -code continue +} + +proc ::sak::test::run::Module {} { + upvar 1 line line ; variable xmodule + if {![regexp "^@@ Module (.*)$" $line -> xmodule]} return + variable xshell + variable xstatus ok + variable maxml + += ${xmodule}[blank [expr {$maxml - [string length $xmodule]}]] + set xmodule [linsert $xshell end $xmodule] + #sak::registry::local set $xmodule + return -code continue +} + +proc ::sak::test::run::Testsuite {} { + upvar 1 line line ; variable xfile + if {![regexp "^@@ Testsuite (.*)$" $line -> xfile]} return + = <[file tail $xfile]> + variable xmodule + set xfile [linsert $xmodule end $xfile] + #sak::registry::local set $xfile Aborted 0 + return -code continue +} + +proc ::sak::test::run::NoTestsuite {} { + upvar 1 line line + if {![string match "Error: No test files remain after*" $line]} return + variable xstatus none + = {No tests} + return -code continue +} + +proc ::sak::test::run::Support {} { + upvar 1 line line + if {![regexp "^- (.*)$" $line -> package]} return + #= "S $package" + foreach {pn pv} $package break + variable xfile + #sak::registry::local set [linsert $xfile end Support] $pn $pv + return -code continue +} + +proc ::sak::test::run::Testing {} { + upvar 1 line line + if {![regexp "^\\* (.*)$" $line -> package]} return + #= "T $package" + foreach {pn pv} $package break + variable xfile + #sak::registry::local set [linsert $xfile end Testing] $pn $pv + return -code continue +} + +proc ::sak::test::run::Other {} { + upvar 1 line line + if {![string match ">*" $line]} return + return -code continue +} + +proc ::sak::test::run::Summary {} { + upvar 1 line line + if {![regexp "^all\\.tcl:(.*)$" $line -> line]} return + variable xmodule + variable xstatus + variable xvstatus + foreach {_ t _ p _ s _ f} [split [string trim $line]] break + #sak::registry::local set $xmodule Total $t ; set t [format %5d $t] + #sak::registry::local set $xmodule Passed $p ; set p [format %5d $p] + #sak::registry::local set $xmodule Skipped $s ; set s [format %5d $s] + #sak::registry::local set $xmodule Failed $f ; set f [format %5d $f] + + upvar 2 total _total ; incr _total $t + upvar 2 pass _pass ; incr _pass $p + upvar 2 skip _skip ; incr _skip $s + upvar 2 fail _fail ; incr _fail $f + upvar 2 err _err + + set t [format %5d $t] + set p [format %5d $p] + set s [format %5d $s] + set f [format %5d $f] + + if {$xstatus == "ok" && $t == 0} { + set xstatus none + } + + set st $xvstatus($xstatus) + + if {$xstatus == "ok"} { + # Quick return for ok suite. + =| "~~ $st T $t P $p S $s F $f" + return -code continue + } + + # Clean out progress display using a non-highlighted + # string. Prevents the char couint from being off. This is + # followed by construction and display of the highlighted version. + + = " $st T $t P $p S $s F $f" + switch -exact -- $xstatus { + none {=| "~~ [yel]$st T $t[rst] P $p S $s F $f"} + aborted {=| "~~ [whi]$st[rst] T $t P $p S $s F $f"} + error { + =| "~~ [mag]$st[rst] T $t P $p S $s F $f" + incr _err + } + fail {=| "~~ [red]$st[rst] T $t P $p S $s [red]F $f[rst]"} + } + return -code continue +} + +proc ::sak::test::run::TestStart {} { + upvar 1 line line + if {![string match {---- * start} $line]} return + set testname [string range $line 5 end-6] + = "---- $testname" + variable xfile + variable xtest [linsert $xfile end $testname] + variable xtestnum + incr xtestnum + return -code continue +} + +proc ::sak::test::run::TestSkipped {} { + upvar 1 line line + if {![string match {++++ * SKIPPED:*} $line]} return + regexp {^[^ ]* (.*)SKIPPED:.*$} $line -> testname + set testname [string trim $testname] + variable xtest + = "SKIP $testname" + if {$xtest == {}} { + variable xfile + set xtest [linsert $xfile end $testname] + } + #sak::registry::local set $xtest Status Skip + set xtest {} + return -code continue +} + +proc ::sak::test::run::TestPassed {} { + upvar 1 line line + if {![string match {++++ * PASSED} $line]} return + set testname [string range $line 5 end-7] + variable xtest + = "PASS $testname" + if {$xtest == {}} { + variable xfile + set xtest [linsert $xfile end $testname] + } + #sak::registry::local set $xtest Status Pass + set xtest {} + return -code continue +} + +proc ::sak::test::run::TestFailed {} { + upvar 1 line line + if {![string match {==== * FAILED} $line]} return + set testname [lindex [split [string range $line 5 end-7]] 0] + = "FAIL $testname" + variable xtest + if {$xtest == {}} { + variable xfile + set xtest [linsert $xfile end $testname] + } + #sak::registry::local set $xtest Status Fail + ## CAPTURE INIT + variable xcollect 1 + variable xbody "" + variable xactual "" + variable xexpected "" + variable xstatus fail + # Ignore failed status if we already have it, or an error + # status. The latter is more important to show. We do override + # status 'aborted'. + if {$xstatus == "ok"} {set xstatus fail} + if {$xstatus == "aborted"} {set xstatus fail} + return -code continue +} + +proc ::sak::test::run::CaptureFailureSync {} { + variable xcollect + if {$xcollect != 1} return + upvar 1 line line + if {![string match {==== Contents*} $line]} return + set xcollect 2 + return -code continue +} + +proc ::sak::test::run::CaptureFailureCollectBody {} { + variable xcollect + if {$xcollect != 2} return + upvar 1 rline line + variable xbody + if {[string match {---- Result was*} $line]} { + set xcollect 3 + return -code continue + } elseif {[string match {---- Test generated error*} $line]} { + set xcollect 5 + return -code continue + } + + variable xbody + append xbody $line \n + return -code continue +} + +proc ::sak::test::run::CaptureFailureCollectActual {} { + variable xcollect + if {$xcollect != 3} return + upvar 1 rline line + if {![string match {---- Result should*} $line]} { + variable xactual + append xactual $line \n + } else { + set xcollect 4 + } + return -code continue +} + +proc ::sak::test::run::CaptureFailureCollectExpected {} { + variable xcollect + if {$xcollect != 4} return + upvar 1 rline line + if {![string match {==== *} $line]} { + variable xexpected + append xexpected $line \n + } else { + variable alog + if {$alog} { + variable logfad + variable xtest + variable xbody + variable xactual + variable xexpected + + puts $logfad "==== [lrange $xtest end-1 end] FAILED =========" + puts $logfad "==== Contents of test case:\n" + puts $logfad $xbody + + puts $logfad "---- Result was:" + puts $logfad [string range $xactual 0 end-1] + + puts $logfad "---- Result should have been:" + puts $logfad [string range $xexpected 0 end-1] + + puts $logfad "==== [lrange $xtest end-1 end] ====\n\n" + flush $logfad + } + set xcollect 0 + #sak::registry::local set $xtest Body $xbody + #sak::registry::local set $xtest Actual $xactual + #sak::registry::local set $xtest Expected $xexpected + set xtest {} + } + return -code continue +} + +proc ::sak::test::run::CaptureFailureCollectError {} { + variable xcollect + if {$xcollect != 5} return + upvar 1 rline line + variable xbody + if {[string match {---- errorCode*} $line]} { + set xcollect 4 + return -code continue + } + + variable xactual + append xactual $line \n + return -code continue +} + +proc ::sak::test::run::Aborted {} { + upvar 1 line line + if {![string match {Aborting the tests found *} $line]} return + variable xfile + variable xstatus + # Ignore aborted status if we already have it, or some other error + # status (like error, or fail). These are more important to show. + if {$xstatus == "ok"} {set xstatus aborted} + = Aborted + #sak::registry::local set $xfile Aborted {} + return -code continue +} + +proc ::sak::test::run::AbortCause {} { + upvar 1 line line + if { + ![string match {Requiring *} $line] && + ![string match {Error in *} $line] + } return ; # {} + variable xfile + = $line + #sak::registry::local set $xfile Aborted $line + return -code continue +} + +proc ::sak::test::run::CaptureStackStart {} { + upvar 1 line line + if {![string match {@+*} $line]} return + variable xstackcollect 1 + variable xstack {} + variable xstatus error + = {Error, capturing stacktrace} + return -code continue +} + +proc ::sak::test::run::CaptureStack {} { + variable xstackcollect + if {!$xstackcollect} return + upvar 1 line line + variable xstack + if {![string match {@-*} $line]} { + append xstack [string range $line 2 end] \n + } else { + set xstackcollect 0 + variable xfile + variable alog + #sak::registry::local set $xfile Stacktrace $xstack + if {$alog} { + variable logerd + puts $logerd "[lindex $xfile end] StackTrace" + puts $logerd "========================================" + puts $logerd $xstack + puts $logerd "========================================\n\n" + flush $logerd + } + } + return -code continue +} + +proc ::sak::test::run::SetupError {} { + upvar 1 line line + if {![string match {SETUP Error*} $line]} return + variable xstatus error + = {Setup error} + return -code continue +} + +# ### + +proc ::sak::test::run::+= {string} { + variable araw + if {$araw} return + variable aprefix + append aprefix " " $string + sak::animate::next $aprefix + return +} + +proc ::sak::test::run::= {string} { + variable araw + if {$araw} return + variable aprefix + sak::animate::next "$aprefix $string" + return +} + +proc ::sak::test::run::=| {string} { + variable araw + if {$araw} return + variable aprefix + sak::animate::last "$aprefix $string" + variable alog + if {$alog} { + variable logsum + variable logfai + variable logski + variable lognon + variable xstatus + puts $logsum "$aprefix $string" ; flush $logsum + switch -exact -- $xstatus { + error - + fail {puts $logfai "$aprefix $string" ; flush $logfai} + none {puts $lognon "$aprefix $string" ; flush $lognon} + aborted {puts $logski "$aprefix $string" ; flush $logski} + } + } + set aprefix "" + return +} + +# ### + +namespace eval ::sak::test::run { + variable base [file join $::distribution support devel] + variable valgrind [auto_execok valgrind] + + # State of test processing. + + variable xstackcollect 0 + variable xstack {} + variable xcollect 0 + variable xbody {} + variable xactual {} + variable xexpected {} + variable xhost {} + variable xplatform {} + variable xcwd {} + variable xshell {} + variable xmodule {} + variable xfile {} + variable xtest {} + variable xstartfile {} + variable xtimes {} + + variable xstatus ok + + # Animation prefix of test processing, and flag controlling the + # nature of logging (raw vs animation). + + variable aprefix {} + variable araw 0 + + # Max length of module names and patchlevel information. + + variable maxml 0 + variable maxvl 0 + + # Map from internal stati to the displayed human readable + # strings. This includes the trailing whitespace needed for + # vertical alignment. + + variable xvstatus + array set xvstatus { + ok { } + none {None } + aborted {Skip } + error {ERR } + fail {FAILS} + } +} + +## +# ### + +package provide sak::test::run 1.0 + +if 0 { + # Bad valgrind, ok no valgrind + if {$config(valgrind)} { + foreach e $valgrind {lappend cmd $e} + lappend cmd --num-callers=8 + lappend cmd --leak-resolution=high + lappend cmd -v --leak-check=yes + lappend cmd --show-reachable=yes + } + lappend cmd $sh + lappend cmd [Driver] -modules $modules +} |