titan-iac/testing/tests/test_quality_contract.py

168 lines
5.5 KiB
Python
Raw Normal View History

from __future__ import annotations
from pathlib import Path
import textwrap
from testing.quality_contract import load_contract
from testing.quality_coverage import run_check as run_coverage_check
from testing.quality_docs import run_check as run_docs_check
from testing.quality_hygiene import run_check as run_hygiene_check
def test_bundled_contract_exposes_local_and_jenkins_profiles():
contract = load_contract()
assert "local" in contract["profiles"]
assert "jenkins" in contract["profiles"]
assert contract["pytest_suites"]["unit"]["paths"]
def test_docs_check_reports_missing_docstring_and_missing_path(tmp_path: Path):
module_path = tmp_path / "managed.py"
module_path.write_text("value = 1\n", encoding="utf-8")
(tmp_path / "README.md").write_text("repo docs\n", encoding="utf-8")
contract = {
"required_docs": [{"path": "README.md", "description": "Docs"}],
"managed_modules": ["managed.py"],
"lint_paths": ["missing-dir"],
"pytest_suites": {"unit": {"description": "Unit", "paths": ["missing-tests"]}},
"manual_scripts": [{"path": "missing-script.py", "description": "Manual"}],
}
issues = run_docs_check(contract, tmp_path)
assert "module docstring missing: managed.py" in issues
assert "contract path missing: missing-dir" in issues
assert "contract path missing: missing-tests" in issues
assert "contract path missing: missing-script.py" in issues
def test_docs_check_reports_missing_required_doc_metadata(tmp_path: Path):
(tmp_path / "README.md").write_text("", encoding="utf-8")
contract = {
"required_docs": [{"path": "README.md", "description": ""}, {"path": "missing.md", "description": "Missing"}],
"managed_modules": [],
"lint_paths": [],
"pytest_suites": {"unit": {"description": "", "paths": []}},
"manual_scripts": [{"path": "manual.py", "description": ""}],
}
issues = run_docs_check(contract, tmp_path)
assert "required doc empty: README.md" in issues
assert "required doc missing description: README.md" in issues
assert "required doc missing: missing.md" in issues
assert "pytest suite missing description: unit" in issues
assert "manual script missing description: manual.py" in issues
def test_hygiene_check_enforces_line_limit_and_name_rules(tmp_path: Path):
tests_dir = tmp_path / "tests"
tests_dir.mkdir()
bad_name = tests_dir / "bad-name.py"
bad_name.write_text("x = 1\n", encoding="utf-8")
long_file = tests_dir / "test_too_long.py"
long_file.write_text("line\n" * 4, encoding="utf-8")
contract = {
"hygiene": {
"max_lines": 3,
"line_limit_globs": ["tests/*.py"],
"naming_rules": [
{
"glob": "tests/*.py",
"pattern": r"^test_[a-z0-9_]+\.py$",
"description": "pytest files use test_*.py names.",
}
],
}
}
issues = run_hygiene_check(contract, tmp_path)
assert any("file exceeds 3 LOC" in issue for issue in issues)
assert any("naming rule failed" in issue and "bad-name.py" in issue for issue in issues)
def test_coverage_check_enforces_per_file_floor(tmp_path: Path):
build_dir = tmp_path / "build"
build_dir.mkdir()
coverage_xml = build_dir / "coverage.xml"
coverage_xml.write_text(
textwrap.dedent(
"""\
<coverage>
<packages>
<package>
<classes>
<class filename="ok.py" line-rate="1.0" />
<class filename="low.py" line-rate="0.90" />
</classes>
</package>
</packages>
</coverage>
"""
),
encoding="utf-8",
)
contract = {
"coverage": {
"minimum_percent": 95.0,
"tracked_files": ["ok.py", "low.py", "missing.py"],
}
}
issues = run_coverage_check(contract, tmp_path, coverage_xml)
assert "coverage below 95.0%: low.py (90.0%)" in issues
assert "coverage missing for tracked file: missing.py" in issues
def test_coverage_check_handles_missing_xml_and_source_root_mapping(tmp_path: Path):
missing_xml = tmp_path / "missing.xml"
assert run_coverage_check({"coverage": {"tracked_files": []}}, tmp_path, missing_xml) == [
"coverage xml missing: missing.xml"
]
source_dir = tmp_path / "pkg"
source_dir.mkdir()
(source_dir / "mapped.py").write_text("value = 1\n", encoding="utf-8")
coverage_xml = tmp_path / "coverage.xml"
coverage_xml.write_text(
textwrap.dedent(
f"""\
<coverage>
<sources>
<source>{source_dir}</source>
</sources>
<packages>
<package>
<classes>
<class filename="mapped.py" line-rate="1.0" />
<class filename="{(tmp_path / 'absolute.py').as_posix()}" line-rate="1.0" />
<class filename="skip.py" />
</classes>
</package>
</packages>
</coverage>
"""
),
encoding="utf-8",
)
(tmp_path / "absolute.py").write_text("value = 2\n", encoding="utf-8")
issues = run_coverage_check(
{
"coverage": {
"minimum_percent": 95.0,
"tracked_files": ["pkg/mapped.py", "absolute.py"],
}
},
tmp_path,
coverage_xml,
)
assert issues == []