mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-11-13 09:13:30 +01:00
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>
189 lines
5.7 KiB
Python
189 lines
5.7 KiB
Python
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))
|