diff --git a/.github/scripts/ci_changes.py b/.github/scripts/ci_changes.py index 7a52564e..7b048f20 100644 --- a/.github/scripts/ci_changes.py +++ b/.github/scripts/ci_changes.py @@ -82,6 +82,9 @@ def classify(paths): for path in paths: if path.startswith("docs/"): selected.add("docs") + if path == "docs/docs/quick_start.md": + # tests/python/test_quick_start.py executes this page's Python blocks. + selected.add("python_build") continue if path.endswith(".md"): continue diff --git a/.github/scripts/test_ci_changes.py b/.github/scripts/test_ci_changes.py index cc1ee247..bbc8ab3b 100644 --- a/.github/scripts/test_ci_changes.py +++ b/.github/scripts/test_ci_changes.py @@ -57,6 +57,9 @@ def test_docs_only_skip_builds(self): {"docs"}, ) self.assertEqual(classify(["README.md", "sample/python/README.md"]), set()) + self.assertEqual( + classify(["docs/docs/quick_start.md"]), {"docs", "python_build"} + ) self.assertEqual( classify([".github/workflows/docs.yml"]), {"docs", "python_quality"} ) diff --git a/.github/scripts/test_ci_workflow_contracts.py b/.github/scripts/test_ci_workflow_contracts.py index 8b56366d..935fdf7f 100644 --- a/.github/scripts/test_ci_workflow_contracts.py +++ b/.github/scripts/test_ci_workflow_contracts.py @@ -870,7 +870,7 @@ def test_documentation_summary_is_available_for_non_doc_prs(self): self.assertIn("needs.changes.outputs.docs == 'true'", build["if"]) self.assertIn("needs.changes.result != 'success'", build["if"]) self.assertIn("github.event_name == 'push'", build["if"]) - self.assertEqual(classify(["docs/docs/quick_start.md"]), {"docs"}) + self.assertEqual(classify(["docs/docs/installation.md"]), {"docs"}) if __name__ == "__main__": diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index fd107788..b2526230 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -95,6 +95,7 @@ jobs: run: >- python -m pytest --tb=short -q tests/python/test_import.py + tests/python/test_quick_start.py tests/python/test_ivf.py::test_save_load_roundtrip tests/python/test_hnsw.py::test_save_load_roundtrip tests/python/test_symqg.py::test_save_load_roundtrip diff --git a/docs/maintaining.md b/docs/maintaining.md index 9950fb53..ff9dc959 100644 --- a/docs/maintaining.md +++ b/docs/maintaining.md @@ -11,6 +11,7 @@ only the affected job groups: | Changes | Checks | | --- | --- | | Documentation and Markdown only | Documentation build for `docs/`; no C++ or Python builds; the documentation summary is always emitted | +| `docs/docs/quick_start.md` | Documentation build plus the Linux source-install job, which runs the page's Python blocks | | Issue forms/configuration and CODEOWNERS | No C++/Python builds | | `benchmarks/core/`, `benchmarks/hotpaths/` code/build files | Ubuntu benchmark build and bounded smoke tests; C++ formatting or Python lint as applicable, without the wheel/platform matrix for benchmark-only edits | | `.github/workflows/test.yaml` | C++ checks, ShellCheck, Python quality (including summary-job tests), and benchmark smoke | diff --git a/tests/python/test_quick_start.py b/tests/python/test_quick_start.py new file mode 100644 index 00000000..58836a21 --- /dev/null +++ b/tests/python/test_quick_start.py @@ -0,0 +1,32 @@ +"""Run the Python code blocks from docs/docs/quick_start.md in one shared namespace. + +Extracts every ```python fence from the Markdown source and executes them in +order so that any API change that breaks the page also fails CI. +""" + +import re +from pathlib import Path + +QUICK_START = Path(__file__).resolve().parents[2] / "docs" / "docs" / "quick_start.md" +_BLOCK_RE = re.compile(r"```python\n(.*?)```", re.DOTALL) + + +def _extract_blocks(path: Path) -> list[str]: + text = path.read_text(encoding="utf-8") + return _BLOCK_RE.findall(text) + + +def test_quick_start_blocks_run_in_order(monkeypatch, tmp_path): + """All Python blocks in quick_start.md execute successfully in one namespace.""" + blocks = _extract_blocks(QUICK_START) + assert len(blocks) == 3, ( + f"Expected 3 python blocks in quick_start.md, found {len(blocks)}. " + "Update this test if the page gains or loses blocks." + ) + + # Run from a temp dir so a future block writing a relative path can't dirty the repo. + monkeypatch.chdir(tmp_path) + + namespace: dict = {} + for i, code in enumerate(blocks, start=1): + exec(compile(code, f"quick_start.md block {i}", "exec"), namespace)