diff --git a/.github/workflows/infra_tests.yml b/.github/workflows/infra_tests.yml index a8c275810..8b3ed96bc 100644 --- a/.github/workflows/infra_tests.yml +++ b/.github/workflows/infra_tests.yml @@ -32,6 +32,6 @@ jobs: sudo env "PATH=$PATH" gcloud components install beta cloud-datastore-emulator - name: Run infra tests - run: sudo env "PATH=$PATH" INTEGRATION_TESTS=1 python infra/presubmit.py infra-tests + run: sudo env "PATH=$PATH" INTEGRATION_TESTS=1 python infra/presubmit.py infra-tests -p diff --git a/infra/ci/build_test.py b/infra/ci/build_test.py index 59fa3cf5c..9da67e253 100644 --- a/infra/ci/build_test.py +++ b/infra/ci/build_test.py @@ -16,9 +16,14 @@ """Tests for build.py""" import os +import sys import unittest from unittest import mock +# pylint: disable=wrong-import-position +INFRA_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(INFRA_DIR) + from ci import build diff --git a/infra/ci/requirements.txt b/infra/ci/requirements.txt index 2a21c2af5..48d2ae758 100644 --- a/infra/ci/requirements.txt +++ b/infra/ci/requirements.txt @@ -2,5 +2,7 @@ parameterized==0.7.4 pyfakefs==4.1.0 pylint==2.5.3 +pytest==6.2.1 +pytest-xdist==2.2.0 PyYAML==5.3.1 yapf==0.30.0 diff --git a/infra/presubmit.py b/infra/presubmit.py index a67ba0b7f..bd5e9c327 100755 --- a/infra/presubmit.py +++ b/infra/presubmit.py @@ -18,17 +18,12 @@ import argparse import os -import re import subprocess import sys import unittest import yaml _SRC_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -TEST_BLOCKLIST = [ - # Test errors with error: "ModuleNotFoundError: No module named 'apt'". - re.compile(r'.*\/infra\/base-images\/base-sanitizer-libs-builder'), -] def _is_project_file(actual_path, expected_filename): @@ -345,32 +340,49 @@ def get_changed_files(): return [os.path.abspath(f) for f in changed_files] -def is_test_dir_blocklisted(directory): - """Returns True if |directory| is blocklisted.""" - for blocklisted_regex in TEST_BLOCKLIST: - if blocklisted_regex.search(directory): - return True - return False +def run_build_tests(): + """Runs build tests because they can't be run in parallel.""" + suite_list = [ + unittest.TestLoader().discover(os.path.join(_SRC_ROOT, 'infra', 'build'), + pattern='*_test.py'), + ] + suite = unittest.TestSuite(suite_list) + print('Running build tests.') + result = unittest.TextTestRunner().run(suite) + return not result.failures and not result.errors -def run_tests(_=None): - """Run all unit tests.""" +def run_nonbuild_tests(parallel): + """Run all tests but build tests. Do it in parallel if |parallel|. The reason + why we exclude build tests is because they use an emulator that prevents them + from being used in parallel.""" + # We look for all project directories because otherwise pytest won't run tests + # that are not in valid modules (e.g. "base-images"). relevant_dirs = set() all_files = get_all_files() for file_path in all_files: directory = os.path.dirname(file_path) - if is_test_dir_blocklisted(directory): - continue relevant_dirs.add(directory) - suite_list = [] - for relevant_dir in relevant_dirs: - suite_list.append(unittest.TestLoader().discover(relevant_dir, - pattern='*_test.py')) - suite = unittest.TestSuite(suite_list) - result = unittest.TextTestRunner().run(suite) + # Use ignore-glob because ignore doesn't seem to work properly with the way we + # pass directories to pytest. + command = [ + 'pytest', + # Test errors with error: "ModuleNotFoundError: No module named 'apt'. + '--ignore-glob=infra/base-images/base-sanitizer-libs-builder/*', + '--ignore-glob=infra/build/*', + ] + if parallel: + command.extend(['-n', 'auto']) + command += list(relevant_dirs) + print('Running non-build tests.') + return subprocess.run(command, check=False).returncode == 0 - return not result.failures and not result.errors + +def run_tests(_=None, parallel=False): + """Runs all unit tests.""" + success = run_nonbuild_tests(parallel) + return success and run_build_tests() def get_all_files(): @@ -387,10 +399,16 @@ def main(): parser.add_argument('command', choices=['format', 'lint', 'license', 'infra-tests'], nargs='?') - parser.add_argument('--all-files', + parser.add_argument('-a', + '--all-files', action='store_true', help='Run presubmit check(s) on all files', default=False) + parser.add_argument('-p', + '--parallel', + action='store_true', + help='Run tests in parallel.', + default=False) args = parser.parse_args() if args.all_files: @@ -414,7 +432,7 @@ def main(): return bool_to_returncode(success) if args.command == 'infra-tests': - success = run_tests(relevant_files) + success = run_tests(relevant_files, parallel=args.parallel) return bool_to_returncode(success) # Do all the checks (but no tests). diff --git a/infra/pytest.ini b/infra/pytest.ini new file mode 100644 index 000000000..d9bb3737e --- /dev/null +++ b/infra/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +python_files = *_test.py \ No newline at end of file