[Piglit] [PATCH 2/2] Script to produce results in a format that Jenkins-CI understand.

jfonseca at vmware.com jfonseca at vmware.com
Fri Jan 20 06:56:21 PST 2012


From: José Fonseca <jfonseca at vmware.com>

I've been using piglit on Jenkins-CI (aka Hudson-CI) for a long time, but
it did not use piglit framework, and implied manually duplicating test
lists.  It was an easy way to get things started, but far from ideal.

This is a first step of an attempt to properly add Jenkins-CI support
to piglit, so that new upstream piglit tests can be picked up automatically
without manual intervention.
---
 framework/junit.py      |  361 +++++++++++++++++++++++++++++++++++
 framework/junit.xsl     |  479 +++++++++++++++++++++++++++++++++++++++++++++++
 piglit-summary-junit.py |  128 +++++++++++++
 3 files changed, 968 insertions(+), 0 deletions(-)
 create mode 100644 framework/junit.py
 create mode 100644 framework/junit.xsl
 create mode 100755 piglit-summary-junit.py

diff --git a/framework/junit.py b/framework/junit.py
new file mode 100644
index 0000000..0209c24
--- /dev/null
+++ b/framework/junit.py
@@ -0,0 +1,361 @@
+###########################################################################
+#
+# Copyright 2010-2011 VMware, Inc.
+# All Rights Reserved.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sub license, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice (including the
+# next paragraph) shall be included in all copies or substantial portions
+# of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR
+# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+###########################################################################
+
+"""Testing framework that assists invoking external programs and outputing
+results in ANT's junit XML format, used by Jenkins-CI."""
+
+
+import locale
+import optparse
+import os.path
+import shutil
+import sys
+import time
+
+
+__all__ = [
+    'Error',
+    'Failure',
+    'Main',
+    'Report',
+    'Test',
+    'TestSuite',
+]
+
+
+class Failure(Exception):
+    pass
+
+
+class Error(Exception):
+    pass
+
+
+def escape(s):
+    '''Escape and encode a XML string.'''
+    if not isinstance(s, unicode):
+        #s = s.decode(locale.getpreferredencoding(), 'replace')
+        s = s.decode('ascii', 'ignore')
+    s = s.replace('&', '&amp;')
+    s = s.replace('<', '&lt;')
+    s = s.replace('>', '&gt;')
+    s = s.replace('"', '&quot;')
+    s = s.replace("'", '&apos;')
+    s = s.encode('UTF-8')
+    return s
+
+
+_printable = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r'
+_printable = ''.join([chr(_c) in _printable and chr(_c) or '?' for _c in range(256)])
+del _c
+
+
+class Report:
+    """Write test results in ANT's junit XML format.
+
+    See also:
+    - https://github.com/jenkinsci/jenkins/tree/master/test/src/test/resources/hudson/tasks/junit
+    - http://www.junit.org/node/399
+    - http://wiki.apache.org/ant/Proposals/EnhancedTestReports
+    """
+
+    def __init__(self, filename, time = True):
+        self.path = os.path.dirname(os.path.abspath(filename))
+        if not os.path.exists(self.path):
+            os.makedirs(self.path)
+
+        self.stream = open(filename, 'wt')
+        self.testsuites = []
+        self.inside_testsuite = False
+        self.inside_testcase = False
+        self.time = time
+
+    def start(self):
+        self.stream.write('<?xml version="1.0" encoding="UTF-8" ?>\n')
+        self.stream.write('<?xml-stylesheet type="text/xsl" href="junit.xsl" ?>\n')
+        self.stream.write('<testsuites>\n')
+
+        xsl = os.path.join(os.path.dirname(__file__), 'junit.xsl')
+        try:
+            shutil.copy2(xsl, self.path)
+        except shutil.Error:
+            pass
+
+    def stop(self):
+        if self.inside_testcase:
+            self.stream.write('</testcase>\n')
+            self.inside_testcase = False
+        if self.inside_testsuite:
+            self.stream.write('</testsuite>\n')
+            self.inside_testsuite = False
+        self.stream.write('</testsuites>\n')
+        self.stream.flush()
+        self.stream.close()
+
+    def startSuite(self, name):
+        self.testsuites.append(name)
+
+    def stopSuite(self):
+        if self.inside_testsuite:
+            self.stream.write('</testsuite>\n')
+            self.inside_testsuite = False
+        self.testsuites.pop(-1)
+
+    def startCase(self, name):
+        assert not self.inside_testcase
+        self.inside_testcase = True
+
+        if not self.inside_testsuite:
+            self.stream.write('<testsuite name="%s">\n' % escape('.'.join(self.testsuites)))
+            self.inside_testsuite = True
+
+        self.case_name = name
+        self.buffer = []
+        self.stdout = []
+        self.stderr = []
+        self.start_time = time.time()
+
+    def stopCase(self, duration = None):
+        assert self.inside_testcase
+        self.inside_testcase = False
+
+        name = self.case_name
+
+        self.stream.write('<testcase name="%s"' % escape(name))
+        if duration is None:
+            if self.time:
+                stop_time = time.time()
+                duration = stop_time - self.start_time
+        if duration is not None:
+            self.stream.write(' time="%f"' % duration)
+
+        if not self.buffer and not self.stdout and not self.stderr:
+            self.stream.write('/>\n')
+        else:
+            self.stream.write('>')
+
+            for entry in self.buffer:
+                self.stream.write(entry)
+            if self.stdout:
+                self.stream.write('<system-out>')
+                for text in self.stdout:
+                    self.stream.write(escape(text))
+                self.stream.write('</system-out>')
+            if self.stderr:
+                self.stream.write('<system-err>')
+                for text in self.stderr:
+                    self.stream.write(escape(text))
+                self.stream.write('</system-err>')
+
+            self.stream.write('</testcase>\n')
+
+        self.stream.flush()
+
+    def addStdout(self, text):
+        if isinstance(text, str):
+            text = text.translate(_printable)
+        self.stdout.append(text)
+
+    def addStderr(self, text):
+        if isinstance(text, str):
+            text = text.translate(_printable)
+        self.stderr.append(text)
+
+    def addSkipped(self):
+        self.buffer.append('<skipped/>\n')
+
+    def addError(self, message, stacktrace=""):
+        self.buffer.append('<error message="%s"' % escape(message))
+        if not stacktrace:
+            self.buffer.append('/>')
+        else:
+            self.buffer.append('>')
+            self.buffer.append(escape(stacktrace))
+            self.buffer.append('</error>')
+
+    def addFailure(self, message, stacktrace=""):
+        self.buffer.append('<failure message="%s"' % escape(message))
+        if not stacktrace:
+            self.buffer.append('/>')
+        else:
+            self.buffer.append('>')
+            self.buffer.append(escape(stacktrace))
+            self.buffer.append('</failure>')
+
+    def addMeasurement(self, name, value):
+        '''Embedded a measurement in the standard output.
+
+        https://wiki.jenkins-ci.org/display/JENKINS/Measurement+Plots+Plugin
+        '''
+
+        if value is not None:
+            message = '<measurement><name>%s</name><value>%f</value></measurement>\n' % (name, value)
+            self.addStdout(message)
+
+    def addAttachment(self, path):
+        '''Attach a file.
+
+        https://wiki.jenkins-ci.org/display/JENKINS/JUnit+Attachments+Plugin
+        '''
+
+        attach_dir = os.path.join(self.path, '.'.join(self.testsuites + [self.case_name]))
+        if not os.path.exists(attach_dir):
+            os.makedirs(attach_dir)
+        shutil.copy2(path, attach_dir)
+
+    def addWorkspaceURL(self, path):
+        import urlparse
+        try:
+            workspace_path = os.environ['WORKSPACE']
+            job_url = os.environ['JOB_URL']
+        except KeyError:
+            self.addStdout(path + '\n')
+        else:
+            rel_path = os.path.relpath(path, workspace_path)
+            workspace_url = urlparse.urljoin(job_url, 'ws/')
+            url = urlparse.urljoin(workspace_url, rel_path)
+            if os.path.isdir(path):
+                url += '/'
+            self.addStdout(url + '\n')
+
+
+class BaseTest:
+
+    def _visit(self, report):
+        raise NotImplementedError
+
+    def fail(self, *args):
+        raise Failure(*args)
+
+    def error(self, *args):
+        raise Error(*args)
+
+
+
+class TestSuite(BaseTest):
+
+    def __init__(self, name, tests=()):
+        self.name = name
+        self.tests = []
+        self.addTests(tests)
+
+    def addTest(self, test):
+        self.tests.append(test)
+
+    def addTests(self, tests):
+        for test in tests:
+            self.addTest(test)
+
+    def run(self, filename = None, report = None):
+        if report is None:
+            if filename is None:
+                filename = self.name + '.xml'
+        report = Report(filename)
+        report.start()
+        try:
+            self._visit(report)
+        finally:
+            report.stop()
+
+    def _visit(self, report):
+        report.startSuite(self.name)
+        try:
+            self.test(report)
+        finally:
+            report.stopSuite()
+
+    def test(self, report):
+        for test in self.tests:
+            test._visit(report)
+
+
+class Test(BaseTest):
+
+    def __init__(self, name):
+        self.name = name
+
+    def _visit(self, report):
+        report.startCase(self.name)
+        try:
+            try:
+                return self.test(report)
+            except Failure, ex:
+                report.addFailure(*ex.args)
+            except Error, ex:
+                report.addError(*ex.args)
+            except KeyboardInterrupt:
+                raise
+            except:
+                report.addError(str(sys.exc_value))
+        finally:
+            report.stopCase()
+
+    def test(self, report):
+        pass
+
+
+class Main:
+
+    default_timeout = 5*60
+
+    def __init__(self, name):
+        self.name = name
+
+    def optparser(self):
+        optparser = optparse.OptionParser(usage="\n\t%prog [options] ...")
+        optparser.add_option(
+            '-n', '--dry-run',
+            action="store_true",
+            dest="dry_run", default=False,
+            help="perform a trial run without executing")
+        optparser.add_option(
+            '-t', '--timeout', metavar='SECONDS',
+            type="float", dest="timeout", default = self.default_timeout,
+            help="timeout in seconds [default: %default]")
+        #optparser.add_option(
+        #    '-f', '--filter',
+        #    action='append',
+        #    type="choice", metevar='GLOB',
+        #    dest="filters", default=[],
+        #    help="filter")
+        return optparser
+
+    def create_suite(self):
+        raise NotImplementedError
+
+    def run_suite(self, suite):
+        filename = self.name + '.xml'
+        report = Report(filename)
+        suite.run()
+
+    def main(self):
+        optparser = self.optparser()
+        (self.options, self.args) = optparser.parse_args(sys.argv[1:])
+
+        suite = self.create_suite()
+        self.run_suite(suite)
+
diff --git a/framework/junit.xsl b/framework/junit.xsl
new file mode 100644
index 0000000..7023946
--- /dev/null
+++ b/framework/junit.xsl
@@ -0,0 +1,479 @@
+<?xml version="1.0"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
+        xmlns:lxslt="http://xml.apache.org/xslt"
+        xmlns:stringutils="xalan://org.apache.tools.ant.util.StringUtils">
+<xsl:output method="html" indent="yes" encoding="UTF-8"
+  doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN" />
+<xsl:decimal-format decimal-separator="." grouping-separator="," />
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+ -->
+
+<xsl:param name="TITLE">Unit Test Results.</xsl:param>
+
+<!--
+
+ Sample stylesheet to be used with Ant JUnitReport output.
+
+ It creates a non-framed report that can be useful to send via
+ e-mail or such.
+
+ http://svn.apache.org/viewvc/ant/core/trunk/src/etc/junit-noframes.xsl
+-->
+<xsl:template match="testsuites">
+    <html>
+        <head>
+            <title><xsl:value-of select="$TITLE"/></title>
+    <style type="text/css">
+      body {
+        font:normal 68% verdana,arial,helvetica;
+        color:#000000;
+      }
+      table tr td, table tr th {
+          font-size: 68%;
+      }
+      table.details tr th{
+        font-weight: bold;
+        text-align:left;
+        background:#a6caf0;
+      }
+      table.details tr td{
+        background:#eeeee0;
+      }
+
+      p {
+        line-height:1.5em;
+        margin-top:0.5em; margin-bottom:1.0em;
+      }
+      h1 {
+        margin: 0px 0px 5px; font: 165% verdana,arial,helvetica
+      }
+      h2 {
+        margin-top: 1em; margin-bottom: 0.5em; font: bold 125% verdana,arial,helvetica
+      }
+      h3 {
+        margin-bottom: 0.5em; font: bold 115% verdana,arial,helvetica
+      }
+      h4 {
+        margin-bottom: 0.5em; font: bold 100% verdana,arial,helvetica
+      }
+      h5 {
+        margin-bottom: 0.5em; font: bold 100% verdana,arial,helvetica
+      }
+      h6 {
+        margin-bottom: 0.5em; font: bold 100% verdana,arial,helvetica
+      }
+      .Error {
+        font-weight:bold; color:red;
+      }
+      .Failure {
+        font-weight:bold; color:purple;
+      }
+      .Properties {
+        text-align:right;
+      }
+      </style>
+      <script type="text/javascript" language="JavaScript">
+        var TestCases = new Array();
+        var cur;
+        <xsl:for-each select="./testsuite">
+            <xsl:apply-templates select="properties"/>
+        </xsl:for-each>
+
+       </script>
+       <script type="text/javascript" language="JavaScript"><![CDATA[
+        function displayProperties (name) {
+          var win = window.open('','JUnitSystemProperties','scrollbars=1,resizable=1');
+          var doc = win.document;
+          doc.open();
+          doc.write("<html><head><title>Properties of " + name + "</title>");
+          doc.write("<style>")
+          doc.write("body {font:normal 68% verdana,arial,helvetica; color:#000000; }");
+          doc.write("table tr td, table tr th { font-size: 68%; }");
+          doc.write("table.properties { border-collapse:collapse; border-left:solid 1 #cccccc; border-top:solid 1 #cccccc; padding:5px; }");
+          doc.write("table.properties th { text-align:left; border-right:solid 1 #cccccc; border-bottom:solid 1 #cccccc; background-color:#eeeeee; }");
+          doc.write("table.properties td { font:normal; text-align:left; border-right:solid 1 #cccccc; border-bottom:solid 1 #cccccc; background-color:#fffffff; }");
+          doc.write("h3 { margin-bottom: 0.5em; font: bold 115% verdana,arial,helvetica }");
+          doc.write("</style>");
+          doc.write("</head><body>");
+          doc.write("<h3>Properties of " + name + "</h3>");
+          doc.write("<div align=\"right\"><a href=\"javascript:window.close();\">Close</a></div>");
+          doc.write("<table class='properties'>");
+          doc.write("<tr><th>Name</th><th>Value</th></tr>");
+          for (prop in TestCases[name]) {
+            doc.write("<tr><th>" + prop + "</th><td>" + TestCases[name][prop] + "</td></tr>");
+          }
+          doc.write("</table>");
+          doc.write("</body></html>");
+          doc.close();
+          win.focus();
+        }
+      ]]>
+      </script>
+        </head>
+        <body>
+            <a name="top"></a>
+            <xsl:call-template name="pageHeader"/>
+
+            <!-- Summary part -->
+            <xsl:call-template name="summary"/>
+            <hr size="1" width="95%" align="left"/>
+
+            <!-- Package List part -->
+            <xsl:call-template name="packagelist"/>
+            <hr size="1" width="95%" align="left"/>
+
+            <!-- For each package create its part -->
+            <xsl:call-template name="packages"/>
+            <hr size="1" width="95%" align="left"/>
+
+            <!-- For each class create the  part -->
+            <xsl:call-template name="classes"/>
+
+        </body>
+    </html>
+</xsl:template>
+
+
+
+    <!-- ================================================================== -->
+    <!-- Write a list of all packages with an hyperlink to the anchor of    -->
+    <!-- of the package name.                                               -->
+    <!-- ================================================================== -->
+    <xsl:template name="packagelist">
+        <h2>Packages</h2>
+        Note: package statistics are not computed recursively, they only sum up all of its testsuites numbers.
+        <table class="details" border="0" cellpadding="5" cellspacing="2" width="95%">
+            <xsl:call-template name="testsuite.test.header"/>
+            <!-- list all packages recursively -->
+            <xsl:for-each select="./testsuite[not(./@package = preceding-sibling::testsuite/@package)]">
+                <xsl:sort select="@package"/>
+                <xsl:variable name="testsuites-in-package" select="/testsuites/testsuite[./@package = current()/@package]"/>
+                <xsl:variable name="testCount" select="sum($testsuites-in-package/@tests)"/>
+                <xsl:variable name="errorCount" select="sum($testsuites-in-package/@errors)"/>
+                <xsl:variable name="failureCount" select="sum($testsuites-in-package/@failures)"/>
+                <xsl:variable name="timeCount" select="sum($testsuites-in-package/@time)"/>
+
+                <!-- write a summary for the package -->
+                <tr valign="top">
+                    <!-- set a nice color depending if there is an error/failure -->
+                    <xsl:attribute name="class">
+                        <xsl:choose>
+                            <xsl:when test="$failureCount &gt; 0">Failure</xsl:when>
+                            <xsl:when test="$errorCount &gt; 0">Error</xsl:when>
+                        </xsl:choose>
+                    </xsl:attribute>
+                    <td><a href="#{@package}"><xsl:value-of select="@package"/></a></td>
+                    <td><xsl:value-of select="$testCount"/></td>
+                    <td><xsl:value-of select="$errorCount"/></td>
+                    <td><xsl:value-of select="$failureCount"/></td>
+                    <td>
+                    <xsl:call-template name="display-time">
+                        <xsl:with-param name="value" select="$timeCount"/>
+                    </xsl:call-template>
+                    </td>
+                    <td><xsl:value-of select="$testsuites-in-package/@timestamp"/></td>
+                    <td><xsl:value-of select="$testsuites-in-package/@hostname"/></td>
+                </tr>
+            </xsl:for-each>
+        </table>
+    </xsl:template>
+
+
+    <!-- ================================================================== -->
+    <!-- Write a package level report                                       -->
+    <!-- It creates a table with values from the document:                  -->
+    <!-- Name | Tests | Errors | Failures | Time                            -->
+    <!-- ================================================================== -->
+    <xsl:template name="packages">
+        <!-- create an anchor to this package name -->
+        <xsl:for-each select="/testsuites/testsuite[not(./@package = preceding-sibling::testsuite/@package)]">
+            <xsl:sort select="@package"/>
+                <a name="{@package}"></a>
+                <h3>Package <xsl:value-of select="@package"/></h3>
+
+                <table class="details" border="0" cellpadding="5" cellspacing="2" width="95%">
+                    <xsl:call-template name="testsuite.test.header"/>
+
+                    <!-- match the testsuites of this package -->
+                    <xsl:apply-templates select="/testsuites/testsuite[./@package = current()/@package]" mode="print.test"/>
+                </table>
+                <a href="#top">Back to top</a>
+                <p/>
+                <p/>
+        </xsl:for-each>
+    </xsl:template>
+
+    <xsl:template name="classes">
+        <xsl:for-each select="testsuite">
+            <xsl:sort select="@name"/>
+            <!-- create an anchor to this class name -->
+            <a name="{@name}"></a>
+            <h3>TestCase <xsl:value-of select="@name"/></h3>
+
+            <table class="details" border="0" cellpadding="5" cellspacing="2" width="95%">
+              <xsl:call-template name="testcase.test.header"/>
+              <!--
+              test can even not be started at all (failure to load the class)
+              so report the error directly
+              -->
+                <xsl:if test="./error">
+                    <tr class="Error">
+                        <td colspan="4"><xsl:apply-templates select="./error"/></td>
+                    </tr>
+                </xsl:if>
+                <xsl:apply-templates select="./testcase" mode="print.test"/>
+            </table>
+            <div class="Properties">
+                <a>
+                    <xsl:attribute name="href">javascript:displayProperties('<xsl:value-of select="@package"/>.<xsl:value-of select="@name"/>');</xsl:attribute>
+                    Properties &#187;
+                </a>
+            </div>
+            <p/>
+
+            <a href="#top">Back to top</a>
+        </xsl:for-each>
+    </xsl:template>
+
+    <xsl:template name="summary">
+        <h2>Summary</h2>
+        <xsl:variable name="testCount" select="sum(testsuite/@tests)"/>
+        <xsl:variable name="errorCount" select="sum(testsuite/@errors)"/>
+        <xsl:variable name="failureCount" select="sum(testsuite/@failures)"/>
+        <xsl:variable name="timeCount" select="sum(testsuite/@time)"/>
+        <xsl:variable name="successRate" select="($testCount - $failureCount - $errorCount) div $testCount"/>
+        <table class="details" border="0" cellpadding="5" cellspacing="2" width="95%">
+        <tr valign="top">
+            <th>Tests</th>
+            <th>Failures</th>
+            <th>Errors</th>
+            <th>Success rate</th>
+            <th>Time</th>
+        </tr>
+        <tr valign="top">
+            <xsl:attribute name="class">
+                <xsl:choose>
+                    <xsl:when test="$failureCount &gt; 0">Failure</xsl:when>
+                    <xsl:when test="$errorCount &gt; 0">Error</xsl:when>
+                </xsl:choose>
+            </xsl:attribute>
+            <td><xsl:value-of select="$testCount"/></td>
+            <td><xsl:value-of select="$failureCount"/></td>
+            <td><xsl:value-of select="$errorCount"/></td>
+            <td>
+                <xsl:call-template name="display-percent">
+                    <xsl:with-param name="value" select="$successRate"/>
+                </xsl:call-template>
+            </td>
+            <td>
+                <xsl:call-template name="display-time">
+                    <xsl:with-param name="value" select="$timeCount"/>
+                </xsl:call-template>
+            </td>
+
+        </tr>
+        </table>
+        <table border="0" width="95%">
+        <tr>
+        <td style="text-align: justify;">
+        Note: <i>failures</i> are anticipated and checked for with assertions while <i>errors</i> are unanticipated.
+        </td>
+        </tr>
+        </table>
+    </xsl:template>
+
+  <!--
+   Write properties into a JavaScript data structure.
+   This is based on the original idea by Erik Hatcher (ehatcher at apache.org)
+   -->
+  <xsl:template match="properties">
+    cur = TestCases['<xsl:value-of select="../@package"/>.<xsl:value-of select="../@name"/>'] = new Array();
+    <xsl:for-each select="property">
+    <xsl:sort select="@name"/>
+        cur['<xsl:value-of select="@name"/>'] = '<xsl:call-template name="JS-escape"><xsl:with-param name="string" select="@value"/></xsl:call-template>';
+    </xsl:for-each>
+  </xsl:template>
+
+<!-- Page HEADER -->
+<xsl:template name="pageHeader">
+    <h1><xsl:value-of select="$TITLE"/></h1>
+    <table width="100%">
+    <tr>
+        <td align="left"></td>
+        <td align="right">Designed for use with <a href='http://www.junit.org'>JUnit</a> and <a href='http://ant.apache.org/ant'>Ant</a>.</td>
+    </tr>
+    </table>
+    <hr size="1"/>
+</xsl:template>
+
+<xsl:template match="testsuite" mode="header">
+    <tr valign="top">
+        <th width="80%">Name</th>
+        <th>Tests</th>
+        <th>Errors</th>
+        <th>Failures</th>
+        <th nowrap="nowrap">Time(s)</th>
+    </tr>
+</xsl:template>
+
+<!-- class header -->
+<xsl:template name="testsuite.test.header">
+    <tr valign="top">
+        <th width="80%">Name</th>
+        <th>Tests</th>
+        <th>Errors</th>
+        <th>Failures</th>
+        <th nowrap="nowrap">Time(s)</th>
+        <th nowrap="nowrap">Time Stamp</th>
+        <th>Host</th>
+    </tr>
+</xsl:template>
+
+<!-- method header -->
+<xsl:template name="testcase.test.header">
+    <tr valign="top">
+        <th>Name</th>
+        <th>Status</th>
+        <th width="80%">Type</th>
+        <th nowrap="nowrap">Time(s)</th>
+    </tr>
+</xsl:template>
+
+
+<!-- class information -->
+<xsl:template match="testsuite" mode="print.test">
+    <tr valign="top">
+        <!-- set a nice color depending if there is an error/failure -->
+        <xsl:attribute name="class">
+            <xsl:choose>
+                <xsl:when test="@failures[.&gt; 0]">Failure</xsl:when>
+                <xsl:when test="@errors[.&gt; 0]">Error</xsl:when>
+            </xsl:choose>
+        </xsl:attribute>
+
+        <!-- print testsuite information -->
+        <td><a href="#{@name}"><xsl:value-of select="@name"/></a></td>
+        <td><xsl:value-of select="@tests"/></td>
+        <td><xsl:value-of select="@errors"/></td>
+        <td><xsl:value-of select="@failures"/></td>
+        <td>
+            <xsl:call-template name="display-time">
+                <xsl:with-param name="value" select="@time"/>
+            </xsl:call-template>
+        </td>
+        <td><xsl:apply-templates select="@timestamp"/></td>
+        <td><xsl:apply-templates select="@hostname"/></td>
+    </tr>
+</xsl:template>
+
+<xsl:template match="testcase" mode="print.test">
+    <tr valign="top">
+        <xsl:attribute name="class">
+            <xsl:choose>
+                <xsl:when test="failure | error">Error</xsl:when>
+            </xsl:choose>
+        </xsl:attribute>
+        <td><xsl:value-of select="@name"/></td>
+        <xsl:choose>
+            <xsl:when test="failure">
+                <td>Failure</td>
+                <td><xsl:apply-templates select="failure"/></td>
+            </xsl:when>
+            <xsl:when test="error">
+                <td>Error</td>
+                <td><xsl:apply-templates select="error"/></td>
+            </xsl:when>
+            <xsl:otherwise>
+                <td>Success</td>
+                <td></td>
+            </xsl:otherwise>
+        </xsl:choose>
+        <td>
+            <xsl:call-template name="display-time">
+                <xsl:with-param name="value" select="@time"/>
+            </xsl:call-template>
+        </td>
+    </tr>
+</xsl:template>
+
+
+<xsl:template match="failure">
+    <xsl:call-template name="display-failures"/>
+</xsl:template>
+
+<xsl:template match="error">
+    <xsl:call-template name="display-failures"/>
+</xsl:template>
+
+<!-- Style for the error and failure in the tescase template -->
+<xsl:template name="display-failures">
+    <xsl:choose>
+        <xsl:when test="not(@message)">N/A</xsl:when>
+        <xsl:otherwise>
+            <xsl:value-of select="@message"/>
+        </xsl:otherwise>
+    </xsl:choose>
+    <!-- display the stacktrace -->
+    <code>
+        <br/><br/>
+        <xsl:call-template name="br-replace">
+            <xsl:with-param name="word" select="."/>
+        </xsl:call-template>
+    </code>
+    <!-- the later is better but might be problematic for non-21" monitors... -->
+    <!--pre><xsl:value-of select="."/></pre-->
+</xsl:template>
+
+<xsl:template name="JS-escape">
+    <xsl:param name="string"/>
+    <xsl:param name="tmp1" select="stringutils:replace(string($string),'\','\\')"/>
+    <xsl:param name="tmp2" select="stringutils:replace(string($tmp1),&quot;'&quot;,&quot;\&apos;&quot;)"/>
+    <xsl:value-of select="$tmp2"/>
+</xsl:template>
+
+
+<!--
+    template that will convert a carriage return into a br tag
+    @param word the text from which to convert CR to BR tag
+-->
+<xsl:template name="br-replace">
+    <xsl:param name="word"/>
+    <xsl:choose>
+      <xsl:when test="contains($word, '&#xa;')">
+        <xsl:value-of select="substring-before($word, '&#xa;')"/>
+        <br/>
+        <xsl:call-template name="br-replace">
+          <xsl:with-param name="word" select="substring-after($word, '&#xa;')"/>
+        </xsl:call-template>
+      </xsl:when>
+      <xsl:otherwise>
+	<xsl:value-of select="$word"/>
+      </xsl:otherwise>
+    </xsl:choose>
+</xsl:template>
+
+<xsl:template name="display-time">
+    <xsl:param name="value"/>
+    <xsl:value-of select="format-number($value,'0.000')"/>
+</xsl:template>
+
+<xsl:template name="display-percent">
+    <xsl:param name="value"/>
+    <xsl:value-of select="format-number($value,'0.00%')"/>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/piglit-summary-junit.py b/piglit-summary-junit.py
new file mode 100755
index 0000000..9cfc06f
--- /dev/null
+++ b/piglit-summary-junit.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python
+#
+# Copyright 2010-2011 VMware, Inc.
+# All Rights Reserved.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sub license, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice (including the
+# next paragraph) shall be included in all copies or substantial portions
+# of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR
+# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+import optparse
+import os
+import sys
+
+import framework.core
+import framework.summary
+from framework import junit
+
+
+class Writer:
+
+	def __init__(self, filename):
+		self.report = junit.Report(filename)
+		self.path = []
+
+	def write(self, args):
+		results = [framework.core.loadTestResults(arg) for arg in args]
+		summary = framework.summary.Summary(results)
+
+		self.report.start()
+		try:
+			for test in summary.allTests():
+				self.write_test(summary, test)
+		finally:
+			self.enter_path([])
+			self.report.stop()
+
+	def write_test(self, summary, test):
+		self.enter_path(test.path.split('/'))
+		for j in range(len(summary.testruns)):
+			tr = summary.testruns[j]
+			tr_name, _ = tr.name.rsplit('.', 1)
+			result = test.results[j]
+
+			self.report.startCase(tr_name)
+			duration = None
+			try:
+				try:
+					self.report.addStdout(result['command'] + '\n')
+				except KeyError:
+					pass
+
+				try:
+					self.report.addStderr(result['info'])
+				except KeyError:
+					pass
+
+				success = result.get('result')
+				if success == 'pass':
+					pass
+				elif success == 'skip':
+					self.report.addSkipped()
+				else:
+					self.report.addFailure(success)
+
+				try:
+					duration = float(result['time'])
+				except KeyError:
+					pass
+			finally:
+				self.report.stopCase(duration)
+
+	def enter_path(self, path):
+		ancestor = 0
+		try:
+			while self.path[ancestor] == path[ancestor]:
+				ancestor += 1
+		except IndexError:
+			pass
+
+		for dirname in self.path[ancestor:]:
+			self.report.stopSuite()
+
+		for dirname in path[ancestor:]:
+			self.report.startSuite(dirname)
+
+		self.path = path
+
+
+def main():
+	optparser = optparse.OptionParser(
+		usage="\n\t%prog [options] [test.results] ...",
+		version="%%prog")
+	optparser.add_option(
+		'-o', '--output', metavar='FILE',
+		type="string", dest="output", default='piglit.xml',
+		help="output filename")
+	(options, args) = optparser.parse_args(sys.argv[1:])
+
+	if not args:
+		optparser.error('need to specify one test result')
+		usage()
+
+	writer = Writer(options.output)
+	writer.write(args)
+
+
+if __name__ == "__main__":
+	main()
+
+
+# vim:set sw=4 ts=4 noet:
-- 
1.7.7.3



More information about the Piglit mailing list