#!/usr/bin/env python3 """Erzeugt die Python-Coverage reproduzierbar ohne globale Paketinstallation.""" from __future__ import annotations import hashlib import os from pathlib import Path import shutil import subprocess import sys import traceback PROJECT_ROOT = Path(__file__).resolve().parents[1] REQUIREMENTS_FILE = PROJECT_ROOT / "requirements-dev.txt" DEPENDENCY_DIR = PROJECT_ROOT / ".python-test-deps" REQUIREMENTS_MARKER = DEPENDENCY_DIR / ".requirements.sha256" def _requirements_digest() -> str: return hashlib.sha256(REQUIREMENTS_FILE.read_bytes()).hexdigest() def _dependency_environment(include_local: bool) -> dict[str, str]: env = os.environ.copy() if include_local: existing = env.get("PYTHONPATH", "") paths = [str(DEPENDENCY_DIR)] if existing: paths.append(existing) env["PYTHONPATH"] = os.pathsep.join(paths) return env def _coverage_available(env: dict[str, str]) -> bool: check = subprocess.run( [ sys.executable, "-c", "import coverage; assert hasattr(coverage, 'Coverage')", ], cwd=PROJECT_ROOT, env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False, ) return check.returncode == 0 def _local_dependencies_are_current() -> bool: if not REQUIREMENTS_MARKER.is_file(): return False return REQUIREMENTS_MARKER.read_text(encoding="ascii").strip() == _requirements_digest() def _install_local_dependencies() -> dict[str, str]: if DEPENDENCY_DIR.exists(): shutil.rmtree(DEPENDENCY_DIR) DEPENDENCY_DIR.mkdir(parents=True) subprocess.run( [ sys.executable, "-m", "pip", "install", "--disable-pip-version-check", "--no-warn-script-location", "--target", str(DEPENDENCY_DIR), "--requirement", str(REQUIREMENTS_FILE), ], cwd=PROJECT_ROOT, check=True, ) REQUIREMENTS_MARKER.write_text(_requirements_digest() + "\n", encoding="ascii") env = _dependency_environment(include_local=True) if not _coverage_available(env): raise RuntimeError("Das lokal installierte Python-Coverage-Modul kann nicht importiert werden.") return env def _coverage_environment() -> dict[str, str]: system_env = _dependency_environment(include_local=False) if _coverage_available(system_env): return system_env local_env = _dependency_environment(include_local=True) if _local_dependencies_are_current() and _coverage_available(local_env): return local_env return _install_local_dependencies() def _run_coverage(env: dict[str, str]) -> None: commands = ( [sys.executable, "-m", "coverage", "erase"], [ sys.executable, "-m", "coverage", "run", "-m", "unittest", "discover", "-s", "test/python", "-p", "test_*.py", ], [ sys.executable, "-m", "coverage", "xml", "-o", "coverage/python/coverage.xml", ], [sys.executable, "-m", "coverage", "report"], ) for command in commands: subprocess.run(command, cwd=PROJECT_ROOT, env=env, check=True) def main() -> int: try: _run_coverage(_coverage_environment()) except Exception as error: # noqa: BLE001 - CI-Ausgabe soll jeden unerwarteten Fehler enthalten. print( f"Python-Coverage fehlgeschlagen: {type(error).__name__}: {error}", file=sys.stderr, ) traceback.print_exc(file=sys.stderr) return 1 return 0 if __name__ == "__main__": raise SystemExit(main())