band-aider-pub/tests/band_aider/test_python_coverage_strategy.py
2026-10-17 11:57:37 -07:00

95 lines
3.4 KiB
Python

# SPDX-FileCopyrightText: 2026 Art Sea LLC
#
# SPDX-License-Identifier: AGPL-3.0-only
"""Coverage-strategy command tests through an explicit in-memory repository."""
from __future__ import annotations
import json
from pathlib import Path
import subprocess
from typing import Iterable, Sequence
from band_aider.context.class_meta import ClassMeta
from band_aider.judgement.python_ast_dependency_strategy import PythonAstDependencyStrategy
from band_aider.judgement.python_coverage_strategy import PythonCoverageStrategy
from band_aider.judgement.test_environment import TEST_ENV_VARIABLES_TO_CLEAR
from tests.support.memory_repo_exec import MemoryRepoExec
class _CoverageRepoExec(MemoryRepoExec):
"""In-memory executor that returns a stationary coverage JSON report."""
def __init__(self) -> None:
super().__init__()
self.environments: list[dict[str, str]] = []
self.unset_environments: list[tuple[str, ...]] = []
def run_cli(
self,
command: Sequence[str],
*,
cwd: Path | None = None,
env: dict[str, str] | None = None,
capture_output: bool = True,
text: bool = True,
check: bool = False,
timeout: int | None = None,
input: bytes | str | None = None,
unset_env: Iterable[str] = (),
) -> subprocess.CompletedProcess:
"""Record one coverage command and materialize its report in memory."""
del cwd, capture_output, text, check, timeout, input
command_parts = list(command)
self.environments.append(dict(env or {}))
self.unset_environments.append(tuple(unset_env))
if "json" in command_parts:
output_path = Path(command_parts[command_parts.index("-o") + 1])
self.write_text(
output_path,
json.dumps(
{
"files": {
"src/widget.py": {
"summary": {
"covered_branches": 1,
"num_branches": 1,
"covered_lines": 1,
"num_statements": 1,
}
}
}
}
),
)
return subprocess.CompletedProcess(command_parts, 0, "", "")
def test_python_coverage_strategy_uses_stationary_sanitized_test_environment() -> None:
"""Verify coverage commands clear host-sensitive variables through the adapter boundary."""
repo_exec = _CoverageRepoExec()
repo_root = repo_exec.root
src_root = repo_root / "src"
test_root = repo_root / "tests"
repo_exec.write_text(test_root / "test_widget.py", "def test_widget():\n assert True\n")
coverage = PythonCoverageStrategy(
dependency_strategy=PythonAstDependencyStrategy(),
repo_exec=repo_exec,
)
assert coverage.branch_coverage_for(
[ClassMeta("src/widget.py", "", [], {})],
[ClassMeta("tests/test_widget.py", "", [], {})],
repo_root,
src_root,
test_root,
) == 1.0
assert len(repo_exec.environments) == 2
assert all(set(environment) == {"COVERAGE_FILE"} for environment in repo_exec.environments)
assert repo_exec.unset_environments == [
TEST_ENV_VARIABLES_TO_CLEAR,
TEST_ENV_VARIABLES_TO_CLEAR,
]