104 lines
2.7 KiB
Python
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())
|