Option C Tools
Loading...
Searching...
No Matches
test_sphinx_write_safety.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# tests/tests_docs/test_sphinx_write_safety.py
4
5"""
6Purpose
7-------
8OI-518 regression coverage — ``oct docs --sphinx`` must not silently
9overwrite user-authored files in ``docs/``. The pipeline may only
10regenerate files it previously wrote (identified by an ``Auto-generated
11by oct docs --sphinx`` marker on their first line).
12
13Protected files:
14 - docs/README.md (copied from root, now marker-guarded)
15 - docs/index.rst (regenerated each build)
16 - docs/dependencies.rst (regenerated when graph has edges)
17 - docs/_api/modules.rst (regenerated by sphinx-apidoc wrapper)
18
19Responsibilities
20----------------
21- Verify ``_is_auto_generated`` marker detection for absent and existing
22 files.
23- Verify user-authored files survive a Sphinx documentation build.
24- Verify generated (marker-present) files are refreshed from their source.
25
26Diagnostics
27-----------
28Domain: DOCS-TESTS
29Levels:
30 L2 — test lifecycle
31 L3 — assertion details
32 L4 — deep tracing
33
34Contracts
35---------
36- ``_is_auto_generated(path, marker)`` returns True when file is absent
37 or first line carries the marker; False otherwise.
38- A pre-existing user-authored ``docs/README.md`` survives a Sphinx run.
39- A generated ``docs/README.md`` (marker present) is refreshed from root.
40"""
41
42from __future__ import annotations
43
44from pathlib import Path
45
46import pytest
47
48from oct.docs import oct_docs
49from oct.docs.oct_docs import (
50 _AUTO_MARKER_MD,
51 _AUTO_MARKER_RST,
52 _is_auto_generated,
53)
54
55
56def test_is_auto_generated_absent(tmp_path: Path) -> None:
57 """Non-existent paths are safe to write — treated as auto-generated."""
58 assert _is_auto_generated(tmp_path / "missing.rst", _AUTO_MARKER_RST)
59
60
61def test_is_auto_generated_marker_matches(tmp_path: Path) -> None:
62 """A file whose first line carries the marker is auto-generated."""
63 path = tmp_path / "index.rst"
64 path.write_text(
65 f"{_AUTO_MARKER_RST}. Edits will be overwritten.\n\nBody\n",
66 encoding="utf-8",
67 )
68 assert _is_auto_generated(path, _AUTO_MARKER_RST) is True
69
70
71def test_is_auto_generated_user_authored(tmp_path: Path) -> None:
72 """A file without the marker is treated as user-authored."""
73 path = tmp_path / "README.md"
74 path.write_text("# My Hand-Written Docs\n\nHello.\n", encoding="utf-8")
75 assert _is_auto_generated(path, _AUTO_MARKER_MD) is False
76
77
79 tmp_path: Path, monkeypatch,
80) -> None:
81 """OI-518: ``run_docs_sphinx`` must not clobber a user's docs/README.md."""
82 root = tmp_path / "proj"
83 docs = root / "docs"
84 docs.mkdir(parents=True)
85
86 # User has hand-authored docs/README.md *and* a root README.md that
87 # differs. The pipeline used to unconditionally copy the root file
88 # on top of the user's docs/ file.
89 user_readme = docs / "README.md"
90 user_readme.write_text("# Hand-written\n", encoding="utf-8")
91 (root / "README.md").write_text("# Root\n", encoding="utf-8")
92
93 # Short-circuit the expensive parts: dependency check, setup, api/deps
94 # generation, index write, and the subprocess invocation.
95 monkeypatch.setattr(oct_docs, "_check_sphinx_deps", lambda: [])
96 monkeypatch.setattr(oct_docs, "setup_sphinx", lambda r: r / "docs")
97 monkeypatch.setattr(oct_docs, "_generate_api_rst",
98 lambda r, d, c: False)
99 monkeypatch.setattr(oct_docs, "_generate_deps_rst", lambda r, d: False)
100 monkeypatch.setattr(oct_docs, "_write_index_rst",
101 lambda *a, **kw: None)
102 monkeypatch.setattr(oct_docs, "run_sphinx_build", lambda d, c: 0)
103
104 rc = oct_docs.run_docs_sphinx(root)
105 assert rc == 0
106 # User-authored content must survive.
107 assert user_readme.read_text(encoding="utf-8") == "# Hand-written\n"
108
109
111 tmp_path: Path, monkeypatch,
112) -> None:
113 """OI-518: a marker-carrying docs/README.md is refreshed from root."""
114 root = tmp_path / "proj"
115 docs = root / "docs"
116 docs.mkdir(parents=True)
117
118 # A previously generated docs/README.md carries the marker. The
119 # pipeline should overwrite it with the current root README.
120 (docs / "README.md").write_text(
121 f"{_AUTO_MARKER_MD}\n\n# Stale Root\n", encoding="utf-8",
122 )
123 (root / "README.md").write_text("# Fresh Root\n", encoding="utf-8")
124
125 monkeypatch.setattr(oct_docs, "_check_sphinx_deps", lambda: [])
126 monkeypatch.setattr(oct_docs, "setup_sphinx", lambda r: r / "docs")
127 monkeypatch.setattr(oct_docs, "_generate_api_rst",
128 lambda r, d, c: False)
129 monkeypatch.setattr(oct_docs, "_generate_deps_rst", lambda r, d: False)
130 monkeypatch.setattr(oct_docs, "_write_index_rst",
131 lambda *a, **kw: None)
132 monkeypatch.setattr(oct_docs, "run_sphinx_build", lambda d, c: 0)
133
134 rc = oct_docs.run_docs_sphinx(root)
135 assert rc == 0
136 refreshed = (docs / "README.md").read_text(encoding="utf-8")
137 assert "# Fresh Root" in refreshed
138 assert refreshed.startswith(_AUTO_MARKER_MD)
None test_run_docs_sphinx_preserves_user_readme(Path tmp_path, monkeypatch)
None test_run_docs_sphinx_refreshes_generated_readme(Path tmp_path, monkeypatch)