From 770f933bf6783959a3ee4437bf236ce12f75aad9 Mon Sep 17 00:00:00 2001 From: Asher Foa <1268088+asherf@users.noreply.github.com> Date: Sun, 19 Apr 2020 17:37:56 -0700 Subject: [PATCH] Add the ability to write junit test results file for pytest runs. Problem With the transition of pytest to the v2 engine, we lost the ability to write junit-xml results files when test runs. This can't be solved by passing options thru directly to pytest since it expect a junit-xml result file name, which means we need a unique name for each target. Solution Introduce the ability to calculate the junit-xml file name based on a user provided direcotry (option) and the test target name. Result JUnit xml result files which can be processed by CI (CircleCI for example) so failing test can be seen w/o digging thru endless logs. --- .../pants/backend/python/rules/pytest_runner.py | 14 +++++++++++++- src/python/pants/core/goals/test.py | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/python/pants/backend/python/rules/pytest_runner.py b/src/python/pants/backend/python/rules/pytest_runner.py index 7f5a29371a8..b12d2c4f8c8 100644 --- a/src/python/pants/backend/python/rules/pytest_runner.py +++ b/src/python/pants/backend/python/rules/pytest_runner.py @@ -3,6 +3,7 @@ import functools from dataclasses import dataclass +from pathlib import Path from typing import Any, List, Optional, Tuple, Union, cast from pants.backend.python.rules.importable_python_sources import ImportablePythonSources @@ -59,6 +60,7 @@ class TestTargetSetup: args: Tuple[str, ...] input_files_digest: Digest timeout_seconds: Optional[int] + results_file_path: Optional[str] # Prevent this class from being detected by pytest as a test class. __test__ = False @@ -210,11 +212,18 @@ async def setup_pytest_for_target( coverage_args.extend(["--cov", package]) specified_source_file_names = sorted(specified_source_files.snapshot.files) + results_dir = test_options.values.results_dir + results_file_path = ( + (Path(results_dir) / f"{config.address.path_safe_spec}.xml").absolute().as_posix() + if results_dir + else None + ) return TestTargetSetup( test_runner_pex=test_runner_pex, args=(*pytest.options.args, *coverage_args, *specified_source_file_names), input_files_digest=merged_input_files, timeout_seconds=config.timeout.calculate_from_global_options(pytest), + results_file_path=results_file_path, ) @@ -228,7 +237,10 @@ async def run_python_test( test_options: TestOptions, ) -> TestResult: """Runs pytest for one target.""" - env = {"PYTEST_ADDOPTS": f"--color={'yes' if global_options.options.colors else 'no'}"} + add_opts = [f"--color={'yes' if global_options.options.colors else 'no'}"] + if test_setup.results_file_path: + add_opts.append(f"--junitxml={test_setup.results_file_path}") + env = {"PYTEST_ADDOPTS": " ".join(add_opts)} run_coverage = test_options.values.run_coverage request = test_setup.test_runner_pex.create_execute_request( python_setup=python_setup, diff --git a/src/python/pants/core/goals/test.py b/src/python/pants/core/goals/test.py index 2455aca1d44..92e91bd60d0 100644 --- a/src/python/pants/core/goals/test.py +++ b/src/python/pants/core/goals/test.py @@ -35,6 +35,7 @@ TargetWithOrigin, ) from pants.engine.unions import UnionMembership, union +from pants.option.custom_types import dir_option # TODO(#6004): use proper Logging singleton, rather than static logger. logger = logging.getLogger(__name__) @@ -230,6 +231,12 @@ def register_options(cls, register) -> None: help="If a coverage report file is generated, open it on the local system if the " "system supports this.", ) + register( + "--results-dir", + type=dir_option, + default=None, + help="Directory to store tests results files", + ) class Test(Goal):