Skip to content

Commit 6aff2ce

Browse files
classiqdorNadavClassiq
authored andcommitted
Prepare testing utils for testing specific notebooks
1 parent 743c4bd commit 6aff2ce

File tree

2 files changed

+112
-6
lines changed

2 files changed

+112
-6
lines changed

tests/utils_for_testbook.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import json
2+
import os
3+
from typing import Any, Callable
4+
import pytest
5+
6+
from testbook import testbook
7+
from tests.utils_for_tests import resolve_notebook_path, should_skip_notebook
8+
9+
from classiq.interface.generator.quantum_program import QuantumProgram
10+
11+
12+
def wrap_testbook(notebook_name: str, timeout_seconds: float = 10) -> Callable:
13+
def inner_decorator(func: Callable) -> Any:
14+
notebook_path = resolve_notebook_path(notebook_name)
15+
16+
for decorator in [
17+
testbook(notebook_path, execute=True, timeout=timeout_seconds),
18+
_build_cd_decorator(notebook_path),
19+
_build_skip_decorator(notebook_path),
20+
]:
21+
func = decorator(func)
22+
return func
23+
24+
return inner_decorator
25+
26+
27+
# The purpose of the `cd_decorator` is to execute the test in the same folder as the `ipynb` file
28+
# so that relative files (images, csv, etc.) will be available
29+
def _build_cd_decorator(file_path: str) -> Callable:
30+
def cd_decorator(func: Callable) -> Any:
31+
def inner(*args: Any, **kwargs: Any) -> Any:
32+
previous_dir = os.getcwd()
33+
os.chdir(os.path.dirname(file_path))
34+
35+
func(*args, **kwargs)
36+
37+
os.chdir(previous_dir)
38+
39+
return inner
40+
41+
return cd_decorator
42+
43+
44+
def _build_skip_decorator(notebook_path: str) -> Callable:
45+
notebook_name = os.path.basename(notebook_path)
46+
return pytest.mark.skipif(
47+
should_skip_notebook(notebook_name),
48+
reason="Didn't change",
49+
)
50+
51+
52+
def validate_quantum_model(model: str) -> None:
53+
# currently that's some dummy test - only checking that it's a valid dict
54+
assert isinstance(json.loads(model), dict)
55+
56+
57+
def validate_quantum_program_size(
58+
quantum_program: str,
59+
expected_width: int | None = None,
60+
expected_depth: int | None = None,
61+
) -> None:
62+
qp = QuantumProgram.model_validate_json(quantum_program)
63+
64+
actual_width = qp.data.width
65+
if expected_width is not None:
66+
assert (
67+
actual_width <= expected_width
68+
), f"The width of the circuit changed! (for the worse!). From {expected_width} to {actual_width}"
69+
70+
assert qp.transpiled_circuit is not None # for mypy
71+
actual_depth = qp.transpiled_circuit.depth
72+
if expected_depth is not None:
73+
assert (
74+
actual_depth <= expected_depth
75+
), f"The depth of the circuit changed! (for the worse!). From {expected_depth} to {actual_depth}"

tests/utils_for_tests.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import os
2-
from collections.abc import Iterable
2+
from functools import lru_cache
33
from pathlib import Path
44

55
ROOT_DIRECTORY = Path(__file__).parents[1]
66

77

8-
def iterate_notebooks() -> Iterable[str]:
8+
def iterate_notebooks() -> list[str]:
99
if os.environ.get("SHOULD_TEST_ALL_FILES", "") == "true":
1010
notebooks_to_test = _get_all_notebooks()
1111
else:
@@ -14,8 +14,39 @@ def iterate_notebooks() -> Iterable[str]:
1414
return notebooks_to_test
1515

1616

17-
def _get_all_notebooks(directory: Path = ROOT_DIRECTORY) -> Iterable[str]:
18-
for root, _, files in os.walk(directory):
17+
@lru_cache
18+
def iterate_notebook_names() -> list[str]:
19+
return list(map(os.path.basename, iterate_notebooks()))
20+
21+
22+
@lru_cache
23+
def _get_all_notebooks(
24+
directory: Path = ROOT_DIRECTORY, suffix: str = ".ipynb"
25+
) -> list[str]:
26+
return [
27+
file
28+
for root, _, files in os.walk(directory)
29+
for file in files
30+
if file.endswith(suffix)
31+
]
32+
33+
34+
def should_run_notebook(notebook_name: str) -> bool:
35+
return notebook_name in iterate_notebook_names()
36+
37+
38+
def should_skip_notebook(notebook_name: str) -> bool:
39+
return not should_run_notebook(notebook_name)
40+
41+
42+
@lru_cache
43+
def resolve_notebook_path(notebook_name: str) -> str:
44+
notebook_name_lower = notebook_name.lower()
45+
if not notebook_name_lower.endswith(".ipynb"):
46+
notebook_name_lower += ".ipynb"
47+
48+
for root, _, files in os.walk(ROOT_DIRECTORY):
1949
for file in files:
20-
if file.endswith(".ipynb"):
21-
yield os.path.join(root, file)
50+
if file.lower() == notebook_name_lower:
51+
return os.path.join(root, file)
52+
raise LookupError(f"Notebook not found: {notebook_name}")

0 commit comments

Comments
 (0)