From 537054bfadcc2e2ebbdbcddea28c991f9fb070ac Mon Sep 17 00:00:00 2001 From: Andrei Chekun Date: Wed, 21 May 2025 19:40:30 +0200 Subject: [PATCH] test.py: add support for coverage for boost test This PR adds the possibility to gather coverage for the boost tests when they're executed with pytest. Since the pytest will be used as the main runner for boost tests as well, we need this before switching the runners. --- test/conftest.py | 2 ++ test/pylib/cpp/common_cpp_conftest.py | 12 ++++++++---- test/pylib/cpp/item.py | 12 ++++++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 53ce240eb3..40293aafe5 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -66,6 +66,8 @@ def pytest_addoption(parser: pytest.Parser) -> None: "Will route the profiles to `tmpdir`/mode/coverage/`suite` and post process them in order to generate " "lcov file per suite, lcov file per mode, and an lcov file for the entire run, " "The lcov files can eventually be used for generating coverage reports") + parser.addoption("--coverage-mode", action='append', type=str, dest="coverage_modes", + help="Collect and process coverage only for the modes specified. implies: --coverage, default: All built modes") parser.addoption("--cluster-pool-size", type=int, help="Set the pool_size for PythonTest and its descendants. Alternatively environment variable " "CLUSTER_POOL_SIZE can be used to achieve the same") diff --git a/test/pylib/cpp/common_cpp_conftest.py b/test/pylib/cpp/common_cpp_conftest.py index 5ad3317a39..62b9850533 100644 --- a/test/pylib/cpp/common_cpp_conftest.py +++ b/test/pylib/cpp/common_cpp_conftest.py @@ -12,7 +12,7 @@ from pytest import Collector from test import ALL_MODES, DEBUG_MODES from test.pylib.cpp.facade import CppTestFacade -from test.pylib.cpp.item import CppFile +from test.pylib.cpp.item import CppFile, coverage from test.pylib.util import get_modes_to_run @@ -90,7 +90,8 @@ def collect_items(file_path: PosixPath, parent: Collector, facade: CppTestFacade ASAN_OPTIONS=":".join(filter(None, ASAN_OPTIONS)), SCYLLA_TEST_ENV='yes', ) - run_id = parent.config.getoption('run_id') + pytest_config = parent.config + run_id = pytest_config.getoption('run_id') modes = get_modes_to_run(parent.session.config) suite_config = read_suite_config(file_path.parent) no_parallel_cases = suite_config.get('no_parallel_cases', []) @@ -100,14 +101,17 @@ def collect_items(file_path: PosixPath, parent: Collector, facade: CppTestFacade extra_scylla_cmdline_options = suite_config.get('extra_scylla_cmdline_options', []) test_name = file_path.stem no_parallel_run = True if test_name in no_parallel_cases else False + coverage_config = coverage(pytest_config.getoption('coverage'), suite_config.get('coverage', True), + pytest_config.getoption('coverage_modes')) custom_args = custom_args_config.get(file_path.stem, ['-c2 -m2G']) args.extend(extra_scylla_cmdline_options) if len(custom_args) > 1: return CppFile.from_parent(parent=parent, path=file_path, arguments=args, parameters=custom_args, no_parallel_run=no_parallel_run, modes=modes, disabled_tests=disabled_tests, - run_id=run_id, facade=facade, env=test_env) + run_id=run_id, facade=facade, env=test_env, coverage_config=coverage_config) else: args.extend(custom_args) return CppFile.from_parent(parent=parent, path=file_path, arguments=args, no_parallel_run=no_parallel_run, - modes=modes, disabled_tests=disabled_tests, run_id=run_id, facade=facade, env=test_env) + modes=modes, disabled_tests=disabled_tests, run_id=run_id, facade=facade, + env=test_env, coverage_config=coverage_config) diff --git a/test/pylib/cpp/item.py b/test/pylib/cpp/item.py index 1963ecfba7..15f15bdcea 100644 --- a/test/pylib/cpp/item.py +++ b/test/pylib/cpp/item.py @@ -25,6 +25,9 @@ # from __future__ import annotations +from collections import namedtuple + +from scripts import coverage as coverage_script from pathlib import Path from typing import Sequence, Any, Iterator @@ -35,6 +38,8 @@ from _pytest._io import TerminalWriter from test import COMBINED_TESTS, BUILD_DIR from test.pylib.cpp.facade import CppTestFailure, CppTestFailureList, CppTestFacade +coverage = namedtuple('coverage', ['global_coverage_flag', 'suite_coverage_flag', 'coverage_modes']) + class CppFailureRepr(object): failure_sep = "---" @@ -122,8 +127,8 @@ class CppFile(pytest.File): Represents the C++ test file with all necessary information for test execution """ def __init__(self, *, no_parallel_run: bool = False, modes: list[str], disabled_tests: dict[str, set[str]], - run_id=None, facade: CppTestFacade, arguments: Sequence[str], parameters: list[str] = None, - env: dict = None, **kwargs: Any) -> None: + run_id=None, facade: CppTestFacade, arguments: Sequence[str], coverage_config: coverage, + parameters: list[str] = None, env: dict = None, **kwargs: Any) -> None: super().__init__(**kwargs) self.facade = facade self.modes = modes @@ -133,6 +138,7 @@ class CppFile(pytest.File): self.parameters = parameters self.env = env self._arguments = arguments + self.coverage_config = coverage_config def collect(self) -> Iterator[CppTestFunction]: for mode in self.modes: @@ -144,6 +150,8 @@ class CppFile(pytest.File): combined, tests = self.facade.list_tests(executable, self.no_parallel_run, mode) if combined: executable = executable.parent / COMBINED_TESTS.stem + if mode == "coverage": + self.env.update(coverage_script.env(executable)) for test_name in tests: if '/' in test_name: test_name = test_name.replace('/', '_')