Files
EOS/tests/test_docsphinx.py
T

183 lines
5.4 KiB
Python
Raw Normal View History

import json
import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Optional
import pytest
from akkudoktoreos.core.coreabc import singletons_init
DIR_PROJECT_ROOT = Path(__file__).absolute().parent.parent
DIR_BUILD = DIR_PROJECT_ROOT / "build"
DIR_BUILD_DOCS = DIR_PROJECT_ROOT / "build" / "docs"
DIR_DOCS = DIR_PROJECT_ROOT / "docs"
DIR_SRC = DIR_PROJECT_ROOT / "src"
HASH_FILE = DIR_BUILD / ".sphinx_hash.json"
2026-07-15 16:38:53 +02:00
import os
import subprocess
from pathlib import Path
2026-07-15 16:38:53 +02:00
def find_sphinx_build() -> list[str]:
"""Return command to invoke sphinx-build via virtualenv, uv, or globally."""
candidates = []
# 1️⃣ Currently active virtualenv
venv = os.getenv("VIRTUAL_ENV")
if venv:
candidates.append(Path(venv))
# 2️⃣ uvmanaged virtualenv
uv_venv = Path(".uv") / "venv"
if uv_venv.exists():
candidates.append(uv_venv)
# 3️⃣ traditional .venv
dot_venv = Path(".venv")
if dot_venv.exists():
candidates.append(dot_venv)
# Check each candidate for the sphinxbuild binary
for base in candidates:
sphinx_build_path = base / ("Scripts" if os.name == "nt" else "bin") / (
"sphinx-build.exe" if os.name == "nt" else "sphinx-build"
)
if sphinx_build_path.exists():
return [str(sphinx_build_path)]
# 4️⃣ fallback to uv run sphinxbuild
try:
subprocess.run(
["uv", "run", "sphinx-build", "--version"],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
return ["uv", "run", "sphinx-build"]
except (subprocess.CalledProcessError, FileNotFoundError):
pass
# 5️⃣ final fallback to system sphinxbuild
return ["sphinx-build"]
@pytest.fixture(scope="session")
def sphinx_changed(version_and_hash) -> Optional[str]:
"""Returns new hash if any watched files have changed since last run.
Hash is stored in .sphinx_hash.json.
"""
new_hash = None
# Load previous hash
try:
previous = json.loads(HASH_FILE.read_text())
previous_hash = previous.get("hash")
except Exception:
previous_hash = None
changed = (previous_hash != version_and_hash["hash_current"])
if changed:
new_hash = version_and_hash["hash_current"]
return new_hash
class TestSphinxDocumentation:
"""Test class to verify Sphinx documentation generation.
Ensures no major warnings are emitted.
"""
2026-07-15 16:38:53 +02:00
SPHINX_CMD = find_sphinx_build() + [
"-M",
"html",
str(DIR_DOCS),
str(DIR_BUILD_DOCS),
]
def _cleanup_autosum_dirs(self):
"""Delete all *_autosum folders inside docs/."""
for folder in DIR_DOCS.rglob("*_autosum"):
if folder.is_dir():
shutil.rmtree(folder)
def _cleanup_build_dir(self):
"""Delete build/docs directory if present."""
if DIR_BUILD_DOCS.exists():
shutil.rmtree(DIR_BUILD_DOCS)
def test_sphinx_build(self, sphinx_changed: Optional[str], is_finalize: bool):
"""Build Sphinx documentation and ensure no major warnings appear in the build output."""
# Ensure docs folder exists
if not DIR_DOCS.exists():
pytest.skip(f"Skipping Sphinx build test - docs folder not present: {DIR_DOCS}")
if not sphinx_changed:
pytest.skip(f"Skipping Sphinx build — no relevant file changes detected: {HASH_FILE}")
if not is_finalize:
pytest.skip("Skipping Sphinx test — not finalize")
# Clean directories
self._cleanup_autosum_dirs()
self._cleanup_build_dir()
# Set environment for sphinx run (sphinx will make eos create a config file)
eos_tmp_dir = tempfile.TemporaryDirectory()
eos_dir = str(eos_tmp_dir.name)
env = os.environ.copy()
env["EOS_DIR"] = eos_dir
env["EOS_CONFIG_DIR"] = eos_dir
try:
process = subprocess.run(
self.SPHINX_CMD,
check=True,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
2026-07-15 16:38:53 +02:00
cwd=DIR_PROJECT_ROOT, # use the existing constant
)
output = process.stdout + "\n" + process.stderr
returncode = process.returncode
2026-07-15 16:38:53 +02:00
except subprocess.CalledProcessError as e:
output = e.stdout + "\n" + e.stderr if e.stdout else ""
returncode = e.returncode
except Exception as e:
output = f"Failed to execute command: {e}"
returncode = -1
# Remove temporary EOS_DIR
eos_tmp_dir.cleanup()
if returncode != 0:
pytest.fail(
f"Sphinx build failed with exit code {returncode}.\n"
f"{output}\n"
)
# Possible markers: ERROR: WARNING: TRACEBACK:
major_markers = ("ERROR:", "TRACEBACK:")
bad_lines = [
line for line in output.splitlines()
if any(marker in line for marker in major_markers)
]
assert not bad_lines, f"Sphinx build contained errors:\n" + "\n".join(bad_lines)
# Update stored hash
HASH_FILE.parent.mkdir(parents=True, exist_ok=True)
HASH_FILE.write_text(json.dumps({"hash": sphinx_changed}, indent=2))