Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 19 additions & 11 deletions .github/actions/send_qmods_slack_notification/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,25 @@ runs:
run: |
echo FAILING_TESTS=$(
yq -o json -I 0 -p xml '
.testsuites[].testcase[] | select(.failure // .error) |
select((.failure // .error)["+@message"] | test("^((AssertionError: Qmod file)|(AssertionError: Found uncommitted Qmod)).*")) |
{
"name": (.["+@classname"] + "::" + .["+@name"]),
"message": (.failure // .error)["+@message"]
}
' ./pytest-results/test-results.xml | \
jq -r -s '
sort_by(.name) | .[] |
"• " + .name + ": " + (.message | @html | split("\n") | .[0]) + "\n\n"
'
.testsuites[].testcase[]
| select(.failure // .error)
| select(
(.failure // .error)["+content"]
| test("(?:^|\n)E?(?:\s*)?(?:\|\s*)?(?:ValueError: (?:Qmod file)|(?:Found uncommitted Qmod))")
)
| {
"name": (.["+@classname"] + "::" + .["+@name"]),
"message": (
(.failure // .error)["+content"]
| match("(?:^|\n)E?(?:\s*)?(?:\|\s*)?((?:ValueError: (?:Qmod file)|(?:Found uncommitted Qmod))[^\n$]+)")
.captures[0].string
)
}
' ./pytest-results/test-results.xml | \
jq -r -s '
sort_by(.name) | .[] |
"• " + .name + ": " + (.message | @html | split("\n") | .[0]) + "\n\n"
'
) >> $GITHUB_ENV

- name: Send JSON to slack workflow
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/Test-CI-daily-notebooks-algorithms.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ jobs:
id: list-files
run: |
shopt -s globstar
echo "files=$(echo algorithms/**/*.ipynb)" >> $GITHUB_OUTPUT
echo "files=algorithms/bernstein_vazirani/bernstein_vazirani.ipynb algorithms/deutsch_jozsa/deutsch_jozsa.ipynb algorithms/swap_test/swap_test.ipynb" >> $GITHUB_OUTPUT
# echo "files=$(echo algorithms/**/*.ipynb)" >> $GITHUB_OUTPUT

# Run Notebook Tests
- name: Run Notebooks
Expand Down
1 change: 0 additions & 1 deletion algorithms/bernstein_vazirani/bernstein_vazirani.qmod
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
qperm bv_predicate(a: int, const x: qbit[], res: qbit) {
repeat (i: x.len) {
if ((floor(a / (2 ** i)) % 2) == 1) {
CX(x[i], res);
Expand Down
1 change: 0 additions & 1 deletion algorithms/deutsch_jozsa/simple_deutsch_jozsa.qmod
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
qfunc deutsch_jozsa(predicate: qfunc (qnum, qbit), x: qnum) {
within {
hadamard_transform(x);
} apply {
Expand Down
1 change: 1 addition & 0 deletions tests/notebooks/test_bernstein_vazirani.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

@wrap_testbook("bernstein_vazirani", timeout_seconds=30)
def test_notebook(tb: TestbookNotebookClient) -> None:
x = y
# test quantum programs
validate_quantum_program_size(
tb.ref_pydantic("qprog"),
Expand Down
63 changes: 54 additions & 9 deletions tests/utils_for_qmod.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,69 @@
from typing import Any, Callable


def strip_inners_from_exception(exc: Exception) -> Exception:
tb = exc.__traceback__
while (tb is not None) and (
# strip "inner" functions (inside decorators)
(tb.tb_frame.f_code.co_name.startswith("inner"))
# strip "testbook" wrapper
or (
tb.tb_frame.f_code.co_filename.endswith("/testbook.py")
and tb.tb_frame.f_code.co_name == "wrapper"
)
):
tb = tb.tb_next

# if we stripped too much, that's probably actually an error from an "inner"
if tb is None:
return exc
else:
return exc.with_traceback(tb)


class StrippedExceptionGroup(ExceptionGroup):
def __init__(self, message: str, exceptions: list[Exception]) -> None:
# super().__init__(message, exceptions)
exceptions_stripped = list(map(strip_inners_from_exception, exceptions))
super().__init__(message, exceptions_stripped)

def __str__(self) -> str:
return f"{super().__str__()} [{self.exceptions[0]}]"


def qmod_compare_decorator(func: Callable) -> Any:
def inner(*args: Any, **kwargs: Any) -> Any:
def inner_qmod(*args: Any, **kwargs: Any) -> Any:
qmods_before = _read_qmod_files()

# collect the error of the test itself, in case of such error
all_errors = []
try:
result = func(*args, **kwargs)
error = None
except Exception as exc:
all_errors.append(str(exc))
error = exc

qmods_after = _read_qmod_files()
# collect the errors of the qmod comparison, in case of such errors
# prepend all the errors from `compare_qmods`, so that `actions/send_qmod_slack_notification` will have a simpler regex
all_errors = _compare_qmods(qmods_before, qmods_after) + all_errors
comparison_errors = _compare_qmods(qmods_before, qmods_after)

if comparison_errors or error:
all_errors = []
if error is not None:
# put error from `func` first
all_errors.append(error)
all_errors.extend(comparison_errors)

# raise all errors
assert not all_errors, "\n".join(all_errors)
if len(all_errors) == 1:
raise strip_inners_from_exception(all_errors[0])
else:
raise StrippedExceptionGroup(
"Main test + qmod compare errors", all_errors
)

return result

return inner
return inner_qmod


def _read_qmod_files(file_path: str = ".") -> dict[str, str]:
Expand All @@ -50,12 +91,16 @@ def _compare_qmods(old_files: dict[str, str], new_files: dict[str, str]) -> list
errors = []
if len(new_files) > len(old_files):
errors.append(
f"Found uncommitted Qmod files: {', '.join(new_files.keys() - old_files.keys())}"
ValueError(
f"Found uncommitted Qmod files: {', '.join(new_files.keys() - old_files.keys())}"
)
)

for file_name, old_content in old_files.items():
new_content = new_files[file_name]
if old_content != new_content:
errors.append(f"Qmod file {os.path.basename(file_name)} is not up-to-date")
errors.append(
ValueError(f"Qmod file {os.path.basename(file_name)} is not up-to-date")
)

return errors
8 changes: 4 additions & 4 deletions tests/utils_for_testbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,14 @@ def inner_decorator(func: Callable) -> Any:

def _build_patch_testbook_client_decorator(notebook_name: str) -> Callable:
def patch_testbook_client_decorator(func: Callable) -> Any:
def inner(*args: Any, **kwargs: Any) -> Any:
def inner_patch(*args: Any, **kwargs: Any) -> Any:
for arg in itertools.chain(args, kwargs.values()):
if isinstance(arg, TestbookNotebookClient):
arg._notebook_name = notebook_name

return func(*args, *kwargs)

return inner
return inner_patch

return patch_testbook_client_decorator

Expand All @@ -86,7 +86,7 @@ def inner(*args: Any, **kwargs: Any) -> Any:
# so that relative files (images, csv, etc.) will be available
def _build_cd_decorator(file_path: str) -> Callable:
def cd_decorator(func: Callable) -> Any:
def inner(*args: Any, **kwargs: Any) -> Any:
def inner_cd(*args: Any, **kwargs: Any) -> Any:
previous_dir = os.getcwd()
os.chdir(ROOT_DIRECTORY)
os.chdir(os.path.dirname(file_path))
Expand All @@ -96,7 +96,7 @@ def inner(*args: Any, **kwargs: Any) -> Any:
os.chdir(previous_dir)
return result

return inner
return inner_cd

return cd_decorator

Expand Down
Loading