 12ca9dd582
			
		
	
	12ca9dd582
	
	
	
		
			
			This commit fixes pep8 issues that were added when subunit2html was copied into the repo. As part of this the subunit2html was missing a license header for the additions made to it on top of the original HTMLTestRunner script it was based on. The addition of the copyright header mirrors project-config change: I6fc16d316012fd3e1c196f74df25d725a310f6dc Change-Id: Ieb42d5baddb3e1446fcd50197136f605324323aa
		
			
				
	
	
		
			740 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			740 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/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:
 | |
|             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()
 |