-
Notifications
You must be signed in to change notification settings - Fork 103
Use pytest+jupytext for notebook testing #1838
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mpharrigan
wants to merge
8
commits into
quantumlib:main
Choose a base branch
from
mpharrigan:2026-04/jupytext
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
8d72aae
Notebook testing
mpharrigan 475342e
Update qualtran/testing.py
mpharrigan 2f3eea8
Update dev_tools/check-notebook-tests.py
mpharrigan 079ad10
Merge branch 'main' into 2026-04/jupytext
mpharrigan 8b76c47
include updated deps in frozen env
mpharrigan 9915616
jupytext in pylint deps too
mpharrigan 1b5aeba
ci
mpharrigan 5f25a56
ci
mpharrigan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| #!/usr/bin/env bash | ||
|
|
||
| ################################################################################ | ||
| # Runs notebook tests via jupytext-based execution. | ||
| ################################################################################ | ||
|
|
||
| # Get the working directory to the repo root. | ||
| thisdir="$(dirname "${BASH_SOURCE[0]}")" || exit $? | ||
| topdir="$(git -C "${thisdir}" rev-parse --show-toplevel)" || exit $? | ||
| cd "${topdir}" || exit $? | ||
|
|
||
| set -e | ||
|
|
||
| python dev_tools/check-notebook-tests.py | ||
|
|
||
| # Use non-interactive backend so plt.show() doesn't open GUI windows. | ||
| export MPLBACKEND=agg | ||
|
|
||
| pytest -v qualtran/ tutorials/ \ | ||
| -m notebook |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| # Copyright 2026 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| """Verify that every committed .ipynb has a corresponding pytest notebook test. | ||
|
|
||
| For each committed notebook, checks that: | ||
| 1. A *_test.py file exists containing an execute_notebook('name') call | ||
| 2. That test function is decorated with @pytest.mark.notebook | ||
|
|
||
| Usage: | ||
| python dev_tools/check-notebook-tests.py | ||
| """ | ||
|
|
||
| import ast | ||
| import subprocess | ||
| import sys | ||
| from pathlib import Path | ||
| from typing import Dict, Tuple | ||
|
|
||
| from qualtran_dev_tools.git_tools import get_git_root | ||
|
|
||
| _EXCLUDED_DIRS = {'dev_tools'} | ||
|
|
||
|
|
||
| def get_committed_notebooks(reporoot: Path) -> Dict[str, Path]: | ||
| """Return {stem: relative_path} for all committed .ipynb files under reporoot. | ||
|
|
||
| Excludes notebooks in dev_tools/ since those are developer utilities, | ||
| not user-facing documentation. | ||
| """ | ||
| result = subprocess.run( | ||
| ['git', 'ls-files', '*.ipynb'], capture_output=True, text=True, check=True, cwd=reporoot | ||
| ) | ||
| return { | ||
| Path(f).stem: Path(f) | ||
| for f in result.stdout.strip().split('\n') | ||
| if f and not any(Path(f).parts[0] == d for d in _EXCLUDED_DIRS) | ||
| } | ||
|
|
||
|
|
||
| def _is_notebook_marker(decorator: ast.expr) -> bool: | ||
| """Check if a decorator is @pytest.mark.notebook.""" | ||
| # Handle pytest.mark.notebook (attr chain) | ||
| if isinstance(decorator, ast.Attribute) and decorator.attr == 'notebook': | ||
| return True | ||
| return False | ||
|
|
||
|
|
||
| def find_notebook_tests(reporoot: Path) -> Dict[str, Tuple[Path, bool]]: | ||
| """Find all execute_notebook() calls in test files. | ||
|
|
||
| Searches all *_test.py files under the repo root (including qualtran/ | ||
| and tutorials/). | ||
|
|
||
| Returns {notebook_name: (test_file_path_relative, has_notebook_marker)}. | ||
| """ | ||
| results: Dict[str, Tuple[Path, bool]] = {} | ||
| for test_file in reporoot.rglob('*_test.py'): | ||
| try: | ||
| tree = ast.parse(test_file.read_text()) | ||
| except SyntaxError: | ||
| continue | ||
|
|
||
| for node in ast.walk(tree): | ||
| if not isinstance(node, ast.FunctionDef): | ||
| continue | ||
| # Check if function body contains execute_notebook('xxx') | ||
| for child in ast.walk(node): | ||
| if ( | ||
| isinstance(child, ast.Call) | ||
| and _is_execute_notebook_call(child) | ||
| and child.args | ||
| and isinstance(child.args[0], ast.Constant) | ||
| ): | ||
| nb_name = child.args[0].value | ||
| # Check for @pytest.mark.notebook decorator | ||
| has_marker = any(_is_notebook_marker(dec) for dec in node.decorator_list) | ||
| results[nb_name] = (test_file.relative_to(reporoot), has_marker) | ||
| return results | ||
|
|
||
|
|
||
| def _is_execute_notebook_call(node: ast.Call) -> bool: | ||
| """Check if a Call node is a call to execute_notebook (with or without module prefix).""" | ||
| if isinstance(node.func, ast.Attribute) and node.func.attr == 'execute_notebook': | ||
| return True | ||
| if isinstance(node.func, ast.Name) and node.func.id == 'execute_notebook': | ||
| return True | ||
| return False | ||
|
|
||
|
|
||
| def main(): | ||
| reporoot = get_git_root() | ||
|
|
||
| committed = get_committed_notebooks(reporoot) | ||
| tested = find_notebook_tests(reporoot) | ||
|
|
||
| errors = [] | ||
|
|
||
| for stem, nb_rel_path in sorted(committed.items()): | ||
| if stem not in tested: | ||
| # Suggest the likely test file location | ||
| nb_dir = nb_rel_path.parent | ||
| test_file = nb_dir / f'{stem}_test.py' | ||
| errors.append( | ||
| f" MISSING TEST: {nb_rel_path}\n" | ||
| f" Add to {test_file}:\n" | ||
| f"\n" | ||
| f" @pytest.mark.notebook\n" | ||
| f" def test_{stem}_notebook():\n" | ||
| f" qlt_testing.execute_notebook('{stem}')\n" | ||
| ) | ||
| else: | ||
| test_file, has_marker = tested[stem] | ||
| if not has_marker: | ||
| errors.append( | ||
| f" MISSING MARKER: {nb_rel_path}\n" | ||
| f" The test in {test_file} calls execute_notebook('{stem}')\n" | ||
| f" but is not decorated with @pytest.mark.notebook.\n" | ||
| f" Add the decorator so this test runs in the notebooks CI job:\n" | ||
| f"\n" | ||
| f" @pytest.mark.notebook\n" | ||
| f" def test_...():\n" | ||
| ) | ||
|
|
||
| if errors: | ||
| print(f"ERROR: {len(errors)} notebook(s) have issues:\n") | ||
| print("\n".join(errors)) | ||
| sys.exit(1) | ||
|
|
||
| print(f"OK: All {len(committed)} notebooks have properly marked tests.") | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| main() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,6 +79,7 @@ test = [ | |
| "pytest-xdist", | ||
|
|
||
| # test executing notebooks | ||
| "jupytext", | ||
| "ipykernel", | ||
| "filelock", | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # Copyright 2026 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| import pytest | ||
|
|
||
| from qualtran import testing as qlt_testing | ||
|
|
||
|
|
||
| @pytest.mark.notebook | ||
| def test_Adjoint_notebook(): | ||
| qlt_testing.execute_notebook('Adjoint') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # Copyright 2026 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| import pytest | ||
|
|
||
| from qualtran import testing as qlt_testing | ||
|
|
||
|
|
||
| @pytest.mark.notebook | ||
| def test_Autodoc_notebook(): | ||
| qlt_testing.execute_notebook('Autodoc') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # Copyright 2026 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| import pytest | ||
|
|
||
| from qualtran import testing as qlt_testing | ||
|
|
||
|
|
||
| @pytest.mark.notebook | ||
| def test_Controlled_notebook(): | ||
| qlt_testing.execute_notebook('Controlled') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # Copyright 2026 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| import pytest | ||
|
|
||
| from qualtran import testing as qlt_testing | ||
|
|
||
|
|
||
| @pytest.mark.notebook | ||
| def test_DataTypes_notebook(): | ||
| qlt_testing.execute_notebook('DataTypes') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # Copyright 2026 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| import pytest | ||
|
|
||
| from qualtran import testing as qlt_testing | ||
|
|
||
|
|
||
| @pytest.mark.notebook | ||
| def test_Protocols_notebook(): | ||
| qlt_testing.execute_notebook('Protocols') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # Copyright 2026 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| import pytest | ||
|
|
||
| from qualtran import testing as qlt_testing | ||
|
|
||
|
|
||
| @pytest.mark.notebook | ||
| def test_conversions_notebook(): | ||
| qlt_testing.execute_notebook('conversions') |
22 changes: 22 additions & 0 deletions
22
qualtran/bloqs/arithmetic/error_analysis_for_fxp_arithmetic_test.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| # Copyright 2026 Google LLC | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # https://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| import pytest | ||
|
|
||
| from qualtran import testing as qlt_testing | ||
|
|
||
|
|
||
| @pytest.mark.notebook | ||
| def test_error_analysis_for_fxp_arithmetic_notebook(): | ||
| qlt_testing.execute_notebook('error_analysis_for_fxp_arithmetic') |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.