1# Copyright 2015 gRPC authors. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Generate XML and HTML test reports.""" 15 16from __future__ import print_function 17 18try: 19 from mako.runtime import Context 20 from mako.template import Template 21 from mako import exceptions 22except (ImportError): 23 pass # Mako not installed but it is ok. 24import datetime 25import os 26import string 27import xml.etree.cElementTree as ET 28import six 29 30 31def _filter_msg(msg, output_format): 32 """Filters out nonprintable and illegal characters from the message.""" 33 if output_format in ['XML', 'HTML']: 34 # keep whitespaces but remove formfeed and vertical tab characters 35 # that make XML report unparseable. 36 filtered_msg = filter( 37 lambda x: x in string.printable and x != '\f' and x != '\v', 38 msg.decode('UTF-8', 'ignore')) 39 if output_format == 'HTML': 40 filtered_msg = filtered_msg.replace('"', '"') 41 return filtered_msg 42 else: 43 return msg 44 45 46def new_junit_xml_tree(): 47 return ET.ElementTree(ET.Element('testsuites')) 48 49 50def render_junit_xml_report(resultset, 51 report_file, 52 suite_package='grpc', 53 suite_name='tests', 54 replace_dots=True): 55 """Generate JUnit-like XML report.""" 56 tree = new_junit_xml_tree() 57 append_junit_xml_results(tree, resultset, suite_package, suite_name, '1', 58 replace_dots) 59 create_xml_report_file(tree, report_file) 60 61 62def create_xml_report_file(tree, report_file): 63 """Generate JUnit-like report file from xml tree .""" 64 # ensure the report directory exists 65 report_dir = os.path.dirname(os.path.abspath(report_file)) 66 if not os.path.exists(report_dir): 67 os.makedirs(report_dir) 68 tree.write(report_file, encoding='UTF-8') 69 70 71def append_junit_xml_results(tree, 72 resultset, 73 suite_package, 74 suite_name, 75 id, 76 replace_dots=True): 77 """Append a JUnit-like XML report tree with test results as a new suite.""" 78 if replace_dots: 79 # ResultStore UI displays test suite names containing dots only as the component 80 # after the last dot, which results bad info being displayed in the UI. 81 # We replace dots by another character to avoid this problem. 82 suite_name = suite_name.replace('.', '_') 83 testsuite = ET.SubElement( 84 tree.getroot(), 85 'testsuite', 86 id=id, 87 package=suite_package, 88 name=suite_name, 89 timestamp=datetime.datetime.now().isoformat()) 90 failure_count = 0 91 error_count = 0 92 for shortname, results in six.iteritems(resultset): 93 for result in results: 94 xml_test = ET.SubElement(testsuite, 'testcase', name=shortname) 95 if result.elapsed_time: 96 xml_test.set('time', str(result.elapsed_time)) 97 filtered_msg = _filter_msg(result.message, 'XML') 98 if result.state == 'FAILED': 99 ET.SubElement( 100 xml_test, 'failure', message='Failure').text = filtered_msg 101 failure_count += 1 102 elif result.state == 'TIMEOUT': 103 ET.SubElement( 104 xml_test, 'error', message='Timeout').text = filtered_msg 105 error_count += 1 106 elif result.state == 'SKIPPED': 107 ET.SubElement(xml_test, 'skipped', message='Skipped') 108 testsuite.set('failures', str(failure_count)) 109 testsuite.set('errors', str(error_count)) 110 111 112def render_interop_html_report(client_langs, server_langs, test_cases, 113 auth_test_cases, http2_cases, http2_server_cases, 114 resultset, num_failures, cloud_to_prod, 115 prod_servers, http2_interop): 116 """Generate HTML report for interop tests.""" 117 template_file = 'tools/run_tests/interop/interop_html_report.template' 118 try: 119 mytemplate = Template(filename=template_file, format_exceptions=True) 120 except NameError: 121 print( 122 'Mako template is not installed. Skipping HTML report generation.') 123 return 124 except IOError as e: 125 print('Failed to find the template %s: %s' % (template_file, e)) 126 return 127 128 sorted_test_cases = sorted(test_cases) 129 sorted_auth_test_cases = sorted(auth_test_cases) 130 sorted_http2_cases = sorted(http2_cases) 131 sorted_http2_server_cases = sorted(http2_server_cases) 132 sorted_client_langs = sorted(client_langs) 133 sorted_server_langs = sorted(server_langs) 134 sorted_prod_servers = sorted(prod_servers) 135 136 args = { 137 'client_langs': sorted_client_langs, 138 'server_langs': sorted_server_langs, 139 'test_cases': sorted_test_cases, 140 'auth_test_cases': sorted_auth_test_cases, 141 'http2_cases': sorted_http2_cases, 142 'http2_server_cases': sorted_http2_server_cases, 143 'resultset': resultset, 144 'num_failures': num_failures, 145 'cloud_to_prod': cloud_to_prod, 146 'prod_servers': sorted_prod_servers, 147 'http2_interop': http2_interop 148 } 149 150 html_report_out_dir = 'reports' 151 if not os.path.exists(html_report_out_dir): 152 os.mkdir(html_report_out_dir) 153 html_file_path = os.path.join(html_report_out_dir, 'index.html') 154 try: 155 with open(html_file_path, 'w') as output_file: 156 mytemplate.render_context(Context(output_file, **args)) 157 except: 158 print(exceptions.text_error_template().render()) 159 raise 160 161 162def render_perf_profiling_results(output_filepath, profile_names): 163 with open(output_filepath, 'w') as output_file: 164 output_file.write('<ul>\n') 165 for name in profile_names: 166 output_file.write('<li><a href=%s>%s</a></li>\n' % (name, name)) 167 output_file.write('</ul>\n') 168