Skip to content

Commit 4dd7565

Browse files
committed
add doctests
1 parent 1202fb1 commit 4dd7565

File tree

1 file changed

+108
-0
lines changed

1 file changed

+108
-0
lines changed

tests/test_docs.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
"""
2+
Tests for executable code blocks in markdown documentation.
3+
4+
This module uses pytest-examples to validate that all Python code examples
5+
with exec="true" in the documentation execute successfully.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
from collections import defaultdict
11+
from pathlib import Path
12+
13+
import pytest
14+
15+
pytest_examples = pytest.importorskip("pytest_examples")
16+
17+
# Find all markdown files with executable code blocks
18+
docs_root = Path(__file__).parent.parent / "docs"
19+
20+
21+
def find_markdown_files_with_exec() -> list[Path]:
22+
"""Find all markdown files containing exec="true" code blocks."""
23+
markdown_files = []
24+
25+
for md_file in docs_root.rglob("*.md"):
26+
try:
27+
content = md_file.read_text(encoding="utf-8")
28+
if 'exec="true"' in content:
29+
markdown_files.append(md_file)
30+
except Exception:
31+
# Skip files that can't be read
32+
continue
33+
34+
return sorted(markdown_files)
35+
36+
37+
def group_examples_by_session() -> list[tuple[str, str]]:
38+
"""
39+
Group examples by their session and file, maintaining order.
40+
41+
Returns a list of session_key tuples where session_key is
42+
(file_path, session_name).
43+
"""
44+
all_examples = list(pytest_examples.find_examples(docs_root))
45+
46+
# Group by file and session
47+
sessions = defaultdict(list)
48+
49+
for example in all_examples:
50+
settings = example.prefix_settings()
51+
if settings.get("exec") != "true":
52+
continue
53+
54+
# Use file path and session name as key
55+
file_path = example.path
56+
session_name = settings.get("session", "_default")
57+
session_key = (str(file_path), session_name)
58+
59+
sessions[session_key].append(example)
60+
61+
# Return sorted list of session keys for consistent test ordering
62+
return sorted(sessions.keys(), key=lambda x: (x[0], x[1]))
63+
64+
65+
def name_example(path: str, session: str) -> str:
66+
"""Generate a readable name for a test case from file path and session."""
67+
return f"{Path(path).relative_to(docs_root)}:{session}"
68+
69+
70+
# Get all example sessions
71+
@pytest.mark.parametrize(
72+
"session_key", group_examples_by_session(), ids=lambda v: name_example(v[0], v[1])
73+
)
74+
def test_documentation_examples(
75+
session_key: tuple[str, str],
76+
eval_example: pytest_examples.EvalExample, # type: ignore[name-defined]
77+
) -> None:
78+
"""
79+
Test that all exec="true" code examples in documentation execute successfully.
80+
81+
This test groups examples by session (file + session name) and runs them
82+
sequentially in the same execution context, allowing code to build on
83+
previous examples.
84+
85+
This test uses pytest-examples to:
86+
- Find all code examples with exec="true" in markdown files
87+
- Group them by session
88+
- Execute them in order within the same context
89+
- Verify no exceptions are raised
90+
"""
91+
file_path, session_name = session_key
92+
93+
# Get examples for this session
94+
all_examples = list(pytest_examples.find_examples(docs_root))
95+
examples = []
96+
for example in all_examples:
97+
settings = example.prefix_settings()
98+
if settings.get("exec") != "true":
99+
continue
100+
if str(example.path) == file_path and settings.get("session", "_default") == session_name:
101+
examples.append(example)
102+
103+
# Run all examples in this session sequentially, preserving state
104+
module_globals: dict[str, object] = {}
105+
for example in examples:
106+
result = eval_example.run(example, module_globals=module_globals)
107+
# Update globals with the results from this execution
108+
module_globals.update(result)

0 commit comments

Comments
 (0)