Skip to content

Commit 7d45d7a

Browse files
Merge pull request #594 from gambitproject/test-run-notebooks
Add test which runs tutorial notebooks
2 parents e55cbed + 06ba186 commit 7d45d7a

4 files changed

Lines changed: 67 additions & 44 deletions

File tree

.github/workflows/python.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
- name: Set up dependencies
3030
run: |
3131
python -m pip install --upgrade pip
32-
pip install setuptools build cython pytest pytest-skip-slow wheel lxml numpy scipy
32+
pip install setuptools build cython pytest pytest-skip-slow wheel lxml numpy scipy nbformat nbclient ipykernel
3333
- name: Build source distribution
3434
run:
3535
python -m build
@@ -38,7 +38,13 @@ jobs:
3838
cd dist
3939
pip install -v pygambit*.tar.gz
4040
- name: Run tests
41-
run: pytest
41+
run: |
42+
if [ "${{ matrix.python-version }}" = "3.9" ]; then
43+
# Python 3.9 on linux skips the notebook execution test (notebooks may require newer kernels/deps)
44+
pytest -q -k 'not test_execute_notebook'
45+
else
46+
pytest
47+
fi
4248
4349
macos-13:
4450
runs-on: macos-13
@@ -56,7 +62,7 @@ jobs:
5662
- name: Set up dependencies
5763
run: |
5864
python -m pip install --upgrade pip
59-
pip install cython pytest pytest-skip-slow wheel lxml numpy scipy
65+
pip install cython pytest pytest-skip-slow wheel lxml numpy scipy nbformat nbclient ipykernel
6066
- name: Build extension
6167
run: |
6268
python -m pip install -v .
@@ -79,7 +85,7 @@ jobs:
7985
- name: Set up dependencies
8086
run: |
8187
python -m pip install --upgrade pip
82-
pip install cython pytest pytest-skip-slow wheel lxml numpy scipy
88+
pip install cython pytest pytest-skip-slow wheel lxml numpy scipy nbformat nbclient ipykernel
8389
- name: Build extension
8490
run: |
8591
python -m pip install -v .
@@ -102,7 +108,7 @@ jobs:
102108
- name: Set up dependencies
103109
run: |
104110
python -m pip install --upgrade pip
105-
pip install cython pytest pytest-skip-slow wheel lxml numpy scipy
111+
pip install cython pytest pytest-skip-slow wheel lxml numpy scipy nbformat nbclient ipykernel
106112
- name: Build extension
107113
run: |
108114
python -m pip install -v .

doc/tutorials/running_locally.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ How to run PyGambit tutorials on your computer
44
==============================================
55

66
The PyGambit tutorials are available as Jupyter notebooks and can be run interactively using any program that supports Jupyter notebooks, such as JupyterLab or VSCode.
7-
You will need a working installation of Python 3 (tested with 3.9 and later) on your machine.
7+
You will need a working installation of Python 3.9+ on your machine to run PyGambit (however the tutorials contain some syntax that may not be compatible with earlier versions of Python than 3.13).
88

99
1. To download the tutorials, open your OS's command prompt and clone the Gambit repository from GitHub, then navigate to the tutorials directory: ::
1010

tests/test_tutorials.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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()

tests/test_userguide.py

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)