diff --git a/src/compwa_policy/__init__.py b/src/compwa_policy/__init__.py index 55c8e754..eeed0089 100644 --- a/src/compwa_policy/__init__.py +++ b/src/compwa_policy/__init__.py @@ -34,6 +34,7 @@ class Arguments: allow_labels: bool allow_vscode_coverage_gutters: bool allowed_cell_metadata: str + branch_coverage: bool ci_skipped_tests: str dev_python_version: PythonVersion doc_apt_packages: str diff --git a/src/compwa_policy/cli/_checks.py b/src/compwa_policy/cli/_checks.py index cb2140ef..4691605b 100644 --- a/src/compwa_policy/cli/_checks.py +++ b/src/compwa_policy/cli/_checks.py @@ -191,7 +191,12 @@ def run_checks( # noqa: C901, PLR0912, PLR0915 do(mypy.main, "mypy" in args.type_checker, precommit_config) do(pyright.main, "pyright" in args.type_checker, precommit_config) do(ty.main, args.type_checker, args.keep_local_precommit, precommit_config) - do(pytest.main, args.allow_vscode_coverage_gutters, args.pytest_single_threaded) + do( + pytest.main, + args.allow_vscode_coverage_gutters, + args.pytest_single_threaded, + args.branch_coverage, + ) do(pyupgrade.main, precommit_config, args.no_ruff) if not args.no_ruff: do(ruff.main, precommit_config, ctx.has_notebooks, args.imports_on_top) diff --git a/src/compwa_policy/cli/_options.py b/src/compwa_policy/cli/_options.py index a952afd3..512f5a3d 100644 --- a/src/compwa_policy/cli/_options.py +++ b/src/compwa_policy/cli/_options.py @@ -112,6 +112,13 @@ class TypeChecker(str, Enum): bool | None, typer.Option("--imports-on-top", help="Sort notebook imports on the top."), ] +BranchCoverage = Annotated[ + bool | None, + typer.Option( + "--branch-coverage/--no-branch-coverage", + help="Enable branch coverage in the Coverage.py pytest configuration.", + ), +] TypeCheckerOption = Annotated[ list[TypeChecker] | None, typer.Option( diff --git a/src/compwa_policy/cli/_settings.py b/src/compwa_policy/cli/_settings.py index fffe4caf..e4afbe21 100644 --- a/src/compwa_policy/cli/_settings.py +++ b/src/compwa_policy/cli/_settings.py @@ -65,6 +65,7 @@ _SCOPED_OPTIONS: dict[str, frozenset[str]] = { "python": frozenset({ "allow_vscode_coverage_gutters", + "branch_coverage", "excluded_python_versions", "imports_on_top", "keep_local_precommit", @@ -185,6 +186,7 @@ class Settings(BaseSettings): excluded_python_versions: str = "" no_ruff: bool = False imports_on_top: bool = False + branch_coverage: bool = True type_checker: list[str] = [] keep_local_precommit: bool = False pytest_single_threaded: bool = False diff --git a/src/compwa_policy/cli/migrate.py b/src/compwa_policy/cli/migrate.py index e8d2f8c5..d5bad0ca 100644 --- a/src/compwa_policy/cli/migrate.py +++ b/src/compwa_policy/cli/migrate.py @@ -54,9 +54,11 @@ GROUP_FLAGS: dict[str, tuple[str, ...]] = { "python": ( "--allow-vscode-coverage-gutters", + "--branch-coverage", "--excluded-python-versions", "--imports-on-top", "--keep-local-precommit", + "--no-branch-coverage", "--no-ruff", "--pytest-single-threaded", "--type-checker", @@ -213,6 +215,11 @@ def _build_policy(args: list[str]) -> dict[str, Any]: if flag in {"--python", "--no-python"}: policy["python"] = flag == "--python" continue + if flag in {"--branch-coverage", "--no-branch-coverage"}: + sub_table = policy_sub_table("branch_coverage") + target = policy if sub_table is None else policy.setdefault(sub_table, {}) + target["branch-coverage"] = flag == "--branch-coverage" + continue field = _flag_to_field(flag) if field == "environment_variables": environment_variables.update(_get_environment_variables(raw_value)) diff --git a/src/compwa_policy/cli/python.py b/src/compwa_policy/cli/python.py index 558e1966..ba039326 100644 --- a/src/compwa_policy/cli/python.py +++ b/src/compwa_policy/cli/python.py @@ -5,6 +5,7 @@ from compwa_policy.cli import _checks from compwa_policy.cli._options import ( AllowVscodeCoverageGutters, + BranchCoverage, DevPythonVersion, ExcludedPythonVersions, ImportsOnTop, @@ -24,6 +25,7 @@ def python( # noqa: PLR0917 type_checker: TypeCheckerOption = None, no_ruff: NoRuff = None, imports_on_top: ImportsOnTop = None, + branch_coverage: BranchCoverage = None, keep_local_precommit: KeepLocalPrecommit = None, pytest_single_threaded: PytestSingleThreaded = None, allow_vscode_coverage_gutters: AllowVscodeCoverageGutters = None, @@ -36,6 +38,7 @@ def python( # noqa: PLR0917 type_checker=type_checker, no_ruff=no_ruff, imports_on_top=imports_on_top, + branch_coverage=branch_coverage, keep_local_precommit=keep_local_precommit, pytest_single_threaded=pytest_single_threaded, allow_vscode_coverage_gutters=allow_vscode_coverage_gutters, diff --git a/src/compwa_policy/python/pytest.py b/src/compwa_policy/python/pytest.py index 856ce6f1..0179b23c 100644 --- a/src/compwa_policy/python/pytest.py +++ b/src/compwa_policy/python/pytest.py @@ -25,14 +25,16 @@ from tomlkit.items import Array -def main(coverage_gutters: bool, single_threaded: bool) -> None: +def main( + coverage_gutters: bool, single_threaded: bool, branch_coverage: bool = True +) -> None: with Executor() as do, ModifiablePyproject.load() as pyproject: if not has_dependency(pyproject, "pytest"): return do(_merge_coverage_into_pyproject, pyproject) do(_merge_pytest_into_pyproject, pyproject) do(_deny_ini_options, pyproject) - do(_update_codecov_settings, pyproject) + do(_update_codecov_settings, pyproject, branch_coverage) do(_update_settings, pyproject) do(_update_vscode_settings, pyproject, coverage_gutters, single_threaded) if single_threaded: @@ -131,12 +133,14 @@ def __split_options(arg: str) -> list[str]: return options -def _update_codecov_settings(pyproject: ModifiablePyproject) -> None: +def _update_codecov_settings( + pyproject: ModifiablePyproject, branch_coverage: bool = True +) -> None: if not has_dependency(pyproject, ("coverage", "pytest-cov")): return updated = __update_settings( config=pyproject.get_table("tool.coverage.run", create=True), - branch=True, + branch=branch_coverage, omit=[ # https://github.com/microsoft/vscode-python/issues/24973#issuecomment-2886889888 "benchmarks/**/*.py", diff --git a/tests/python/test_pytest.py b/tests/python/test_pytest.py index 15b251a9..e16e1162 100644 --- a/tests/python/test_pytest.py +++ b/tests/python/test_pytest.py @@ -1,4 +1,7 @@ -from compwa_policy.python.pytest import _deny_ini_options +import pytest + +from compwa_policy.errors import PrecommitError +from compwa_policy.python.pytest import _deny_ini_options, _update_codecov_settings from compwa_policy.utilities.pyproject import ModifiablePyproject @@ -9,3 +12,19 @@ def test_deny_ini_options_ignores_missing_pytest_table(): """) as pyproject: _deny_ini_options(pyproject) assert pyproject.changelog == [] + + +def test_update_codecov_settings_can_disable_branch_coverage(): + with ( + pytest.raises(PrecommitError, match=r"Updated pytest coverage settings"), + ModifiablePyproject.load(""" + [project] + name = "x" + + [dependency-groups] + test = ["pytest-cov"] + """) as pyproject, + ): + _update_codecov_settings(pyproject, branch_coverage=False) + coverage = pyproject.get_table("tool.coverage.run") + assert coverage["branch"] is False diff --git a/tests/test_cli.py b/tests/test_cli.py index f3d5e2fb..18c483e4 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -31,6 +31,7 @@ def test_build_arguments_defaults() -> None: assert args.type_checker == set() assert args.excluded_python_versions == set() assert args.keep_workflow == set() + assert args.branch_coverage is True assert args.python is None @@ -73,6 +74,7 @@ def test_flatten_nested_tables( package-manager = "pixi" [tool.compwa.policy.python] + branch-coverage = false imports-on-top = true type-checker = ["mypy", "pyright"] @@ -90,6 +92,7 @@ def test_flatten_nested_tables( assert _read_policy_config() == { "dev_python_version": "3.12", "package_manager": "pixi", + "branch_coverage": False, "imports_on_top": True, "type_checker": ["mypy", "pyright"], "no_binder": True, @@ -111,6 +114,7 @@ def test_pyproject_overrides_defaults( dev-python-version = "3.12" [tool.compwa.policy.python] + branch-coverage = false type-checker = ["mypy", "pyright"] [tool.compwa.policy.setup.env] @@ -119,6 +123,7 @@ def test_pyproject_overrides_defaults( ) args = build_arguments() assert args.dev_python_version == "3.12" + assert args.branch_coverage is False assert args.type_checker == {"mypy", "pyright"} assert args.environment_variables == "PYTHONHASHSEED=0" @@ -136,6 +141,19 @@ def test_cli_overrides_pyproject( assert load_settings(dev_python_version="3.11").dev_python_version == "3.11" assert load_settings(dev_python_version=None).dev_python_version == "3.12" + def test_branch_coverage_cli_overrides_pyproject( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: + _write_policy( + tmp_path, + monkeypatch, + """ + [tool.compwa.policy.python] + branch-coverage = false + """, + ) + assert load_settings(branch_coverage=True).branch_coverage is True + def test_unknown_option_is_rejected( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: @@ -165,6 +183,7 @@ class TestBuildPolicy: def test_groups_into_sub_tables(self) -> None: policy = _build_policy([ "--allow-labels", + "--no-branch-coverage", "--keep-local-precommit", "--no-pypi", "--pytest-single-threaded", @@ -174,7 +193,11 @@ def test_groups_into_sub_tables(self) -> None: ]) assert policy == { "github": {"allow-labels": True, "no-pypi": True}, - "python": {"keep-local-precommit": True, "type-checker": ["ty"]}, + "python": { + "branch-coverage": False, + "keep-local-precommit": True, + "type-checker": ["ty"], + }, "pytest-single-threaded": True, "repo-name": "policy", "repo-title": "ComPWA repository policy",