2026-04-20 21:33:46 -03:00
|
|
|
"""Focused coverage-helper tests for titan-iac quality modules."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import textwrap
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
from testing.quality_coverage import compute_workspace_line_coverage, run_check
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_compute_workspace_line_coverage_handles_missing_xml(tmp_path: Path) -> None:
|
|
|
|
|
"""Missing coverage XML should produce a zero workspace coverage score."""
|
|
|
|
|
|
|
|
|
|
contract = {"coverage": {"tracked_files": ["managed.py"]}}
|
|
|
|
|
assert compute_workspace_line_coverage(contract, tmp_path, tmp_path / "missing.xml") == 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_compute_workspace_line_coverage_averages_present_tracked_files(tmp_path: Path) -> None:
|
|
|
|
|
"""Workspace coverage should average only tracked files that appear in the report."""
|
|
|
|
|
|
|
|
|
|
coverage_xml = tmp_path / "coverage.xml"
|
|
|
|
|
coverage_xml.write_text(
|
|
|
|
|
textwrap.dedent(
|
|
|
|
|
"""\
|
|
|
|
|
<coverage>
|
|
|
|
|
<packages>
|
|
|
|
|
<package>
|
|
|
|
|
<classes>
|
|
|
|
|
<class filename="alpha.py" line-rate="1.0" />
|
|
|
|
|
<class filename="beta.py" line-rate="0.5" />
|
|
|
|
|
</classes>
|
|
|
|
|
</package>
|
|
|
|
|
</packages>
|
|
|
|
|
</coverage>
|
|
|
|
|
"""
|
|
|
|
|
),
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
contract = {"coverage": {"tracked_files": ["alpha.py", "beta.py", "missing.py"]}}
|
|
|
|
|
assert compute_workspace_line_coverage(contract, tmp_path, coverage_xml) == 75.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_check_keeps_relative_names_when_source_roots_do_not_match(tmp_path: Path) -> None:
|
|
|
|
|
"""Relative filenames should remain relative when no declared source root contains them."""
|
|
|
|
|
|
|
|
|
|
coverage_xml = tmp_path / "coverage.xml"
|
|
|
|
|
unmatched_root = tmp_path / "other-root"
|
|
|
|
|
unmatched_root.mkdir()
|
|
|
|
|
coverage_xml.write_text(
|
|
|
|
|
textwrap.dedent(
|
|
|
|
|
f"""\
|
|
|
|
|
<coverage>
|
|
|
|
|
<sources>
|
|
|
|
|
<source>{unmatched_root}</source>
|
|
|
|
|
</sources>
|
|
|
|
|
<packages>
|
|
|
|
|
<package>
|
|
|
|
|
<classes>
|
2026-08-17 15:10:17 -03:00
|
|
|
<class filename="relative.py" line-rate="0.80" branch-rate="1.0" />
|
2026-04-20 21:33:46 -03:00
|
|
|
</classes>
|
|
|
|
|
</package>
|
|
|
|
|
</packages>
|
|
|
|
|
</coverage>
|
|
|
|
|
"""
|
|
|
|
|
),
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
issues = run_check(
|
|
|
|
|
{"coverage": {"minimum_percent": 95.0, "tracked_files": ["relative.py"]}},
|
|
|
|
|
tmp_path,
|
|
|
|
|
coverage_xml,
|
|
|
|
|
)
|
|
|
|
|
|
2026-08-17 15:10:17 -03:00
|
|
|
assert issues == ["line coverage below 95.0%: relative.py (80.0%)"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_check_fails_a_file_below_the_branch_floor(tmp_path: Path) -> None:
|
|
|
|
|
"""Full line coverage must never hide a file whose branch coverage is weak."""
|
|
|
|
|
|
|
|
|
|
coverage_xml = tmp_path / "coverage.xml"
|
|
|
|
|
coverage_xml.write_text(
|
|
|
|
|
textwrap.dedent(
|
|
|
|
|
"""\
|
|
|
|
|
<coverage>
|
|
|
|
|
<packages>
|
|
|
|
|
<package>
|
|
|
|
|
<classes>
|
|
|
|
|
<class filename="weak.py" line-rate="1.0" branch-rate="0.90" />
|
|
|
|
|
<class filename="solid.py" line-rate="1.0" branch-rate="0.95" />
|
|
|
|
|
</classes>
|
|
|
|
|
</package>
|
|
|
|
|
</packages>
|
|
|
|
|
</coverage>
|
|
|
|
|
"""
|
|
|
|
|
),
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
issues = run_check(
|
|
|
|
|
{"coverage": {"minimum_percent": 95.0, "tracked_files": ["weak.py", "solid.py"]}},
|
|
|
|
|
tmp_path,
|
|
|
|
|
coverage_xml,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert issues == ["branch coverage below 95.0%: weak.py (90.0%)"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_run_check_fails_closed_when_branch_evidence_is_absent(tmp_path: Path) -> None:
|
|
|
|
|
"""A report generated without branch measurement cannot satisfy the gate."""
|
|
|
|
|
|
|
|
|
|
coverage_xml = tmp_path / "coverage.xml"
|
|
|
|
|
coverage_xml.write_text(
|
|
|
|
|
textwrap.dedent(
|
|
|
|
|
"""\
|
|
|
|
|
<coverage>
|
|
|
|
|
<packages>
|
|
|
|
|
<package>
|
|
|
|
|
<classes>
|
|
|
|
|
<class filename="unmeasured.py" line-rate="1.0" />
|
|
|
|
|
</classes>
|
|
|
|
|
</package>
|
|
|
|
|
</packages>
|
|
|
|
|
</coverage>
|
|
|
|
|
"""
|
|
|
|
|
),
|
|
|
|
|
encoding="utf-8",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
issues = run_check(
|
|
|
|
|
{"coverage": {"minimum_percent": 95.0, "tracked_files": ["unmeasured.py"]}},
|
|
|
|
|
tmp_path,
|
|
|
|
|
coverage_xml,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert issues == ["branch coverage missing for tracked file: unmeasured.py"]
|