Files
EOS/tests/test_docsphinx.py
T
c230d9d9de fix: keep tests and doc generators away from the developer's config files
test_docsphinx pointed EOS_DIR/EOS_CONFIG_DIR at an empty temporary
directory. EOS then fell back to the config file in the user config
directory, migrated it and wrote it back with a timestamped backup. The
test now writes an empty config file into the temporary directory, as
server_base does.

Additional fixes
- Decode the Sphinx output in test_sphinx_build as UTF-8; on Windows the
  default code page failed and the test reported a NoneType error.
- Check the user config directory with the app author in
  --check-config-side-effect, as EOS does; on Windows the check looked at
  the wrong directory.
- Let the doc generators create EOS' config file in a temporary directory.
  They left an EOS.config.json in the working directory, and a following
  make test-ci then failed in every test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 13:40:25 +01:00

189 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"
import os
import subprocess
from pathlib import Path
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️⃣ uv‑managed 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 sphinx‑build 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 sphinx‑build
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 sphinx‑build
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.
"""
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
# An empty config file, so EOS does not fall back to (and migrate) the
# user's own config file in the user config directory.
Path(eos_dir, "EOS.config.json").write_text("{}", encoding="utf-8")
try:
process = subprocess.run(
self.SPHINX_CMD,
check=True,
env=env,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
# Sphinx writes UTF-8; the platform default (e.g. cp1252 on
# Windows) cannot decode it.
encoding="utf-8",
errors="replace",
cwd=DIR_PROJECT_ROOT, # use the existing constant
)
output = process.stdout + "\n" + process.stderr
returncode = process.returncode
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))