::xounit
Class TestResultsWebFormatter

Heritage:
::xotcl::Object
  |
  +--::xounit::TestResultsTextFormatter
Direct Known Subclasses:
::xounit::TestFailuresWebFormatter, ::xounit::TestSuiteContinuous, ::xounit::WebTestRunner,

Associated Test:
::xounit::test::TestTestResultsWebFormatter

Class TestResultsWebFormatter
superclass ::xounit::TestResultsTextFormatter,
Format test results for display on a web page.
Variables
NameDefault ValueClassComment
apiDoc  http://xotcllib.sourceforge.net/xodoc/  ::xounit::TestResultsWebFormatter
 url to API documentation
location    ::xounit::TestResultsWebFormatter
 location to write html file to
styleSheet  style.css  ::xounit::TestResultsWebFormatter
 CSS stylesheet to use in html file
textFormatter    ::xounit::TestResultsWebFormatter
 text formatter to use
title    ::xounit::TestResultsWebFormatter
 title to use in html file
url    ::xounit::TestResultsWebFormatter
 url to more project information
webPath    ::xounit::TestResultsWebFormatter
 url where html file will be served
 
Methods
NameComment
barGraph {passed total}   Creates the html code for a bar graph that measures the number of passes out of a total number of tests
cleanUpData {data}   Cleans up data for presentation in html
cleanUpLink {link}   Cleans up a string for use as an html link
formatClassMethodsInError {error}   Parse the Tcl/XOTcl stack trace and find instances of XOTcl method calls and add a link to the Class/Method in stack trace
formatObjectClassMethodLine {line}   In a stack trace line find a class and method and create a link to the API documentation from that class and method
formatResults {results}   Format a list of results and return the formatted string
formatWebResults {results}   Format a list of results for display on a web page
init {}   Initalizes the TestResultsWebFormatter with a new textFormatter
makeClassLink {result}   Make an html link from the testedClass in a TestResult
makeClassMethodLink {result}   Make an html linke from the testedClass and testedMethod in a TestResult
organizeResults {resultOne resultTwo}   Comparison method that will compare two test results to organize them by TestResult name
passed {results}   Returns true if all results passed
printError {result anError}   Format a TestError
printFailure {result aFailure}   Format a TestFailure
printPass {result aPass}   Format a TestPass
printPasses {aResult}   Formats all passing sub-results in a TestResult
printResult {aResult}   Format one TestResult and return the string
printSubResult {result subResult}   Format a subtest result
rssItem {results link}   Create an RSS item that describes how many tests failed in this test report
testSummary {results}   Format a summary of a list of TestResults
writeWebResults {results}   Write the formatted TestResults to location and update the RSS feed and the html index
   
Methods from ::xotcl::Object
#, ., ?, ?code, ?methods, ?object, abstract, copy, coverageFilter, defaultmethod, extractConfigureArg, filterappend, garbageCollect, get#, getClean#, hasclass, init, methodTag, mixinappend, move, profileFilter, self, setParameterDefaults, shell, tclcmd, traceFilter,
 
Instproc Detail

barGraph

Description:
 Creates the html code for a bar graph that measures
 the number of passes out of a total number of tests.
Arguments:
Code:
  ::xounit::TestResultsWebFormatter instproc barGraph {passed total}  {
   

        if { $total == 0 } {

            set passWidth 0
            set failWidth 200
            set failed 0 
            set passed 0

        } else {

            set failed [ expr { $total - $passed } ]
            set passWidth [ expr { int( 200 * $passed / $total ) } ]
            set failWidth [ expr { int( 200 * $failed / $total ) } ]
        }

        if { $passed == $total && $total > 0 } {

        append buffer "
<TABLE class=barGraph cellspacing=0>
<TBODY>
<TR>
<TD class=covered><img src=\"spacer.gif\"
width=\"$passWidth\" height=\"12\"></TD>
</TR>
</TBODY>
</TABLE>"

        } elseif { $passed == 0 } {

        append buffer "
<TABLE class=barGraph cellspacing=0>
<TBODY>
<TR>
<TD class=uncovered><img src=\"spacer.gif\"
width=\"$failWidth\" height=\"12\"></TD>
</TR>
</TBODY>
</TABLE>"

        } else {

        append buffer "
<TABLE class=barGraph cellspacing=0>
<TBODY>
<TR>
<TD class=covered><img src=\"spacer.gif\"
width=\"$passWidth\" height=\"12\"></TD>
<TD class=uncovered><img src=\"spacer.gif\"
width=\"$failWidth\" height=\"12\"></TD>
</TR>
</TBODY>
</TABLE>"

        }

        return $buffer

    
}

cleanUpData

Description:
 Cleans up data for presentation in html
Arguments:
Code:
  ::xounit::TestResultsWebFormatter instproc cleanUpData {data}  {
   

        regsub -all {\&} "$data" {\&amp;} data
        regsub -all < "$data" {\&lt;} data
        regsub -all > "$data" {\&gt;} data

        return $data
    
}

cleanUpLink

Description:
 Cleans up a string for use as an html link.
Arguments:
Code:
  ::xounit::TestResultsWebFormatter instproc cleanUpLink {link}  {
   

        regsub -all {::} "$link" {_} link
        regsub -all {#} "$link" {_} link

        return $link
   
}

formatClassMethodsInError

Description:
 Parse the Tcl/XOTcl stack trace and find instances
 of XOTcl method calls and add a link to the Class/Method
 in stack trace.
Arguments:
Code:
  ::xounit::TestResultsWebFormatter instproc formatClassMethodsInError {error}  {
   

        set newError ""

        foreach line [ split $error "\n" ] {

            if { [ string first "->" $line ] != -1 } {

                append newError [ my formatObjectClassMethodLine $line ]

            } else {
            append newError [ my cleanUpData $line ]
            append newError "\n"
            }
        }

        return $newError
    
}

formatObjectClassMethodLine

Description:
 In a stack trace line find a class and method and create
 a link to the API documentation from that class and method.
Arguments:
Code:
  ::xounit::TestResultsWebFormatter instproc formatObjectClassMethodLine {line}  {
   

        my instvar apiDoc

        if { ! [ my exists apiDoc ] } { return $line }

        set newError ""

        set object [ lindex $line 0 ]
        set classMethod [ lindex $line 1 ]

        set class [ lindex [ split $classMethod "->" ] 0 ]
        set method [ lindex [ split $classMethod "->" ] 2 ]

        if { ! [ Object isclass $class ] } { return $line }

        set cleanClass [ my cleanUpLink $class ]

        set index [ string first $class $line ]
        incr index -1

        append newError [ string range $line 0 $index ]
        append newError "<a href=\"$apiDoc$cleanClass.html#$method\">[ my cleanUpData $classMethod ] </a>"
        append newError "\n"

        return $newError
    
}

formatResults

Description:
 Format a list of results and return the formatted string.
Arguments:
Overrides:
formatResults in ::xounit::TestResultsTextFormatter
Code:
  ::xounit::TestResultsWebFormatter instproc formatResults {results}  {
   

        my instvar styleSheet

        set text [ my formatWebResults $results ]

        append buffer "<html><head>"
        append buffer "<LINK REL=StyleSheet HREF=\"$styleSheet\" TYPE=\"text/css\" />\n"
        append buffer "</head><body>\n"
        append buffer "<a href=\"index.html\">\[ Index \]</a><br/>\n"
        append buffer "$text\n"
        append buffer "</body></html>\n"
        
        return $buffer
    
}

formatWebResults

Description:
 Format a list of results for display on a web page.
Arguments:
Code:
  ::xounit::TestResultsWebFormatter instproc formatWebResults {results}  {
   

        set results [ lsort -command "[ self ] organizeResults" $results ]

        foreach result $results {

            puts "[ $result name ]"
        }

        set buffer ""

        append buffer [ my testSummary $results ]

        foreach result $results {

            if [ $result passed ] { continue }
            append buffer [ my printResult $result ]
        }

        append buffer "<h1>Passes</h1>"
        append buffer "<a href=\"#summary\">\[Summary\]</a> <br />"
        append buffer "
<table class=\"FailureTable\"><tbody>
<tr>
<td class=\"FailureTableHeader\" >Test</td>
<td class=\"FailureTableHeader\" >Message</td>
</tr>"
        foreach result $results {

            append buffer [ my printPasses $result ]
        }
        append buffer "</tbody><table>"

        return $buffer
    
}

init

Description:
 Initalizes the TestResultsWebFormatter with a new textFormatter.
Overrides:
init in ::xotcl::Object
Code:
  ::xounit::TestResultsWebFormatter instproc init {}  {
   

        my textFormatter [ ::xounit::TestResultsTextFormatter new ]
    
}

makeClassLink

Description:
 Make an html link from the testedClass in a TestResult.
Arguments:
Code:
  ::xounit::TestResultsWebFormatter instproc makeClassLink {result}  {
   

       my instvar apiDoc

       set class [ $result testedClass ]

       if { ! [ my exists apiDoc ] || ( "$class" == "" ) } {

           return [ $result name ]
       }

       set cleanClass [ my cleanUpLink $class ]
       return "<a href=\"$apiDoc$cleanClass.html\">[ $result name ]</a>"
   
}

makeClassMethodLink

Description:
 Make an html linke from the testedClass and testedMethod in
 a TestResult.
Arguments:
Code:
  ::xounit::TestResultsWebFormatter instproc makeClassMethodLink {result}  {
   

       my instvar apiDoc

       set class [ $result testedClass ]
       set method [ $result testedMethod ]

       if { ! [ my exists apiDoc ] || ( "$class" == "" ) || ( "$method" == "" ) } {

           return [ $result test ]
       }

       set cleanClass [ my cleanUpLink $class ]
       return "<a href=\"$apiDoc$cleanClass.html#$method\">[ $result test ]</a>"
   
}

organizeResults

Description:
 Comparison method that will compare two test results to organize
 them by TestResult name.
Arguments:
Code:
  ::xounit::TestResultsWebFormatter instproc organizeResults {resultOne resultTwo}  {
   

        set nameOne [ $resultOne name ]
        set nameTwo [ $resultTwo name ]

        return [ string compare $nameOne $nameTwo ]
    
}

passed

Description:
 Returns true if all results passed.
Arguments:
Code:
  ::xounit::TestResultsWebFormatter instproc passed {results}  {
   

        foreach result $results {

            if { ! [ $result passed ] } { return 0 }
        }

        return 1
    
}

printError

Description:
 Format a TestError
Arguments:
Overrides:
printError in ::xounit::TestResultsTextFormatter
Code:
  ::xounit::TestResultsWebFormatter instproc printError {result anError}  {
   


        set return "<tr>
<td class=\"ErrorTable\">
<a name=\"[ my cleanUpLink $anError ]\"/>
[ my makeClassLink $anError ] [ my makeClassMethodLink $anError ]
</td>
<td class=\"ErrorTable\">Error stacktrace below</td>
<tr><td class=\"ErrorTable\" colspan=\"2\">
<pre>[ my formatClassMethodsInError [ $anError error] ]</pre>
</td></tr>"

        return $return
    
}

printFailure

Description:
 Format a TestFailure
Arguments:
Overrides:
printFailure in ::xounit::TestResultsTextFormatter
Code:
  ::xounit::TestResultsWebFormatter instproc printFailure {result aFailure}  {
   


        set return "<tr>
<td class=\"FailureTable\">[ my makeClassLink $aFailure ] [ my makeClassMethodLink $aFailure ] </td>
<td class=\"FailureTable\">
<a name=\"[ my cleanUpLink $aFailure ]\"/>
<pre>[ my cleanUpData [$aFailure error] ]</pre>
</td></tr>"

        return $return
    
}

printPass

Description:
 Format a TestPass
Arguments:
Overrides:
printPass in ::xounit::TestResultsTextFormatter
Code:
  ::xounit::TestResultsWebFormatter instproc printPass {result aPass}  {
   

        set return "<tr>
<td class=\"PassTable\">[ my makeClassLink $aPass ] [ my makeClassMethodLink $aPass ] </td>
<td class=\"PassTable\">
<pre> [ my cleanUpData [$aPass message] ]</pre>
</td></tr>"

        return $return
    
}

printPasses

Description:
 Formats all passing sub-results in a TestResult.
Arguments:
Code:
  ::xounit::TestResultsWebFormatter instproc printPasses {aResult}  {
   

        set buffer ""

        foreach pass [ $aResult passes ] {

            append buffer [ my printPass $aResult $pass ]
        }

        return $buffer
    
}

printResult

Description:
 Format one TestResult and return the string.
Arguments:
Overrides:
printResult in ::xounit::TestResultsTextFormatter
Code:
  ::xounit::TestResultsWebFormatter instproc printResult {aResult}  {
   

        set buffer ""

        append buffer "<a href=\"#summary\">\[Summary\]</a> <br />"

        append buffer "
<table class=\"FailureTable\"><tbody>
<tr>
<td class=\"FailureTableHeader\" >Failure</td>
<td class=\"FailureTableHeader\" >Message</td>
</tr>"

        foreach error [ $aResult errors ] {
            
            append buffer [ my printError $aResult $error ]
        }

        foreach failure [ $aResult failures ] {

            append buffer [ my printFailure $aResult $failure ]
        }

        foreach subResult [ $aResult results ] {

            if [ $subResult hasclass ::xounit::TestPass ] { continue }
            if [ $subResult hasclass ::xounit::TestFail ] { continue }
            if [ $subResult hasclass ::xounit::TestError ] { continue }
            if [ $subResult passed ] { continue }

            append buffer [ my printSubResult $aResult $subResult ]
        }

        append buffer "</tbody><table>"

        return $buffer
    
}

printSubResult

Description:
 Format a subtest result.
Arguments:
Overrides:
printSubResult in ::xounit::TestResultsTextFormatter
Code:
  ::xounit::TestResultsWebFormatter instproc printSubResult {result subResult}  {
   

        my instvar textFormatter

        set return "<tr>
<td class=\"FailureTable\">[ $result name ] [ $subResult name ] </td>
<td class=\"FailureTable\">
<a name=\"[ my cleanUpLink $subResult ]\"/>
<pre>[ $textFormatter printSubResult $subResult ]</pre>
</td></tr>"

        return $return
    
}

rssItem

Description:
 Create an RSS item that describes how many tests failed
 in this test report.
Arguments:
Code:
  ::xounit::TestResultsWebFormatter instproc rssItem {results link}  {
   

        set failures [ expr { [ my numberOfErrors $results ] + [ my numberOfFailures $results ] } ]

        set summary "[ clock format [ clock seconds ] -format "%b %d %H:%M:%S" ] Tests fail: $failures"
        
return "
<item>
<title>$summary</title>
<description>$summary</description>
<link>$link</link>
</item>"

    
}

testSummary

Description:
 Format a summary of a list of TestResults.
Arguments:
Overrides:
testSummary in ::xounit::TestResultsTextFormatter
Code:
  ::xounit::TestResultsWebFormatter instproc testSummary {results}  {
   

        set buffer ""

        append buffer "<div class=\"summary\">"
        append buffer "<h1>Test Summary</h1><a name=\"summary\" />"
        append buffer "<table class=\"SummaryTable\"><tbody>"
        append buffer "<tr><td class=\"SummaryTableHeader\">Timestamp</td>"
        append buffer "<td class=\"SummaryTableHeader\">Tests</td>"
        append buffer "<td class=\"SummaryTableHeader\">Failures</td>"
        append buffer "<td class=\"SummaryTableHeader\">Passes</td>"
        append buffer "<td class=\"SummaryTableHeader\">Graph</td>"
        append buffer "</tr><tr>"
        append buffer "<td class=\"SummaryTable\"><span class=\"time\">[ clock format  [ clock seconds ]  -format "%b %d %H:%M:%S" ]</span></td>"
        append buffer "<td class=\"SummaryTable\">[ my numberOfTests $results ]</td>"
        set failures [ expr { [ my numberOfErrors $results ] + [ my numberOfFailures $results ] } ]
        append buffer "<td class=\"SummaryTable\">$failures</td>"
        append buffer "<td class=\"SummaryTable\">[ my numberOfPasses $results ]</td>"
        append buffer "<td class=\"SummaryTable\">"
        append buffer [ my barGraph [ my numberOfPasses $results ] [ my numberOfTests $results ]  ]
        append buffer "</td></tr></tbody></table>"

        set index 0

        append buffer "<h1>Failures</h1><a name=\"failures\" />"
        append buffer "<tr>"
        append buffer "<table class=\"SummaryTable\"><tbody>"
        append buffer "<td class=\"SummaryTableHeader\">Test Name</td>"
        append buffer "<td class=\"SummaryTableHeader\">Failure</td>"
        foreach result $results {
        foreach failure [ concat [ $result errors ] [ $result failures ] ] {
            
            append buffer "</tr><tr>"
            append buffer "<td class=\"SummaryTable\"><a href=\"#[ my cleanUpLink $failure ]\">[ $failure name ]</a></td>"
            append buffer "<td class=\"SummaryTable\"><a href=\"#[ my cleanUpLink $failure ]\">[ $failure test ]</a></td>"
            incr index
        }
        }
        append buffer "</td></tr></tbody></table>"

        append buffer "</div>"

        return $buffer
    
}

writeWebResults

Description:
 Write the formatted TestResults to location and update
 the RSS feed and the html index.html. If the number
 of old test runs is greater than 100 then delete the
 oldest ones over 100.
Arguments:
Code:
  ::xounit::TestResultsWebFormatter instproc writeWebResults {results}  {
   

        my instvar location webPath title url

        global env

        if { ! [ file exists $location ] } {
            file mkdir $location
        }

        set xounit [ ::xounit packagePath ]

        if { ! [ file exists [ file join $location rss.gif ] ] } {
            file copy $xounit/test/rss.gif $location
        }

        if { ! [ file exists [ file join $location spacer.gif ] ] } {
            file copy $xounit/test/spacer.gif $location
        }

        file copy -force $xounit/test/style.css $location

        set resultsName "results[ clock seconds ].html"
        set rssName "rss[ clock seconds ].xml"

        ::xox::withOpenFile [ file join $location $resultsName ]  w file {

            puts $file [ my formatResults $results ]
        }

        ::xox::withOpenFile [ file join $location $rssName ]  w file {

            puts $file [ my rssItem $results "$webPath/$resultsName" ]
        }

        ::xox::withOpenFile [ file join $location feed.xml ]  w feed {

            puts $feed  "<rss version=\"2.0\">

            <channel>

            <title>$title</title>
            <description>Tests</description>
            <link>$url</link>

            "

            set rssFiles [ lsort -decreasing [ glob -nocomplain -directory $location rss*.xml ] ]

            foreach rssFile [ lrange $rssFiles 0 5 ] {

            puts $feed [ ::xox::readFile [ file join $location $rssFile ] ]
            }

            puts $feed  "
            </channel>

            </rss>

            "

        }

        ::xox::withOpenFile [ file join $location index.html ]  w index {

            puts $index "<html><body><a href=\"$url\"><h1>$title</h1></a>"

            set resultsFiles [ lsort -decreasing [ glob -nocomplain -directory $location results*.html ] ]

            puts $index {
            100 Most Recent Test Runs:<br />
            }

            foreach resultsFile [ lrange $resultsFiles 100 end ] {

                set webFile [ file tail $resultsFile ]
                set path [ file dirname $resultsFile ]
                regexp {results(\d+)} $webFile dummy time
                set rssFileName [ file join $path "rss$time.xml" ]


                puts "deleting old results $resultsFile"
                file delete $resultsFile
                puts "deleting old results $rssFileName"
                file delete $rssFileName
            }

            foreach resultsFile [ lrange $resultsFiles 0 100 ] {

                catch {

                set webFile [ file tail $resultsFile ]

                regexp {results(\d+)} $webFile dummy time

                set rssFileName "rss$time.xml"

                set rssDom [ [ dom parse [ ::xox::readFile [ file join $location $rssFileName ] ] ] documentElement ]

                set failures [ [ $rssDom getElementsByTagName description ] text ]

                puts $index "<a href=\"$webFile\" > $failures </a><br/>"
                } 
            }

            puts $index  {<link rel="alternate" type="application/rss+xml"
             title="Test Results Feed"
             href="feed.xml">}

            puts $index {
            <a href="feed.xml" alt="help!" ><img src="rss.gif" /></a>
            }

            puts $index "</body></html>"

        }
    
}