[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('&', '&')
+ s = s.replace('<', '<')
+ s = s.replace('>', '>')
+ s = s.replace('"', '"')
+ s = s.replace("'", ''')
+ 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 > 0">Failure</xsl:when>
+ <xsl:when test="$errorCount > 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 »
+ </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 > 0">Failure</xsl:when>
+ <xsl:when test="$errorCount > 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[.> 0]">Failure</xsl:when>
+ <xsl:when test="@errors[.> 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),"'","\'")"/>
+ <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, '
')">
+ <xsl:value-of select="substring-before($word, '
')"/>
+ <br/>
+ <xsl:call-template name="br-replace">
+ <xsl:with-param name="word" select="substring-after($word, '
')"/>
+ </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