144 lines
3.8 KiB
Python
144 lines
3.8 KiB
Python
#!/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())
|