Merge "Remove the local copy of subunit2html"
This commit is contained in:
@@ -39,7 +39,7 @@ function process_testr_artifacts {
|
|||||||
elif [ -f ".testrepository/0" ] ; then
|
elif [ -f ".testrepository/0" ] ; then
|
||||||
$bin_path/testr last --subunit > ./testrepository.subunit
|
$bin_path/testr last --subunit > ./testrepository.subunit
|
||||||
fi
|
fi
|
||||||
$PYTHON $script_path/subunit2html.py ./testrepository.subunit testr_results.html
|
/usr/os-testr-env/bin/subunit2html ./testrepository.subunit testr_results.html
|
||||||
SUBUNIT_SIZE=$(du -k ./testrepository.subunit | awk '{print $1}')
|
SUBUNIT_SIZE=$(du -k ./testrepository.subunit | awk '{print $1}')
|
||||||
gzip -9 ./testrepository.subunit
|
gzip -9 ./testrepository.subunit
|
||||||
gzip -9 ./testr_results.html
|
gzip -9 ./testr_results.html
|
||||||
|
@@ -1,742 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
#
|
|
||||||
# Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# Licensed 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Utility to convert a subunit stream to an html results file.
|
|
||||||
Code is adapted from the pyunit Html test runner at
|
|
||||||
http://tungwaiyip.info/software/HTMLTestRunner.html
|
|
||||||
|
|
||||||
Takes two arguments. First argument is path to subunit log file, second
|
|
||||||
argument is path of desired output file. Second argument is optional,
|
|
||||||
defaults to 'results.html'.
|
|
||||||
|
|
||||||
Original HTMLTestRunner License:
|
|
||||||
------------------------------------------------------------------------
|
|
||||||
Copyright (c) 2004-2007, Wai Yip Tung
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
* Neither the name Wai Yip Tung nor the names of its contributors may be
|
|
||||||
used to endorse or promote products derived from this software without
|
|
||||||
specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
|
||||||
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
||||||
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
|
||||||
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
||||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import collections
|
|
||||||
import datetime
|
|
||||||
import io
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
from xml.sax import saxutils
|
|
||||||
|
|
||||||
import subunit
|
|
||||||
import testtools
|
|
||||||
|
|
||||||
__version__ = '0.1'
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateData(object):
|
|
||||||
"""
|
|
||||||
Define a HTML template for report customerization and generation.
|
|
||||||
|
|
||||||
Overall structure of an HTML report
|
|
||||||
|
|
||||||
HTML
|
|
||||||
+------------------------+
|
|
||||||
|<html> |
|
|
||||||
| <head> |
|
|
||||||
| |
|
|
||||||
| STYLESHEET |
|
|
||||||
| +----------------+ |
|
|
||||||
| | | |
|
|
||||||
| +----------------+ |
|
|
||||||
| |
|
|
||||||
| </head> |
|
|
||||||
| |
|
|
||||||
| <body> |
|
|
||||||
| |
|
|
||||||
| HEADING |
|
|
||||||
| +----------------+ |
|
|
||||||
| | | |
|
|
||||||
| +----------------+ |
|
|
||||||
| |
|
|
||||||
| REPORT |
|
|
||||||
| +----------------+ |
|
|
||||||
| | | |
|
|
||||||
| +----------------+ |
|
|
||||||
| |
|
|
||||||
| ENDING |
|
|
||||||
| +----------------+ |
|
|
||||||
| | | |
|
|
||||||
| +----------------+ |
|
|
||||||
| |
|
|
||||||
| </body> |
|
|
||||||
|</html> |
|
|
||||||
+------------------------+
|
|
||||||
"""
|
|
||||||
|
|
||||||
STATUS = {
|
|
||||||
0: 'pass',
|
|
||||||
1: 'fail',
|
|
||||||
2: 'error',
|
|
||||||
3: 'skip',
|
|
||||||
}
|
|
||||||
|
|
||||||
DEFAULT_TITLE = 'Unit Test Report'
|
|
||||||
DEFAULT_DESCRIPTION = ''
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
|
||||||
# HTML Template
|
|
||||||
|
|
||||||
HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
|
||||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<title>%(title)s</title>
|
|
||||||
<meta name="generator" content="%(generator)s"/>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
|
||||||
%(stylesheet)s
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script language="javascript" type="text/javascript"><!--
|
|
||||||
output_list = Array();
|
|
||||||
|
|
||||||
/* level - 0:Summary; 1:Failed; 2:All */
|
|
||||||
function showCase(level) {
|
|
||||||
trs = document.getElementsByTagName("tr");
|
|
||||||
for (var i = 0; i < trs.length; i++) {
|
|
||||||
tr = trs[i];
|
|
||||||
id = tr.id;
|
|
||||||
if (id.substr(0,2) == 'ft') {
|
|
||||||
if (level < 1) {
|
|
||||||
tr.className = 'hiddenRow';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tr.className = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (id.substr(0,2) == 'pt') {
|
|
||||||
if (level > 1) {
|
|
||||||
tr.className = '';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
tr.className = 'hiddenRow';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function showClassDetail(cid, count) {
|
|
||||||
var id_list = Array(count);
|
|
||||||
var toHide = 1;
|
|
||||||
for (var i = 0; i < count; i++) {
|
|
||||||
tid0 = 't' + cid.substr(1) + '.' + (i+1);
|
|
||||||
tid = 'f' + tid0;
|
|
||||||
tr = document.getElementById(tid);
|
|
||||||
if (!tr) {
|
|
||||||
tid = 'p' + tid0;
|
|
||||||
tr = document.getElementById(tid);
|
|
||||||
}
|
|
||||||
id_list[i] = tid;
|
|
||||||
if (tr.className) {
|
|
||||||
toHide = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var i = 0; i < count; i++) {
|
|
||||||
tid = id_list[i];
|
|
||||||
if (toHide) {
|
|
||||||
document.getElementById('div_'+tid).style.display = 'none'
|
|
||||||
document.getElementById(tid).className = 'hiddenRow';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
document.getElementById(tid).className = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function showTestDetail(div_id){
|
|
||||||
var details_div = document.getElementById(div_id)
|
|
||||||
var displayState = details_div.style.display
|
|
||||||
// alert(displayState)
|
|
||||||
if (displayState != 'block' ) {
|
|
||||||
displayState = 'block'
|
|
||||||
details_div.style.display = 'block'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
details_div.style.display = 'none'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function html_escape(s) {
|
|
||||||
s = s.replace(/&/g,'&');
|
|
||||||
s = s.replace(/</g,'<');
|
|
||||||
s = s.replace(/>/g,'>');
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* obsoleted by detail in <div>
|
|
||||||
function showOutput(id, name) {
|
|
||||||
var w = window.open("", //url
|
|
||||||
name,
|
|
||||||
"resizable,scrollbars,status,width=800,height=450");
|
|
||||||
d = w.document;
|
|
||||||
d.write("<pre>");
|
|
||||||
d.write(html_escape(output_list[id]));
|
|
||||||
d.write("\n");
|
|
||||||
d.write("<a href='javascript:window.close()'>close</a>\n");
|
|
||||||
d.write("</pre>\n");
|
|
||||||
d.close();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
--></script>
|
|
||||||
|
|
||||||
%(heading)s
|
|
||||||
%(report)s
|
|
||||||
%(ending)s
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
# variables: (title, generator, stylesheet, heading, report, ending)
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
|
||||||
# Stylesheet
|
|
||||||
#
|
|
||||||
# alternatively use a <link> for external style sheet, e.g.
|
|
||||||
# <link rel="stylesheet" href="$url" type="text/css">
|
|
||||||
|
|
||||||
STYLESHEET_TMPL = """
|
|
||||||
<style type="text/css" media="screen">
|
|
||||||
body { font-family: verdana, arial, helvetica, sans-serif;
|
|
||||||
font-size: 80%; }
|
|
||||||
table { font-size: 100%; width: 100%;}
|
|
||||||
pre { font-size: 80%; }
|
|
||||||
|
|
||||||
/* -- heading -------------------------------------------------------------- */
|
|
||||||
h1 {
|
|
||||||
font-size: 16pt;
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
.heading {
|
|
||||||
margin-top: 0ex;
|
|
||||||
margin-bottom: 1ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading .attribute {
|
|
||||||
margin-top: 1ex;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading .description {
|
|
||||||
margin-top: 4ex;
|
|
||||||
margin-bottom: 6ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- css div popup -------------------------------------------------------- */
|
|
||||||
a.popup_link {
|
|
||||||
}
|
|
||||||
|
|
||||||
a.popup_link:hover {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup_window {
|
|
||||||
display: none;
|
|
||||||
overflow-x: scroll;
|
|
||||||
/*border: solid #627173 1px; */
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #E6E6D6;
|
|
||||||
font-family: "Ubuntu Mono", "Lucida Console", "Courier New", monospace;
|
|
||||||
text-align: left;
|
|
||||||
font-size: 8pt;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
/* -- report --------------------------------------------------------------- */
|
|
||||||
#show_detail_line {
|
|
||||||
margin-top: 3ex;
|
|
||||||
margin-bottom: 1ex;
|
|
||||||
}
|
|
||||||
#result_table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
border: 1px solid #777;
|
|
||||||
}
|
|
||||||
#header_row {
|
|
||||||
font-weight: bold;
|
|
||||||
color: white;
|
|
||||||
background-color: #777;
|
|
||||||
}
|
|
||||||
#result_table td {
|
|
||||||
border: 1px solid #777;
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
#total_row { font-weight: bold; }
|
|
||||||
.passClass { background-color: #6c6; }
|
|
||||||
.failClass { background-color: #c60; }
|
|
||||||
.errorClass { background-color: #c00; }
|
|
||||||
.passCase { color: #6c6; }
|
|
||||||
.failCase { color: #c60; font-weight: bold; }
|
|
||||||
.errorCase { color: #c00; font-weight: bold; }
|
|
||||||
.hiddenRow { display: none; }
|
|
||||||
.testcase { margin-left: 2em; }
|
|
||||||
td.testname {width: 40%}
|
|
||||||
td.small {width: 40px}
|
|
||||||
|
|
||||||
/* -- ending --------------------------------------------------------------- */
|
|
||||||
#ending {
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
"""
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
|
||||||
# Heading
|
|
||||||
#
|
|
||||||
|
|
||||||
HEADING_TMPL = """<div class='heading'>
|
|
||||||
<h1>%(title)s</h1>
|
|
||||||
%(parameters)s
|
|
||||||
<p class='description'>%(description)s</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
""" # variables: (title, parameters, description)
|
|
||||||
|
|
||||||
HEADING_ATTRIBUTE_TMPL = """
|
|
||||||
<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
|
|
||||||
""" # variables: (name, value)
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
|
||||||
# Report
|
|
||||||
#
|
|
||||||
|
|
||||||
REPORT_TMPL = """
|
|
||||||
<p id='show_detail_line'>Show
|
|
||||||
<a href='javascript:showCase(0)'>Summary</a>
|
|
||||||
<a href='javascript:showCase(1)'>Failed</a>
|
|
||||||
<a href='javascript:showCase(2)'>All</a>
|
|
||||||
</p>
|
|
||||||
<table id='result_table'>
|
|
||||||
<colgroup>
|
|
||||||
<col align='left' />
|
|
||||||
<col align='right' />
|
|
||||||
<col align='right' />
|
|
||||||
<col align='right' />
|
|
||||||
<col align='right' />
|
|
||||||
<col align='right' />
|
|
||||||
<col align='right' />
|
|
||||||
<col align='right' />
|
|
||||||
</colgroup>
|
|
||||||
<tr id='header_row'>
|
|
||||||
<td>Test Group/Test case</td>
|
|
||||||
<td>Count</td>
|
|
||||||
<td>Pass</td>
|
|
||||||
<td>Fail</td>
|
|
||||||
<td>Error</td>
|
|
||||||
<td>Skip</td>
|
|
||||||
<td>View</td>
|
|
||||||
<td> </td>
|
|
||||||
</tr>
|
|
||||||
%(test_list)s
|
|
||||||
<tr id='total_row'>
|
|
||||||
<td>Total</td>
|
|
||||||
<td>%(count)s</td>
|
|
||||||
<td>%(Pass)s</td>
|
|
||||||
<td>%(fail)s</td>
|
|
||||||
<td>%(error)s</td>
|
|
||||||
<td>%(skip)s</td>
|
|
||||||
<td> </td>
|
|
||||||
<td> </td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
""" # variables: (test_list, count, Pass, fail, error)
|
|
||||||
|
|
||||||
REPORT_CLASS_TMPL = r"""
|
|
||||||
<tr class='%(style)s'>
|
|
||||||
<td class="testname">%(desc)s</td>
|
|
||||||
<td class="small">%(count)s</td>
|
|
||||||
<td class="small">%(Pass)s</td>
|
|
||||||
<td class="small">%(fail)s</td>
|
|
||||||
<td class="small">%(error)s</td>
|
|
||||||
<td class="small">%(skip)s</td>
|
|
||||||
<td class="small"><a href="javascript:showClassDetail('%(cid)s',%(count)s)"
|
|
||||||
>Detail</a></td>
|
|
||||||
<td> </td>
|
|
||||||
</tr>
|
|
||||||
""" # variables: (style, desc, count, Pass, fail, error, cid)
|
|
||||||
|
|
||||||
REPORT_TEST_WITH_OUTPUT_TMPL = r"""
|
|
||||||
<tr id='%(tid)s' class='%(Class)s'>
|
|
||||||
<td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
|
|
||||||
<td colspan='7' align='left'>
|
|
||||||
|
|
||||||
<!--css div popup start-->
|
|
||||||
<a class="popup_link" onfocus='this.blur();'
|
|
||||||
href="javascript:showTestDetail('div_%(tid)s')" >
|
|
||||||
%(status)s</a>
|
|
||||||
|
|
||||||
<div id='div_%(tid)s' class="popup_window">
|
|
||||||
<div style='text-align: right; color:red;cursor:pointer'>
|
|
||||||
<a onfocus='this.blur();'
|
|
||||||
onclick="document.getElementById('div_%(tid)s').style.display = 'none' " >
|
|
||||||
[x]</a>
|
|
||||||
</div>
|
|
||||||
<pre>
|
|
||||||
%(script)s
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
<!--css div popup end-->
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
""" # variables: (tid, Class, style, desc, status)
|
|
||||||
|
|
||||||
REPORT_TEST_NO_OUTPUT_TMPL = r"""
|
|
||||||
<tr id='%(tid)s' class='%(Class)s'>
|
|
||||||
<td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
|
|
||||||
<td colspan='6' align='center'>%(status)s</td>
|
|
||||||
</tr>
|
|
||||||
""" # variables: (tid, Class, style, desc, status)
|
|
||||||
|
|
||||||
REPORT_TEST_OUTPUT_TMPL = r"""
|
|
||||||
%(id)s: %(output)s
|
|
||||||
""" # variables: (id, output)
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
|
||||||
# ENDING
|
|
||||||
#
|
|
||||||
|
|
||||||
ENDING_TMPL = """<div id='ending'> </div>"""
|
|
||||||
|
|
||||||
# -------------------- The end of the Template class -------------------
|
|
||||||
|
|
||||||
|
|
||||||
class ClassInfoWrapper(object):
|
|
||||||
def __init__(self, name, mod):
|
|
||||||
self.name = name
|
|
||||||
self.mod = mod
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "%s" % (self.name)
|
|
||||||
|
|
||||||
|
|
||||||
class HtmlOutput(testtools.TestResult):
|
|
||||||
"""Output test results in html."""
|
|
||||||
|
|
||||||
def __init__(self, html_file='result.html'):
|
|
||||||
super(HtmlOutput, self).__init__()
|
|
||||||
self.success_count = 0
|
|
||||||
self.failure_count = 0
|
|
||||||
self.error_count = 0
|
|
||||||
self.skip_count = 0
|
|
||||||
self.result = []
|
|
||||||
self.html_file = html_file
|
|
||||||
|
|
||||||
def addSuccess(self, test):
|
|
||||||
self.success_count += 1
|
|
||||||
output = test.shortDescription()
|
|
||||||
if output is None:
|
|
||||||
output = test.id()
|
|
||||||
self.result.append((0, test, output, ''))
|
|
||||||
|
|
||||||
def addSkip(self, test, err):
|
|
||||||
output = test.shortDescription()
|
|
||||||
if output is None:
|
|
||||||
output = test.id()
|
|
||||||
self.skip_count += 1
|
|
||||||
self.result.append((3, test, output, ''))
|
|
||||||
|
|
||||||
def addError(self, test, err):
|
|
||||||
output = test.shortDescription()
|
|
||||||
if output is None:
|
|
||||||
output = test.id()
|
|
||||||
# Skipped tests are handled by SkipTest Exceptions.
|
|
||||||
#if err[0] == SkipTest:
|
|
||||||
# self.skip_count += 1
|
|
||||||
# self.result.append((3, test, output, ''))
|
|
||||||
else:
|
|
||||||
self.error_count += 1
|
|
||||||
_exc_str = self.formatErr(err)
|
|
||||||
self.result.append((2, test, output, _exc_str))
|
|
||||||
|
|
||||||
def addFailure(self, test, err):
|
|
||||||
print(test)
|
|
||||||
self.failure_count += 1
|
|
||||||
_exc_str = self.formatErr(err)
|
|
||||||
output = test.shortDescription()
|
|
||||||
if output is None:
|
|
||||||
output = test.id()
|
|
||||||
self.result.append((1, test, output, _exc_str))
|
|
||||||
|
|
||||||
def formatErr(self, err):
|
|
||||||
exctype, value, tb = err
|
|
||||||
return ''.join(traceback.format_exception(exctype, value, tb))
|
|
||||||
|
|
||||||
def stopTestRun(self):
|
|
||||||
super(HtmlOutput, self).stopTestRun()
|
|
||||||
self.stopTime = datetime.datetime.now()
|
|
||||||
report_attrs = self._getReportAttributes()
|
|
||||||
generator = 'subunit2html %s' % __version__
|
|
||||||
heading = self._generate_heading(report_attrs)
|
|
||||||
report = self._generate_report()
|
|
||||||
ending = self._generate_ending()
|
|
||||||
output = TemplateData.HTML_TMPL % dict(
|
|
||||||
title=saxutils.escape(TemplateData.DEFAULT_TITLE),
|
|
||||||
generator=generator,
|
|
||||||
stylesheet=TemplateData.STYLESHEET_TMPL,
|
|
||||||
heading=heading,
|
|
||||||
report=report,
|
|
||||||
ending=ending,
|
|
||||||
)
|
|
||||||
if self.html_file:
|
|
||||||
with open(self.html_file, 'wb') as html_file:
|
|
||||||
html_file.write(output.encode('utf8'))
|
|
||||||
|
|
||||||
def _getReportAttributes(self):
|
|
||||||
"""Return report attributes as a list of (name, value)."""
|
|
||||||
status = []
|
|
||||||
if self.success_count:
|
|
||||||
status.append('Pass %s' % self.success_count)
|
|
||||||
if self.failure_count:
|
|
||||||
status.append('Failure %s' % self.failure_count)
|
|
||||||
if self.error_count:
|
|
||||||
status.append('Error %s' % self.error_count)
|
|
||||||
if self.skip_count:
|
|
||||||
status.append('Skip %s' % self.skip_count)
|
|
||||||
if status:
|
|
||||||
status = ' '.join(status)
|
|
||||||
else:
|
|
||||||
status = 'none'
|
|
||||||
return [
|
|
||||||
('Status', status),
|
|
||||||
]
|
|
||||||
|
|
||||||
def _generate_heading(self, report_attrs):
|
|
||||||
a_lines = []
|
|
||||||
for name, value in report_attrs:
|
|
||||||
line = TemplateData.HEADING_ATTRIBUTE_TMPL % dict(
|
|
||||||
name=saxutils.escape(name),
|
|
||||||
value=saxutils.escape(value),
|
|
||||||
)
|
|
||||||
a_lines.append(line)
|
|
||||||
heading = TemplateData.HEADING_TMPL % dict(
|
|
||||||
title=saxutils.escape(TemplateData.DEFAULT_TITLE),
|
|
||||||
parameters=''.join(a_lines),
|
|
||||||
description=saxutils.escape(TemplateData.DEFAULT_DESCRIPTION),
|
|
||||||
)
|
|
||||||
return heading
|
|
||||||
|
|
||||||
def _generate_report(self):
|
|
||||||
rows = []
|
|
||||||
sortedResult = self._sortResult(self.result)
|
|
||||||
for cid, (cls, cls_results) in enumerate(sortedResult):
|
|
||||||
# subtotal for a class
|
|
||||||
np = nf = ne = ns = 0
|
|
||||||
for n, t, o, e in cls_results:
|
|
||||||
if n == 0:
|
|
||||||
np += 1
|
|
||||||
elif n == 1:
|
|
||||||
nf += 1
|
|
||||||
elif n == 2:
|
|
||||||
ne += 1
|
|
||||||
else:
|
|
||||||
ns += 1
|
|
||||||
|
|
||||||
# format class description
|
|
||||||
if cls.mod == "__main__":
|
|
||||||
name = cls.name
|
|
||||||
else:
|
|
||||||
name = "%s" % (cls.name)
|
|
||||||
doc = cls.__doc__ and cls.__doc__.split("\n")[0] or ""
|
|
||||||
desc = doc and '%s: %s' % (name, doc) or name
|
|
||||||
|
|
||||||
row = TemplateData.REPORT_CLASS_TMPL % dict(
|
|
||||||
style=(ne > 0 and 'errorClass' or nf > 0
|
|
||||||
and 'failClass' or 'passClass'),
|
|
||||||
desc = desc,
|
|
||||||
count = np + nf + ne + ns,
|
|
||||||
Pass = np,
|
|
||||||
fail = nf,
|
|
||||||
error = ne,
|
|
||||||
skip = ns,
|
|
||||||
cid = 'c%s' % (cid + 1),
|
|
||||||
)
|
|
||||||
rows.append(row)
|
|
||||||
|
|
||||||
for tid, (n, t, o, e) in enumerate(cls_results):
|
|
||||||
self._generate_report_test(rows, cid, tid, n, t, o, e)
|
|
||||||
|
|
||||||
report = TemplateData.REPORT_TMPL % dict(
|
|
||||||
test_list=''.join(rows),
|
|
||||||
count=str(self.success_count + self.failure_count +
|
|
||||||
self.error_count + self.skip_count),
|
|
||||||
Pass=str(self.success_count),
|
|
||||||
fail=str(self.failure_count),
|
|
||||||
error=str(self.error_count),
|
|
||||||
skip=str(self.skip_count),
|
|
||||||
)
|
|
||||||
return report
|
|
||||||
|
|
||||||
def _sortResult(self, result_list):
|
|
||||||
# unittest does not seems to run in any particular order.
|
|
||||||
# Here at least we want to group them together by class.
|
|
||||||
rmap = {}
|
|
||||||
classes = []
|
|
||||||
for n, t, o, e in result_list:
|
|
||||||
if hasattr(t, '_tests'):
|
|
||||||
for inner_test in t._tests:
|
|
||||||
self._add_cls(rmap, classes, inner_test,
|
|
||||||
(n, inner_test, o, e))
|
|
||||||
else:
|
|
||||||
self._add_cls(rmap, classes, t, (n, t, o, e))
|
|
||||||
classort = lambda s: str(s)
|
|
||||||
sortedclasses = sorted(classes, key=classort)
|
|
||||||
r = [(cls, rmap[str(cls)]) for cls in sortedclasses]
|
|
||||||
return r
|
|
||||||
|
|
||||||
def _add_cls(self, rmap, classes, test, data_tuple):
|
|
||||||
if hasattr(test, 'test'):
|
|
||||||
test = test.test
|
|
||||||
if test.__class__ == subunit.RemotedTestCase:
|
|
||||||
#print(test._RemotedTestCase__description.rsplit('.', 1)[0])
|
|
||||||
cl = test._RemotedTestCase__description.rsplit('.', 1)[0]
|
|
||||||
mod = cl.rsplit('.', 1)[0]
|
|
||||||
cls = ClassInfoWrapper(cl, mod)
|
|
||||||
else:
|
|
||||||
cls = ClassInfoWrapper(str(test.__class__), str(test.__module__))
|
|
||||||
if not str(cls) in rmap:
|
|
||||||
rmap[str(cls)] = []
|
|
||||||
classes.append(cls)
|
|
||||||
rmap[str(cls)].append(data_tuple)
|
|
||||||
|
|
||||||
def _generate_report_test(self, rows, cid, tid, n, t, o, e):
|
|
||||||
# e.g. 'pt1.1', 'ft1.1', etc
|
|
||||||
# ptx.x for passed/skipped tests and ftx.x for failed/errored tests.
|
|
||||||
has_output = bool(o or e)
|
|
||||||
tid = ((n == 0 or n == 3) and
|
|
||||||
'p' or 'f') + 't%s.%s' % (cid + 1, tid + 1)
|
|
||||||
name = t.id().split('.')[-1]
|
|
||||||
# if shortDescription is not the function name, use it
|
|
||||||
if t.shortDescription().find(name) == -1:
|
|
||||||
doc = t.shortDescription()
|
|
||||||
else:
|
|
||||||
doc = None
|
|
||||||
desc = doc and ('%s: %s' % (name, doc)) or name
|
|
||||||
tmpl = (has_output and TemplateData.REPORT_TEST_WITH_OUTPUT_TMPL
|
|
||||||
or TemplateData.REPORT_TEST_NO_OUTPUT_TMPL)
|
|
||||||
|
|
||||||
script = TemplateData.REPORT_TEST_OUTPUT_TMPL % dict(
|
|
||||||
id=tid,
|
|
||||||
output=saxutils.escape(o + e),
|
|
||||||
)
|
|
||||||
|
|
||||||
row = tmpl % dict(
|
|
||||||
tid=tid,
|
|
||||||
Class=((n == 0 or n == 3) and 'hiddenRow' or 'none'),
|
|
||||||
style=(n == 2 and 'errorCase' or
|
|
||||||
(n == 1 and 'failCase' or 'none')),
|
|
||||||
desc=desc,
|
|
||||||
script=script,
|
|
||||||
status=TemplateData.STATUS[n],
|
|
||||||
)
|
|
||||||
rows.append(row)
|
|
||||||
if not has_output:
|
|
||||||
return
|
|
||||||
|
|
||||||
def _generate_ending(self):
|
|
||||||
return TemplateData.ENDING_TMPL
|
|
||||||
|
|
||||||
def startTestRun(self):
|
|
||||||
super(HtmlOutput, self).startTestRun()
|
|
||||||
|
|
||||||
|
|
||||||
class FileAccumulator(testtools.StreamResult):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(FileAccumulator, self).__init__()
|
|
||||||
self.route_codes = collections.defaultdict(io.BytesIO)
|
|
||||||
|
|
||||||
def status(self, **kwargs):
|
|
||||||
if kwargs.get('file_name') != 'stdout':
|
|
||||||
return
|
|
||||||
file_bytes = kwargs.get('file_bytes')
|
|
||||||
if not file_bytes:
|
|
||||||
return
|
|
||||||
route_code = kwargs.get('route_code')
|
|
||||||
stream = self.route_codes[route_code]
|
|
||||||
stream.write(file_bytes)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print("Need at least one argument: path to subunit log.")
|
|
||||||
exit(1)
|
|
||||||
subunit_file = sys.argv[1]
|
|
||||||
if len(sys.argv) > 2:
|
|
||||||
html_file = sys.argv[2]
|
|
||||||
else:
|
|
||||||
html_file = 'results.html'
|
|
||||||
|
|
||||||
html_result = HtmlOutput(html_file)
|
|
||||||
stream = open(subunit_file, 'rb')
|
|
||||||
|
|
||||||
# Feed the subunit stream through both a V1 and V2 parser.
|
|
||||||
# Depends on having the v2 capable libraries installed.
|
|
||||||
# First V2.
|
|
||||||
# Non-v2 content and captured non-test output will be presented as file
|
|
||||||
# segments called stdout.
|
|
||||||
suite = subunit.ByteStreamToStreamResult(stream, non_subunit_name='stdout')
|
|
||||||
# The HTML output code is in legacy mode.
|
|
||||||
result = testtools.StreamToExtendedDecorator(html_result)
|
|
||||||
# Divert non-test output
|
|
||||||
accumulator = FileAccumulator()
|
|
||||||
result = testtools.StreamResultRouter(result)
|
|
||||||
result.add_rule(accumulator, 'test_id', test_id=None)
|
|
||||||
result.startTestRun()
|
|
||||||
suite.run(result)
|
|
||||||
# Now reprocess any found stdout content as V1 subunit
|
|
||||||
for bytes_io in accumulator.route_codes.values():
|
|
||||||
bytes_io.seek(0)
|
|
||||||
suite = subunit.ProtocolTestCase(bytes_io)
|
|
||||||
suite.run(html_result)
|
|
||||||
result.stopTestRun()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
Reference in New Issue
Block a user