Skip to content
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

Fix JsException passing from pyodide environment to host #44

Merged
merged 8 commits into from
Oct 12, 2022
23 changes: 22 additions & 1 deletion pytest_pyodide/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
from base64 import b64decode, b64encode
from collections.abc import Callable, Collection
from copy import deepcopy
from io import BytesIO
from typing import Any

import pytest

from .hook import ORIGINAL_MODULE_ASTS, REWRITTEN_MODULE_ASTS
from .pyodide import JsException
from .utils import package_is_built as _package_is_built


Expand All @@ -35,6 +37,25 @@ def _encode(obj: Any) -> str:
return b64encode(pickle.dumps(obj)).decode()


class Unpickler(pickle.Unpickler):
def find_class(self, module, name):
"""
Catch exceptions that only exist in the pyodide environment and
convert them to exception in the host.
"""
if module == "pyodide" and name == "JsException":
return JsException
else:
return super().find_class(module, name)


def _decode(result: str) -> Any:
buffer = BytesIO()
buffer.write(b64decode(result))
buffer.seek(0)
return Unpickler(buffer).load()


def _create_outer_test_function(
run_test: Callable,
node: Any,
Expand Down Expand Up @@ -229,7 +250,7 @@ def _run_test(self, selenium: SeleniumType, args: tuple):
r = selenium.run_async(code)
[status, result] = r

result = pickle.loads(b64decode(result))
result = _decode(result)
if status:
raise result
else:
Expand Down
15 changes: 15 additions & 0 deletions pytest_pyodide/pyodide.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class JsException(Exception):
"""
The python code of the test can call javascript functions using
```
from js import XMLHttpRequest

xhr = XMLHttpRequest.new()
xhr.responseType = 'arraybuffer';
xhr.open('url', None, False) # this will fail in main thread
```

The code fails and raises a `JsException` in the pyodide environment. When the exception
is sent back to host, the host tries to unpickle the exception. The unpickle will fail
because "pyodide.JsException" only exists in the pyodide environment.
"""
15 changes: 15 additions & 0 deletions tests/test_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from pytest_pyodide.decorator import run_in_pyodide
from pytest_pyodide.hypothesis import any_strategy, std_hypothesis_settings
from pytest_pyodide.pyodide import JsException
from pytest_pyodide.utils import parse_driver_timeout


Expand Down Expand Up @@ -69,6 +70,20 @@ def inner_function(selenium, x):
assert inner_function(selenium, 6) == 7


def test_inner_function_js_exception(selenium):
@run_in_pyodide
def inner_function(selenium):
from js import eval as js_eval

js_eval("throw 'some error'")
koenvo marked this conversation as resolved.
Show resolved Hide resolved

with pytest.raises(
JsException,
match="Error: some error",
):
inner_function(selenium)


def complicated_decorator(attr_name: str):
def inner_func(value):
def dec(func):
Expand Down