Files
Brian Johnson 45b32bd16a Fix android component unit tests
Always continue on fail for `--output_xml`
fix json2xunit.py when elapsed_time_ms = null (crashes)
add missing data deps
2025-08-21 15:32:13 -07:00

114 lines
4.2 KiB
Python
Executable File

#!/usr/bin/env vpython3
# Copyright (c) 2021 The Brave Authors. All rights reserved.
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at https://mozilla.org/MPL/2.0/.
"""
On Android the `--output' switch to `npm run test', which produces xunit
reports, doesn't work (https://github.com/brave/brave-browser/issues/15595).
This script reads json on stdin (`--json-results-file') and prints xunit xml
on stdout.
This is a temporary hack until `--output' on Android is fixed. Only one global
test iteration is supported (it seems only one is used anyway). In per-test
iterations, only one is reported - a successful one, if it exists, otherwise a
failed one.
"""
import collections
import re
import sys
import json
from operator import add
from string import printable
from functools import reduce
def pick_iteration(test_case, iterations):
"""Pick the test iteration that provides the most relevant feedback"""
def score(iteration):
score = 1000000 if iteration['status'] == 'SUCCESS' else 0
score += len(iteration['output_snippet'])
started_this_test = re.compile(r'\[ RUN \] [\w/#\.]*' +
re.escape(test_case) + '\\n')
score += 7500 if started_this_test.match(
iteration['output_snippet']) else 0
for string in ['Stack Trace:', 'Expected:', 'Actual:']:
score += 2500 if string in iteration['output_snippet'] else 0
return score
return sorted(iterations, key=score, reverse=True)[0]
def transform(input_json):
"""Read json input and return a dictionary with information necessary to
produce the xunit output"""
output = collections.defaultdict(lambda: {
'xml': '',
'test_count': 0,
'failure_count': 0
})
if not input_json['per_iteration_data']:
return output
test_results = input_json['per_iteration_data'][0]
for test_fullname, iterations in test_results.items():
delim = ('#' if '#' in test_fullname else
'/' if '/' in test_fullname else '.')
test_suite, test_case = test_fullname.rsplit(delim, maxsplit=1)
iteration = pick_iteration(test_case, iterations)
output[test_suite]['test_count'] += 1
elapsed_time_ms = int(iteration["elapsed_time_ms"]
) if iteration["elapsed_time_ms"] else 0
output[test_suite]['xml'] += (f'<testcase name="{test_case}" '
f'time="{elapsed_time_ms/100.0}">')
if iteration['status'] == 'SUCCESS':
if iteration['output_snippet']:
sanitized_output = ''.join(
filter(lambda x: x in printable,
iteration['output_snippet']))
output[test_suite]['xml'] += (
f"<system-out><![CDATA["
f"{sanitized_output}]]></system-out>")
else:
output[test_suite]['failure_count'] += 1
sanitized_output = ''.join(
filter(lambda x: x in printable, iteration['output_snippet']))
output[test_suite]['xml'] += (
f'<failure message="failed"><![CDATA['
f'{sanitized_output}]]></failure>')
output[test_suite]['xml'] += "</testcase>"
return output
def main():
"""Read json on stdin and print xunit on stdout"""
output = transform(json.load(sys.stdin))
test_count = reduce(add, (ts['test_count'] for ts in output.values()), 0)
failure_count = reduce(add,
(ts['failure_count'] for ts in output.values()), 0)
print(
f'<?xml version="1.0" encoding="UTF-8"?>\n<testsuites name="tests" '
f'tests="{test_count}" errors="0" failures="{failure_count}" skip="0">',
end='')
for test_suite in output.keys():
print(
f'<testsuite name="{test_suite}" '
f'tests="{output[test_suite]["test_count"]}" errors="0" failures='
f'"{output[test_suite]["failure_count"]}" '
f'skip="0">{output[test_suite]["xml"]}</testsuite>',
end='')
print('</testsuites>', end='')
if __name__ == "__main__":
main()