|
| 1 | +import contextlib |
| 2 | +from pathlib import Path |
| 3 | + |
| 4 | +import nbformat |
| 5 | +import pytest |
| 6 | +from nbclient import NotebookClient |
| 7 | +from nbclient.exceptions import CellExecutionError |
| 8 | + |
| 9 | + |
| 10 | +def _find_tutorial_notebooks(): |
| 11 | + """Return a sorted list of notebook Paths under doc/tutorials. |
| 12 | +
|
| 13 | + Skips the entire module if the tutorials directory does not exist. |
| 14 | + """ |
| 15 | + root = Path(__file__).resolve().parents[1] / "doc" / "tutorials" |
| 16 | + if not root.exists(): |
| 17 | + pytest.skip(f"Tutorials folder not found: {root}") |
| 18 | + notebooks = sorted(root.rglob("*.ipynb")) |
| 19 | + if not notebooks: |
| 20 | + pytest.skip(f"No tutorial notebooks found in: {root}") |
| 21 | + return notebooks |
| 22 | + |
| 23 | + |
| 24 | +# Discover notebooks at import time so pytest can parametrize them. |
| 25 | +_NOTEBOOKS = _find_tutorial_notebooks() |
| 26 | + |
| 27 | + |
| 28 | +@pytest.mark.parametrize("nb_path", _NOTEBOOKS, ids=[p.name for p in _NOTEBOOKS]) |
| 29 | +def test_execute_notebook(nb_path): |
| 30 | + """Execute a single Jupyter notebook and fail if any cell errors occur. |
| 31 | +
|
| 32 | + This uses nbclient.NotebookClient to run the notebook in its parent directory |
| 33 | + so relative paths within the notebook resolve correctly. |
| 34 | + """ |
| 35 | + nb = nbformat.read(str(nb_path), as_version=4) |
| 36 | + |
| 37 | + # Prefer the notebook's kernelspec if provided, otherwise let nbclient pick the default. |
| 38 | + kernel_name = nb.metadata.get("kernelspec", {}).get("name") |
| 39 | + |
| 40 | + client = NotebookClient( |
| 41 | + nb, |
| 42 | + timeout=600, |
| 43 | + kernel_name=kernel_name, |
| 44 | + resources={"metadata": {"path": str(nb_path.parent)}}, |
| 45 | + ) |
| 46 | + |
| 47 | + try: |
| 48 | + client.execute() |
| 49 | + except CellExecutionError as exc: |
| 50 | + # Re-raise with more context so pytest shows which notebook failed. |
| 51 | + raise AssertionError(f"Error while executing notebook {nb_path}: {exc}") from exc |
| 52 | + finally: |
| 53 | + # Ensure kernel is shut down. |
| 54 | + with contextlib.suppress(Exception): |
| 55 | + client.shutdown_kernel() |
0 commit comments