# Copyright 2015, Google Inc. # 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 of Google Inc. 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. """Generate XML and HTML test reports.""" import os import string import xml.etree.cElementTree as ET def _filter_msg(msg, output_format): """Filters out nonprintable and illegal characters from the message.""" if output_format in ['XML', 'HTML']: # keep whitespaces but remove formfeed and vertical tab characters # that make XML report unparseable. filtered_msg = filter( lambda x: x in string.printable and x != '\f' and x != '\v', msg.decode(errors='ignore')) if output_format == 'HTML': filtered_msg = filtered_msg.replace('"', '"') return filtered_msg else: return msg def render_xml_report(resultset, xml_report): """Generate JUnit-like XML report.""" root = ET.Element('testsuites') testsuite = ET.SubElement(root, 'testsuite', id='1', package='grpc', name='tests') for shortname, results in resultset.iteritems(): for result in results: xml_test = ET.SubElement(testsuite, 'testcase', name=shortname) if result.elapsed_time: xml_test.set('time', str(result.elapsed_time)) ET.SubElement(xml_test, 'system-out').text = _filter_msg(result.message, 'XML') if result.state == 'FAILED': ET.SubElement(xml_test, 'failure', message='Failure') elif result.state == 'TIMEOUT': ET.SubElement(xml_test, 'error', message='Timeout') tree = ET.ElementTree(root) tree.write(xml_report, encoding='UTF-8') # TODO(adelez): Use mako template. def fill_one_test_result(shortname, resultset, html_str): if shortname in resultset: # Because interop tests does not have runs_per_test flag, each test is run # once. So there should only be one element for each result. result = resultset[shortname][0] if result.state == 'PASSED': html_str = '%sPASS\n' % html_str else: tooltip = '' if result.returncode > 0 or result.message: if result.returncode > 0: tooltip = 'returncode: %d ' % result.returncode if result.message: escaped_msg = _filter_msg(result.message, 'HTML') tooltip = '%smessage: %s' % (tooltip, escaped_msg) if result.state == 'FAILED': html_str = '%s' % html_str if tooltip: html_str = ('%sFAIL\n' % (html_str, tooltip)) else: html_str = '%sFAIL\n' % html_str elif result.state == 'TIMEOUT': html_str = '%s' % html_str if tooltip: html_str = ('%sTIMEOUT\n' % (html_str, tooltip)) else: html_str = '%sTIMEOUT\n' % html_str else: html_str = '%sNot implemented\n' % html_str return html_str def render_html_report(client_langs, server_langs, test_cases, auth_test_cases, http2_cases, resultset, num_failures, cloud_to_prod, http2_interop): """Generate html report.""" sorted_test_cases = sorted(test_cases) sorted_auth_test_cases = sorted(auth_test_cases) sorted_http2_cases = sorted(http2_cases) sorted_client_langs = sorted(client_langs) sorted_server_langs = sorted(server_langs) html_str = ('\n' '\n' 'Interop Test Result\n' '\n') if num_failures > 1: html_str = ( '%s

%d tests failed!

\n' % (html_str, num_failures)) elif num_failures: html_str = ( '%s

%d test failed!

\n' % (html_str, num_failures)) else: html_str = ( '%s

All tests passed!

\n' % html_str) if cloud_to_prod: # Each column header is the client language. html_str = ('%s

Cloud to Prod

\n' '\n' '\n' '\n') % html_str for client_lang in sorted_client_langs: html_str = '%s\n' % html_str for test_case in sorted_test_cases + sorted_auth_test_cases: html_str = '%s\n' % (html_str, test_case) for client_lang in sorted_client_langs: if not test_case in sorted_auth_test_cases: shortname = 'cloud_to_prod:%s:%s' % (client_lang, test_case) else: shortname = 'cloud_to_prod_auth:%s:%s' % (client_lang, test_case) html_str = fill_one_test_result(shortname, resultset, html_str) html_str = '%s\n' % html_str html_str = '%s
Client languages ►%s\n' % (html_str, client_lang) html_str = '%s
%s
\n' % html_str if http2_interop: # Each column header is the server language. html_str = ('%s

HTTP/2 Interop

\n' '\n' '\n' '\n') % html_str for server_lang in sorted_server_langs: html_str = '%s\n' % html_str for test_case in sorted_http2_cases: html_str = '%s\n' % (html_str, test_case) # Fill up the cells with test result. for server_lang in sorted_server_langs: shortname = 'cloud_to_cloud:%s:%s_server:%s' % ( "http2", server_lang, test_case) html_str = fill_one_test_result(shortname, resultset, html_str) if cloud_to_prod: shortname = 'cloud_to_prod:%s:%s' % ("http2", test_case) html_str = fill_one_test_result(shortname, resultset, html_str) html_str = '%s\n' % html_str html_str = '%s
Servers ►
' 'Test Cases ▼
%s\n' % (html_str, server_lang) if cloud_to_prod: html_str = '%s%s\n' % (html_str, "prod") html_str = '%s
%s
\n' % html_str if server_langs: for test_case in sorted_test_cases: # Each column header is the client language. html_str = ('%s

%s

\n' '\n' '\n' '\n') % (html_str, test_case) for client_lang in sorted_client_langs: html_str = '%s\n' % html_str # Each row head is the server language. for server_lang in sorted_server_langs: html_str = '%s\n' % (html_str, server_lang) # Fill up the cells with test result. for client_lang in sorted_client_langs: shortname = 'cloud_to_cloud:%s:%s_server:%s' % ( client_lang, server_lang, test_case) html_str = fill_one_test_result(shortname, resultset, html_str) html_str = '%s\n' % html_str html_str = '%s
Client languages ►
' 'Server languages ▼
%s\n' % (html_str, client_lang) html_str = '%s
%s
\n' % html_str html_str = ('%s\n' '\n' '\n' '') % html_str # Write to reports/index.html as set up in Jenkins plugin. html_report_dir = 'reports' if not os.path.exists(html_report_dir): os.mkdir(html_report_dir) html_file_path = os.path.join(html_report_dir, 'index.html') with open(html_file_path, 'w') as f: f.write(html_str)