Skip to content

Commit 1f77172

Browse files
committed
better docs testing support
1 parent 92d8590 commit 1f77172

30 files changed

Lines changed: 2098 additions & 192 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ tmp/
1212
# pytest results
1313
junit/
1414

15+
# Generated doc tests (from scripts/docs/generate_doc_tests.py)
16+
python/quantum-pecos/tests/docs/generated/
17+
1518
# Cython
1619
*.pyc
1720
*.pyd

Justfile

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,22 @@ docs-build:
138138
docs port="8000":
139139
cargo run -p pecos --features cli -- docs --port {{port}}
140140

141-
# Test all code examples in documentation
141+
# Test all code examples in documentation (generates pytest files and runs them)
142142
docs-test:
143-
uv run python scripts/docs/test_code_examples.py
143+
uv run python scripts/docs/generate_doc_tests.py
144+
uv run pytest python/quantum-pecos/tests/docs/generated -v
145+
146+
# Generate doc tests without running them
147+
docs-test-generate:
148+
uv run python scripts/docs/generate_doc_tests.py
144149

145-
# Test only working code examples in documentation
146-
docs-test-working:
147-
uv run python scripts/docs/test_working_examples.py
150+
# Run doc tests with pytest options (e.g., just docs-test-run "-k bell_state")
151+
docs-test-run *args:
152+
uv run pytest python/quantum-pecos/tests/docs/generated {{args}}
153+
154+
# Legacy: test code examples with old script
155+
docs-test-legacy:
156+
uv run python scripts/docs/test_code_examples.py
148157

149158
# =============================================================================
150159
# Linting / Formatting

docs/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ Simulate a distance-3 repetition code with syndrome extraction using [Guppy](htt
1616
pip install quantum-pecos
1717
```
1818

19-
<!--skip: Guppy example requires guppylang-->
2019
```python
2120
from pecos import Guppy, sim, state_vector, depolarizing_noise
2221
from guppylang import guppy

docs/assets/test-data/README.txt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Test Data Directory
2+
3+
This directory contains test data files (HUGR programs, WASM modules, etc.) used by documentation code examples.
4+
5+
## Usage
6+
7+
When documentation code examples need external files, place them here. The doc test generator (`scripts/docs/generate_doc_tests.py`) can copy these files to the test environment.
8+
9+
## Example Files
10+
11+
- `repetition_code.hugr` - A compiled repetition code circuit (TODO: generate)
12+
13+
## Generating HUGR Files
14+
15+
HUGR files can be generated from Guppy programs:
16+
17+
```python
18+
from guppylang import GuppyModule
19+
from guppylang.std.quantum import qubit, cx, measure
20+
21+
@guppy
22+
def my_circuit() -> None:
23+
q = qubit()
24+
# ... circuit logic ...
25+
26+
# Export to HUGR
27+
my_circuit.compile().save("my_circuit.hugr")
28+
```
29+
30+
## Note
31+
32+
If a documentation example requires a file that doesn't exist here, mark it with `<!--skip: Requires filename.hugr-->` in the documentation.

docs/assets/test-data/repetition_code.hugr

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

docs/development/DEVELOPMENT.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,4 @@ PECOS_HOME = { value = "/custom/path", force = true }
151151
For specific development topics, see:
152152

153153
- [Parallel Blocks and Optimization](parallel-blocks-and-optimization.md) - Guide to using and extending the Parallel block construct and optimizer
154+
- [Documentation Code Testing](doc-testing.md) - Guide to testing code examples in documentation

docs/development/doc-testing.md

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
# Documentation Code Testing
2+
3+
<!--skip: Examples show marker syntax, not runnable code-->
4+
5+
PECOS automatically tests code examples in documentation to ensure they remain correct as the codebase evolves.
6+
7+
## Overview
8+
9+
The documentation testing system:
10+
11+
1. Extracts Python and Rust code blocks from Markdown files
12+
2. Generates pytest test files from the extracted code
13+
3. Runs the tests as part of CI
14+
15+
## Running Documentation Tests
16+
17+
```bash
18+
# Generate tests from documentation
19+
uv run python scripts/docs/generate_doc_tests.py
20+
21+
# Run all documentation tests
22+
uv run pytest python/quantum-pecos/tests/docs/generated -v
23+
24+
# Run tests for a specific document
25+
uv run pytest python/quantum-pecos/tests/docs/generated/user-guide/test_getting_started.py -v
26+
```
27+
28+
## Marker Reference
29+
30+
Markers are HTML comments placed immediately before code blocks to control test behavior.
31+
32+
### Skip Markers
33+
34+
#### Block-level Skip
35+
36+
Skip a single code block:
37+
38+
```markdown
39+
<!--skip-->
40+
\```python
41+
# This code won't be tested
42+
from some_unavailable_module import thing
43+
\```
44+
```
45+
46+
Skip with a reason (shown in test output):
47+
48+
```markdown
49+
<!--skip: requires external hardware-->
50+
\```python
51+
connect_to_quantum_computer()
52+
\```
53+
```
54+
55+
#### Document-level Skip
56+
57+
Skip all code blocks in a document by placing a skip marker at the beginning of the file (after the heading):
58+
59+
```markdown
60+
# Document Title
61+
62+
<!--skip: All examples require external files-->
63+
64+
Content here...
65+
```
66+
67+
### Conditional Skip
68+
69+
Skip tests when CUDA is not available:
70+
71+
```markdown
72+
<!--skip-if-no-cuda-->
73+
\```python
74+
from pecos.simulators import CudaSim
75+
sim = CudaSim()
76+
\```
77+
```
78+
79+
### Expected Errors
80+
81+
Test that code raises a specific error:
82+
83+
```markdown
84+
<!--expect-error: TypeError-->
85+
\```python
86+
"string" + 123 # This will raise TypeError
87+
\```
88+
```
89+
90+
The test passes if the code raises an error containing the specified text.
91+
92+
### Expected Output
93+
94+
Verify code produces specific output:
95+
96+
```markdown
97+
<!--expect-output: Hello, World!-->
98+
\```python
99+
print("Hello, World!")
100+
\```
101+
```
102+
103+
### Test Names
104+
105+
Give a test a specific name for easier identification:
106+
107+
```markdown
108+
<!--test-name: bell_state_example-->
109+
\```python
110+
# This test will be named test_bell_state_example
111+
\```
112+
```
113+
114+
### Pytest Marks
115+
116+
Add pytest marks to tests:
117+
118+
```markdown
119+
<!--mark.slow-->
120+
\```python
121+
# This test will have @pytest.mark.slow
122+
\```
123+
```
124+
125+
### Test Data Files
126+
127+
Copy test data files to the test directory (for Rust cargo tests):
128+
129+
```markdown
130+
<!--test-data: repetition_code.hugr-->
131+
\```rust
132+
fn main() {
133+
let data = std::fs::read("repetition_code.hugr").unwrap();
134+
}
135+
\```
136+
```
137+
138+
Test data files should be placed in `docs/assets/test-data/`.
139+
140+
## Hidden Preambles
141+
142+
Use hidden code blocks to provide imports or setup code that should be included in tests but not shown in documentation:
143+
144+
```markdown
145+
\```hidden-python
146+
from pecos.slr import Main, QReg, CReg
147+
from pecos.slr.qeclib import qubit as qb
148+
\```
149+
150+
Now the visible code can use these imports:
151+
152+
\```python
153+
prog = Main(
154+
q := QReg("q", 2),
155+
qb.H(q[0]),
156+
)
157+
\```
158+
```
159+
160+
The hidden preamble is prepended to all subsequent code blocks in the document until a `<!--preamble-reset-->` marker is encountered.
161+
162+
### Preamble Reset
163+
164+
Reset the accumulated preamble:
165+
166+
```markdown
167+
<!--preamble-reset-->
168+
\```python
169+
# This code starts fresh without any preamble
170+
\```
171+
```
172+
173+
## Code Block Languages
174+
175+
### Python
176+
177+
Python code blocks are tested using `exec()`:
178+
179+
```markdown
180+
\```python
181+
from pecos import sim
182+
result = sim(Qasm("...")).run(10)
183+
\```
184+
```
185+
186+
### Rust
187+
188+
Rust code blocks can be tested in two ways:
189+
190+
1. **Simple Rust** (rustc): Code with `fn main()` that doesn't use external crates
191+
2. **Cargo Rust**: Code using `pecos` crates (detected by `use pecos*::`)
192+
193+
Incomplete Rust snippets (no `fn main()`, traits, impls without full context) are automatically skipped.
194+
195+
#### Rust with Cargo Dependencies
196+
197+
For Rust code that uses PECOS crates:
198+
199+
```markdown
200+
\```rust
201+
use pecos::prelude::*;
202+
203+
fn main() -> Result<(), Box<dyn std::error::Error>> {
204+
let results = sim(Qasm::from_string("...")).run(10)?;
205+
Ok(())
206+
}
207+
\```
208+
```
209+
210+
This creates a temporary Cargo project with the necessary dependencies.
211+
212+
## Fence-level Skip Markers
213+
214+
Standard Rust documentation markers are also supported:
215+
216+
```markdown
217+
\```rust,skip
218+
// This code is skipped
219+
\```
220+
221+
\```rust,ignore
222+
// This code is also skipped
223+
\```
224+
225+
\```rust,no_run
226+
// This code is also skipped
227+
\```
228+
```
229+
230+
## Best Practices
231+
232+
### Do
233+
234+
- Add hidden preambles for common imports to keep visible code focused
235+
- Use meaningful skip reasons to explain why code can't be tested
236+
- Test error conditions with `<!--expect-error-->` markers
237+
- Keep code examples self-contained when possible
238+
239+
### Don't
240+
241+
- Don't skip code unless necessary
242+
- Don't rely on state from previous code blocks (each block is tested independently)
243+
- Don't include real API keys or credentials in examples
244+
- Don't use `exec()` in your actual code examples (it's only used internally for testing)
245+
246+
## Generated Files
247+
248+
The test generator creates files in:
249+
250+
```
251+
python/quantum-pecos/tests/docs/generated/
252+
conftest.py # Shared fixtures
253+
test_README.py # Tests from docs/README.md
254+
user-guide/ # Tests from docs/user-guide/
255+
development/ # Tests from docs/development/
256+
```
257+
258+
These files are gitignored and regenerated before tests run.
259+
260+
## Debugging Failed Tests
261+
262+
When a documentation test fails:
263+
264+
1. Check the test docstring for the source file and line number
265+
2. The test name includes the source file and block number
266+
3. Run the specific test with `-v` for verbose output
267+
4. Check if the code requires imports that should be in a hidden preamble
268+
269+
Example:
270+
271+
```bash
272+
# Run with verbose output
273+
uv run pytest python/quantum-pecos/tests/docs/generated/user-guide/test_gates.py::test_gates_block_5 -v
274+
275+
# The test docstring shows the source:
276+
# """Test from docs/user-guide/gates.md:142."""
277+
```
278+
279+
## Adding New Documentation
280+
281+
When adding new documentation:
282+
283+
1. Add code examples in fenced code blocks
284+
2. Add hidden preambles for common imports if needed
285+
3. Add skip markers for code that can't be tested
286+
4. Run `uv run python scripts/docs/generate_doc_tests.py` to generate tests
287+
5. Run the generated tests to verify examples work
288+
6. Commit the documentation (generated tests are gitignored)

docs/development/parallel-blocks-and-optimization.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,10 @@ Comprehensive tests are available in:
223223

224224
Here's a more complex example showing parallel phase gates:
225225

226-
<!--skip-->
226+
<!--skip: CRZ gate not yet implemented-->
227227
```python
228228
from pecos.slr import Main, Parallel, QReg
229-
from pecos.qeclib import qubit as qb
229+
from pecos.slr.qeclib import qubit as qb
230230

231231

232232
def qft_layer(q, n, k):

0 commit comments

Comments
 (0)