Files

104 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""Run the optional CI coverage baseline lane."""
from __future__ import annotations
import argparse
import importlib.util
import os
import subprocess
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[1]
def required_coverage_dependency_modules() -> tuple[tuple[str, str], ...]:
"""Return import modules required before pytest can collect coverage tests."""
return (
("pytest", "pytest"),
("pytest_cov", "pytest-cov"),
("aiohttp", "aiohttp"),
("anyio", "anyio"),
("numpy", "numpy"),
("PIL", "pillow"),
)
def build_coverage_command(
python_exe: str,
*,
xml_path: str = "coverage.xml",
fail_under: int = 0,
) -> list[str]:
command = [
python_exe,
"-m",
"pytest",
"tests",
"--cov=.",
"--cov-report=term-missing:skip-covered",
f"--cov-fail-under={fail_under}",
]
if xml_path:
command.append(f"--cov-report=xml:{xml_path}")
return command
def require_coverage_dependencies() -> None:
missing = [
package_name
for module_name, package_name in required_coverage_dependency_modules()
if importlib.util.find_spec(module_name) is None
]
if missing:
packages = ", ".join(missing)
raise RuntimeError(
f"Missing coverage/test dependency: {packages}. "
"Install dev dependencies with: python -m pip install -r requirements-dev.txt"
)
def _require_pytest_cov() -> None:
require_coverage_dependencies()
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Run pytest with coverage baseline reporting.")
parser.add_argument(
"--xml",
default="coverage.xml",
help="Coverage XML output path. Use an empty value to skip XML output.",
)
parser.add_argument(
"--fail-under",
type=int,
default=0,
help="Coverage threshold. Defaults to 0 for baseline reporting only.",
)
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv or sys.argv[1:])
try:
require_coverage_dependencies()
except RuntimeError as exc:
print(str(exc), file=sys.stderr)
return 2
env = os.environ.copy()
env.setdefault("DOCTOR_STATE_DIR", str(PROJECT_ROOT / "doctor_state" / "_coverage"))
command = build_coverage_command(
sys.executable,
xml_path=args.xml,
fail_under=args.fail_under,
)
return subprocess.call(command, cwd=PROJECT_ROOT, env=env)
if __name__ == "__main__":
raise SystemExit(main())