1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
|
# Copyright 2021 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This is a library for wrapping Rust test executables in a way that is
compatible with the requirements of the `main_program` module.
"""
import argparse
import os
import re
import subprocess
import sys
import exe_util
import main_program
import test_results
def _format_test_name(test_executable_name, test_case_name):
assert '//' not in test_executable_name
assert '/' not in test_case_name
test_case_name = '/'.join(test_case_name.split('::'))
return '{}//{}'.format(test_executable_name, test_case_name)
def _parse_test_name(test_name):
assert '//' in test_name
assert '::' not in test_name
test_executable_name, test_case_name = test_name.split('//', 1)
test_case_name = '::'.join(test_case_name.split('/'))
return test_executable_name, test_case_name
def _scrape_test_list(output, test_executable_name):
"""Scrapes stdout from running a Rust test executable with
--list and --format=terse.
Args:
output: A string with the full stdout of a Rust test executable.
test_executable_name: A string. Used as a prefix in "full" test names
in the returned results.
Returns:
A list of strings - a list of all test names.
"""
TEST_SUFFIX = ': test'
BENCHMARK_SUFFIX = ': benchmark'
test_case_names = []
for line in output.splitlines():
if line.endswith(TEST_SUFFIX):
test_case_names.append(line[:-len(TEST_SUFFIX)])
elif line.endswith(BENCHMARK_SUFFIX):
continue
else:
raise ValueError(
'Unexpected format of a list of tests: {}'.format(output))
test_names = [
_format_test_name(test_executable_name, test_case_name)
for test_case_name in test_case_names
]
return test_names
def _scrape_test_results(output, test_executable_name,
list_of_expected_test_case_names):
"""Scrapes stdout from running a Rust test executable with
--test --format=pretty.
Args:
output: A string with the full stdout of a Rust test executable.
test_executable_name: A string. Used as a prefix in "full" test names
in the returned TestResult objects.
list_of_expected_test_case_names: A list of strings - expected test case
names (from the perspective of a single executable / with no prefix).
Returns:
A list of test_results.TestResult objects.
"""
results = []
regex = re.compile(r'^test ([:\w]+) \.\.\. (\w+)')
for line in output.splitlines():
match = regex.match(line.strip())
if not match:
continue
test_case_name = match.group(1)
if test_case_name not in list_of_expected_test_case_names:
continue
actual_test_result = match.group(2)
if actual_test_result == 'ok':
actual_test_result = 'PASS'
elif actual_test_result == 'FAILED':
actual_test_result = 'FAIL'
elif actual_test_result == 'ignored':
actual_test_result = 'SKIP'
test_name = _format_test_name(test_executable_name, test_case_name)
results.append(test_results.TestResult(test_name, actual_test_result))
return results
def _get_exe_specific_tests(expected_test_executable_name, list_of_test_names):
results = []
for test_name in list_of_test_names:
actual_test_executable_name, test_case_name = _parse_test_name(
test_name)
if actual_test_executable_name != expected_test_executable_name:
continue
results.append(test_case_name)
return results
class _TestExecutableWrapper:
def __init__(self, path_to_test_executable):
if not os.path.isfile(path_to_test_executable):
raise ValueError('No such file: ' + path_to_test_executable)
self._path_to_test_executable = path_to_test_executable
self._name_of_test_executable, _ = os.path.splitext(
os.path.basename(path_to_test_executable))
def list_all_tests(self):
"""Returns:
A list of strings - a list of all test names.
"""
args = [self._path_to_test_executable, '--list', '--format=terse']
output = subprocess.check_output(args, text=True)
return _scrape_test_list(output, self._name_of_test_executable)
def run_tests(self, list_of_tests_to_run):
"""Runs tests listed in `list_of_tests_to_run`. Ignores tests for other
test executables.
Args:
list_of_tests_to_run: A list of strings (a list of test names).
Returns:
A list of test_results.TestResult objects.
"""
list_of_tests_to_run = _get_exe_specific_tests(
self._name_of_test_executable, list_of_tests_to_run)
if not list_of_tests_to_run:
return []
# TODO(lukasza): Avoid passing all test names on the cmdline (might
# require adding support to Rust test executables for reading cmdline
# args from a file).
# TODO(lukasza): Avoid scraping human-readable output (try using
# JSON output once it stabilizes; hopefully preserving human-readable
# output to the terminal).
args = [
self._path_to_test_executable, '--test', '--format=pretty',
'--color=always', '--exact'
]
args.extend(list_of_tests_to_run)
print('Running tests from {}...'.format(self._name_of_test_executable))
output = exe_util.run_and_tee_output(args)
print('Running tests from {}... DONE.'.format(
self._name_of_test_executable))
print()
return _scrape_test_results(output, self._name_of_test_executable,
list_of_tests_to_run)
def _parse_args(args):
description = 'Wrapper for running Rust unit tests with support for ' \
'Chromium test filters, sharding, and test output.'
parser = argparse.ArgumentParser(description=description)
main_program.add_cmdline_args(parser)
parser.add_argument('--rust-test-executable',
action='append',
dest='rust_test_executables',
default=[],
help=argparse.SUPPRESS,
metavar='FILEPATH',
required=True)
return parser.parse_args(args=args)
if __name__ == '__main__':
parsed_args = _parse_args(sys.argv[1:])
rust_tests_wrappers = [
_TestExecutableWrapper(path)
for path in parsed_args.rust_test_executables
]
main_program.main(rust_tests_wrappers, parsed_args, os.environ)
|