mirror of https://github.com/grpc/grpc.git
The C based gRPC (C++, Python, Ruby, Objective-C, PHP, C#)
https://grpc.io/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
7.7 KiB
189 lines
7.7 KiB
# Copyright 2015 gRPC authors. |
|
# |
|
# 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. |
|
"""Generate XML and HTML test reports.""" |
|
|
|
try: |
|
from mako import exceptions |
|
from mako.runtime import Context |
|
from mako.template import Template |
|
except (ImportError): |
|
pass # Mako not installed but it is ok. |
|
import datetime |
|
import os |
|
import string |
|
import xml.etree.cElementTree as ET |
|
|
|
import six |
|
|
|
|
|
def _filter_msg(msg, output_format): |
|
"""Filters out nonprintable and illegal characters from the message.""" |
|
if output_format in ['XML', 'HTML']: |
|
if isinstance(msg, bytes): |
|
decoded_msg = msg.decode('UTF-8', 'ignore') |
|
else: |
|
decoded_msg = msg |
|
# keep whitespaces but remove formfeed and vertical tab characters |
|
# that make XML report unparsable. |
|
filtered_msg = ''.join( |
|
filter(lambda x: x in string.printable and x != '\f' and x != '\v', |
|
decoded_msg)) |
|
if output_format == 'HTML': |
|
filtered_msg = filtered_msg.replace('"', '"') |
|
return filtered_msg |
|
else: |
|
return msg |
|
|
|
|
|
def new_junit_xml_tree(): |
|
return ET.ElementTree(ET.Element('testsuites')) |
|
|
|
|
|
def render_junit_xml_report(resultset, |
|
report_file, |
|
suite_package='grpc', |
|
suite_name='tests', |
|
replace_dots=True, |
|
multi_target=False): |
|
"""Generate JUnit-like XML report.""" |
|
if not multi_target: |
|
tree = new_junit_xml_tree() |
|
append_junit_xml_results(tree, resultset, suite_package, suite_name, |
|
'1', replace_dots) |
|
create_xml_report_file(tree, report_file) |
|
else: |
|
# To have each test result displayed as a separate target by the Resultstore/Sponge UI, |
|
# we generate a separate XML report file for each test result |
|
for shortname, results in six.iteritems(resultset): |
|
one_result = {shortname: results} |
|
tree = new_junit_xml_tree() |
|
append_junit_xml_results(tree, one_result, |
|
'%s_%s' % (suite_package, shortname), |
|
'%s_%s' % (suite_name, shortname), '1', |
|
replace_dots) |
|
per_suite_report_file = os.path.join(os.path.dirname(report_file), |
|
shortname, |
|
os.path.basename(report_file)) |
|
create_xml_report_file(tree, per_suite_report_file) |
|
|
|
|
|
def create_xml_report_file(tree, report_file): |
|
"""Generate JUnit-like report file from xml tree .""" |
|
# env variable can be used to override the base location for the reports |
|
base_dir = os.getenv('GRPC_TEST_REPORT_BASE_DIR', None) |
|
if base_dir: |
|
report_file = os.path.join(base_dir, report_file) |
|
# ensure the report directory exists |
|
report_dir = os.path.dirname(os.path.abspath(report_file)) |
|
if not os.path.exists(report_dir): |
|
os.makedirs(report_dir) |
|
tree.write(report_file, encoding='UTF-8') |
|
|
|
|
|
def append_junit_xml_results(tree, |
|
resultset, |
|
suite_package, |
|
suite_name, |
|
id, |
|
replace_dots=True): |
|
"""Append a JUnit-like XML report tree with test results as a new suite.""" |
|
if replace_dots: |
|
# ResultStore UI displays test suite names containing dots only as the component |
|
# after the last dot, which results bad info being displayed in the UI. |
|
# We replace dots by another character to avoid this problem. |
|
suite_name = suite_name.replace('.', '_') |
|
testsuite = ET.SubElement(tree.getroot(), |
|
'testsuite', |
|
id=id, |
|
package=suite_package, |
|
name=suite_name, |
|
timestamp=datetime.datetime.now().isoformat()) |
|
failure_count = 0 |
|
error_count = 0 |
|
for shortname, results in six.iteritems(resultset): |
|
for result in results: |
|
xml_test = ET.SubElement(testsuite, 'testcase', name=shortname) |
|
if result.elapsed_time: |
|
xml_test.set('time', str(result.elapsed_time)) |
|
filtered_msg = _filter_msg(result.message, 'XML') |
|
if result.state == 'FAILED': |
|
ET.SubElement(xml_test, 'failure', |
|
message='Failure').text = filtered_msg |
|
failure_count += 1 |
|
elif result.state == 'TIMEOUT': |
|
ET.SubElement(xml_test, 'error', |
|
message='Timeout').text = filtered_msg |
|
error_count += 1 |
|
elif result.state == 'SKIPPED': |
|
ET.SubElement(xml_test, 'skipped', message='Skipped') |
|
testsuite.set('failures', str(failure_count)) |
|
testsuite.set('errors', str(error_count)) |
|
|
|
|
|
def render_interop_html_report(client_langs, server_langs, test_cases, |
|
auth_test_cases, http2_cases, http2_server_cases, |
|
resultset, num_failures, cloud_to_prod, |
|
prod_servers, http2_interop): |
|
"""Generate HTML report for interop tests.""" |
|
template_file = 'tools/run_tests/interop/interop_html_report.template' |
|
try: |
|
mytemplate = Template(filename=template_file, format_exceptions=True) |
|
except NameError: |
|
print( |
|
'Mako template is not installed. Skipping HTML report generation.') |
|
return |
|
except IOError as e: |
|
print(('Failed to find the template %s: %s' % (template_file, e))) |
|
return |
|
|
|
sorted_test_cases = sorted(test_cases) |
|
sorted_auth_test_cases = sorted(auth_test_cases) |
|
sorted_http2_cases = sorted(http2_cases) |
|
sorted_http2_server_cases = sorted(http2_server_cases) |
|
sorted_client_langs = sorted(client_langs) |
|
sorted_server_langs = sorted(server_langs) |
|
sorted_prod_servers = sorted(prod_servers) |
|
|
|
args = { |
|
'client_langs': sorted_client_langs, |
|
'server_langs': sorted_server_langs, |
|
'test_cases': sorted_test_cases, |
|
'auth_test_cases': sorted_auth_test_cases, |
|
'http2_cases': sorted_http2_cases, |
|
'http2_server_cases': sorted_http2_server_cases, |
|
'resultset': resultset, |
|
'num_failures': num_failures, |
|
'cloud_to_prod': cloud_to_prod, |
|
'prod_servers': sorted_prod_servers, |
|
'http2_interop': http2_interop |
|
} |
|
|
|
html_report_out_dir = 'reports' |
|
if not os.path.exists(html_report_out_dir): |
|
os.mkdir(html_report_out_dir) |
|
html_file_path = os.path.join(html_report_out_dir, 'index.html') |
|
try: |
|
with open(html_file_path, 'w') as output_file: |
|
mytemplate.render_context(Context(output_file, **args)) |
|
except: |
|
print((exceptions.text_error_template().render())) |
|
raise |
|
|
|
|
|
def render_perf_profiling_results(output_filepath, profile_names): |
|
with open(output_filepath, 'w') as output_file: |
|
output_file.write('<ul>\n') |
|
for name in profile_names: |
|
output_file.write('<li><a href=%s>%s</a></li>\n' % (name, name)) |
|
output_file.write('</ul>\n')
|
|
|