diff --git a/CHANGELOG.md b/CHANGELOG.md index d824c3c..6e6538d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.1.0] - 2025-02-14 ### Added -- Pytest plugin can collect jupyter notebooks and cells which use the `%%ipytest` magic +- Pytest plugin collects jupyter notebooks and cells which use the `%%ipytest` magic - Published documentation for `CollectionTree`. ## [0.0.2] - 2025-02-12 diff --git a/__pyversion__ b/__pyversion__ index 6812f81..6c6aa7c 100644 --- a/__pyversion__ +++ b/__pyversion__ @@ -1 +1 @@ -0.0.3 \ No newline at end of file +0.1.0 \ No newline at end of file diff --git a/pytest_ipynb2/plugin.py b/pytest_ipynb2/plugin.py index db8edc7..f1a8735 100644 --- a/pytest_ipynb2/plugin.py +++ b/pytest_ipynb2/plugin.py @@ -43,17 +43,19 @@ def collect(self) -> Generator[Cell, None, None]: class Cell(pytest.Module): """ A collector for jupyter notebook cells. - + `pytest` will recognise these cells as `pytest.Module`s and use standard collection on them as it would any other python module. """ def _getobj(self) -> ModuleType: notebook = self.stash[ipynb2_notebook] + cellsabove = [source for cellid, source in notebook.getcodecells().items() if cellid < int(self.nodeid)] + othercells = "\n".join(cellsabove) cellsource = notebook.gettestcells()[int(self.nodeid)] cellspec = importlib.util.spec_from_loader(f"Cell{self.name}", loader=None) cell = importlib.util.module_from_spec(cellspec) - exec(cellsource, cell.__dict__) # noqa: S102 + exec(f"{othercells}\n{cellsource}", cell.__dict__) # noqa: S102 return cell diff --git a/pytest_ipynb2/pytester_helpers.py b/pytest_ipynb2/pytester_helpers.py index 92c7e38..bb6d193 100644 --- a/pytest_ipynb2/pytester_helpers.py +++ b/pytest_ipynb2/pytester_helpers.py @@ -27,7 +27,7 @@ class CollectionTree: """ @classmethod - #TODO(MusicalNinjaDad): #8 Refactor CollectionTree.from_items to be easier to understand. + # TODO(MusicalNinjaDad): #8 Refactor CollectionTree.from_items to be easier to understand. def from_items(cls, items: list[pytest.Item]) -> Self: """Create a CollectionTree from a list of collection items, as returned by `pytester.genitems()`.""" diff --git a/tests/test_runtests.py b/tests/test_runtests.py new file mode 100644 index 0000000..3700f9e --- /dev/null +++ b/tests/test_runtests.py @@ -0,0 +1,73 @@ +from pathlib import Path +from typing import TYPE_CHECKING + +import pytest + +from pytest_ipynb2.pytester_helpers import CollectedDir, ExampleDir + +if TYPE_CHECKING: + from pytest_ipynb2.plugin import Cell + + +@pytest.mark.parametrize( + ["example_dir", "outcomes"], + [ + pytest.param( + ExampleDir( + files=[Path("tests/assets/notebook.ipynb").absolute()], + conftest="pytest_plugins = ['pytest_ipynb2.plugin']", + ), + { + "passed": 2, + }, + id="Simple Notebook", + ), + pytest.param( + ExampleDir( + files=[Path("tests/assets/notebook_2tests.ipynb").absolute()], + conftest="pytest_plugins = ['pytest_ipynb2.plugin']", + ), + { + "passed": 3, + }, + id="Notebook 2 test cells", + ), + pytest.param( + ExampleDir( + files=[ + Path("tests/assets/notebook_2tests.ipynb").absolute(), + Path("tests/assets/notebook.ipynb").absolute(), + ], + conftest="pytest_plugins = ['pytest_ipynb2.plugin']", + ), + { + "passed": 5, + }, + id="Both notebooks - unsorted", + ), + ], + indirect=["example_dir"], +) +def test_runtests(example_dir: CollectedDir, outcomes: dict): + results = example_dir.pytester_instance.runpytest() + results.assert_outcomes(**outcomes) + + +@pytest.mark.parametrize( + "example_dir", + [ + pytest.param( + ExampleDir( + files=[Path("tests/assets/notebook.ipynb").absolute()], + conftest="pytest_plugins = ['pytest_ipynb2.plugin']", + ), + id="Simple Notebook", + ), + ], + indirect=True, +) +def test_cellmodule_contents(example_dir: CollectedDir): + cell: Cell = example_dir.items[0].parent + expected_attrs = ["x", "y", "adder", "test_adder", "test_globals"] + public_attrs = [attr for attr in cell._obj.__dict__ if not attr.startswith("__")] # noqa: SLF001 + assert public_attrs == expected_attrs