191 lines
6.7 KiB
Python
191 lines
6.7 KiB
Python
# 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
|
|
|
|
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
|
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',
|
|
'--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)
|