mirror of https://github.com/pyodide/pyodide.git
Use tblib to pickle errors in `run_in_pyodide` (#2619)
This commit is contained in:
parent
806e5dff61
commit
d818f410c4
|
@ -6,7 +6,7 @@ defaults: &defaults
|
|||
# Note: when updating the docker image version,
|
||||
# make sure there are no extra old versions lying around.
|
||||
# (e.g. `rg -F --hidden <old_tag>`)
|
||||
- image: pyodide/pyodide-env:20220504-py310-chrome101-firefox100
|
||||
- image: pyodide/pyodide-env:20220525-py310-chrome102-firefox100
|
||||
environment:
|
||||
- EMSDK_NUM_CORES: 3
|
||||
EMCC_CORES: 3
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package:
|
||||
name: tblib
|
||||
version: 1.7.0
|
||||
source:
|
||||
url: https://files.pythonhosted.org/packages/f8/cd/2fad4add11c8837e72f50a30e2bda30e67a10d70462f826b291443a55c7d/tblib-1.7.0-py2.py3-none-any.whl
|
||||
sha256: 289fa7359e580950e7d9743eab36b0691f0310fce64dee7d9c31065b8f723e23
|
||||
test:
|
||||
imports:
|
||||
- tblib
|
||||
about:
|
||||
home: https://github.com/ionelmc/python-tblib
|
||||
PyPI: https://pypi.org/project/tblib
|
||||
summary: Traceback serialization library.
|
||||
license: BSD-2-Clause
|
|
@ -76,6 +76,7 @@ CORE_PACKAGES = {
|
|||
"cpp-exceptions-test",
|
||||
"ssl",
|
||||
"pytest",
|
||||
"tblib",
|
||||
}
|
||||
|
||||
CORE_SCIPY_PACKAGES = {
|
||||
|
|
|
@ -3,7 +3,6 @@ import pickle
|
|||
import sys
|
||||
from base64 import b64decode, b64encode
|
||||
from copy import deepcopy
|
||||
from traceback import TracebackException
|
||||
from typing import Any, Callable, Collection
|
||||
|
||||
from pyodide_test_runner.utils import package_is_built as _package_is_built
|
||||
|
@ -169,6 +168,9 @@ class run_in_pyodide:
|
|||
REWRITTEN_MODULE_ASTS if pytest_assert_rewrites else ORIGINAL_MODULE_ASTS
|
||||
)
|
||||
|
||||
if package_is_built("tblib"):
|
||||
self._pkgs.append("tblib")
|
||||
|
||||
self._pytest_assert_rewrites = pytest_assert_rewrites
|
||||
|
||||
def _code_template(self, args: tuple) -> str:
|
||||
|
@ -186,15 +188,20 @@ class run_in_pyodide:
|
|||
co = compile(mod, {self._module_filename!r}, "exec")
|
||||
d = {{}}
|
||||
exec(co, d)
|
||||
def encode(x):
|
||||
return b64encode(pickle.dumps(x)).decode()
|
||||
try:
|
||||
result = d[{self._func_name!r}](None, *args)
|
||||
if {self._async_func}:
|
||||
result = await result
|
||||
return [0, encode(result)]
|
||||
except BaseException as e:
|
||||
import traceback
|
||||
tb = traceback.TracebackException(type(e), e, e.__traceback__)
|
||||
serialized_err = pickle.dumps(tb)
|
||||
return b64encode(serialized_err).decode()
|
||||
try:
|
||||
from tblib import pickling_support
|
||||
pickling_support.install()
|
||||
except ImportError:
|
||||
pass
|
||||
return [1, encode(e)]
|
||||
|
||||
try:
|
||||
result = await __tmp()
|
||||
|
@ -210,27 +217,14 @@ class run_in_pyodide:
|
|||
if self._pkgs:
|
||||
selenium.load_package(self._pkgs)
|
||||
|
||||
result = selenium.run_async(code)
|
||||
r = selenium.run_async(code)
|
||||
[status, result] = r
|
||||
|
||||
if result:
|
||||
err: TracebackException = pickle.loads(b64decode(result))
|
||||
err.stack.pop(0) # Get rid of __tmp in traceback
|
||||
self._fail(err)
|
||||
|
||||
def _fail(self, err: TracebackException):
|
||||
"""
|
||||
Fail the test with a helpful message.
|
||||
|
||||
Separated out for test mock purposes.
|
||||
"""
|
||||
msg = "Error running function in pyodide\n\n" + "".join(err.format(chain=True))
|
||||
if self._pytest_not_built:
|
||||
msg += (
|
||||
"\n"
|
||||
"Note: pytest not available in Pyodide. We could generate a"
|
||||
"better traceback if pytest were available."
|
||||
)
|
||||
pytest.fail(msg, pytrace=False)
|
||||
result = pickle.loads(b64decode(result))
|
||||
if status:
|
||||
raise result
|
||||
else:
|
||||
return result
|
||||
|
||||
def _generate_pyodide_ast(
|
||||
self, module_ast: ast.Module, funcname: str, func_line_no: int
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import asyncio
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from pyodide_test_runner.decorator import run_in_pyodide
|
||||
|
@ -53,43 +51,19 @@ class selenium_mock:
|
|||
return asyncio.new_event_loop().run_until_complete(eval_code_async(code))
|
||||
|
||||
|
||||
@dataclass
|
||||
class local_mocks_cls:
|
||||
exc_list: list[Any] = field(default_factory=list)
|
||||
|
||||
def check_err(self, ty, msg):
|
||||
try:
|
||||
assert self.exc_list
|
||||
err = self.exc_list[0]
|
||||
assert err
|
||||
assert "".join(err.format_exception_only()) == msg
|
||||
finally:
|
||||
del self.exc_list[0]
|
||||
|
||||
def _patched_fail(self, exc):
|
||||
self.exc_list.append(exc)
|
||||
def test_local1():
|
||||
with pytest.raises(AssertionError, match="assert 6 == 7"):
|
||||
example_func1(selenium_mock)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def local_mocks(monkeypatch):
|
||||
mocks = local_mocks_cls()
|
||||
monkeypatch.setattr(run_in_pyodide, "_fail", mocks._patched_fail)
|
||||
return mocks
|
||||
def test_local2():
|
||||
with pytest.raises(AssertionError, match="assert 6 == 7"):
|
||||
example_func2(selenium_mock)
|
||||
|
||||
|
||||
def test_local1(local_mocks):
|
||||
example_func1(selenium_mock)
|
||||
local_mocks.check_err(AssertionError, "AssertionError: assert 6 == 7\n")
|
||||
|
||||
|
||||
def test_local2(local_mocks):
|
||||
example_func1(selenium_mock)
|
||||
local_mocks.check_err(AssertionError, "AssertionError: assert 6 == 7\n")
|
||||
|
||||
|
||||
def test_local3(local_mocks):
|
||||
async_example_func(selenium_mock)
|
||||
local_mocks.check_err(AssertionError, "AssertionError: assert 6 == 7\n")
|
||||
def test_local3():
|
||||
with pytest.raises(AssertionError, match="assert 6 == 7"):
|
||||
async_example_func(selenium_mock)
|
||||
|
||||
|
||||
def test_local_inner_function():
|
||||
|
@ -129,7 +103,7 @@ def example_decorator_func(selenium):
|
|||
pass
|
||||
|
||||
|
||||
def test_local4(local_mocks):
|
||||
def test_local4():
|
||||
example_decorator_func(selenium_mock)
|
||||
assert example_decorator_func.dec_info == [
|
||||
("testdec1", "a"),
|
||||
|
@ -138,18 +112,13 @@ def test_local4(local_mocks):
|
|||
]
|
||||
|
||||
|
||||
def test_local5(local_mocks):
|
||||
example_func1(selenium_mock)
|
||||
local_mocks.check_err(AssertionError, "AssertionError: assert 6 == 7\n")
|
||||
|
||||
|
||||
class selenium_mock_fail_load_package(selenium_mock):
|
||||
@staticmethod
|
||||
def load_package(*args, **kwargs):
|
||||
raise OSError("STOP!")
|
||||
|
||||
|
||||
def test_local_fail_load_package(local_mocks):
|
||||
def test_local_fail_load_package():
|
||||
exc = None
|
||||
try:
|
||||
example_func1(selenium_mock_fail_load_package)
|
||||
|
@ -169,13 +138,12 @@ def test_local_fail_load_package(local_mocks):
|
|||
)
|
||||
|
||||
|
||||
def test_selenium(selenium, local_mocks):
|
||||
example_func1(selenium)
|
||||
def test_selenium(selenium):
|
||||
with pytest.raises(AssertionError, match="assert 6 == 7"):
|
||||
example_func1(selenium)
|
||||
|
||||
local_mocks.check_err(AssertionError, "AssertionError: assert 6 == 7\n")
|
||||
|
||||
example_func2(selenium)
|
||||
local_mocks.check_err(AssertionError, "AssertionError: assert 6 == 7\n")
|
||||
with pytest.raises(AssertionError, match="assert 6 == 7"):
|
||||
example_func2(selenium)
|
||||
|
||||
|
||||
@run_in_pyodide
|
||||
|
|
|
@ -15,5 +15,6 @@
|
|||
pytest-rerunfailures
|
||||
pytest-xdist
|
||||
selenium==4.1.0
|
||||
tblib
|
||||
# maintenance
|
||||
bump2version
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
PYODIDE_IMAGE_REPO="pyodide"
|
||||
PYODIDE_IMAGE_TAG="20220504-py310-chrome101-firefox100"
|
||||
PYODIDE_IMAGE_TAG="20220525-py310-chrome102-firefox100"
|
||||
PYODIDE_PREBUILT_IMAGE_TAG="0.20.0"
|
||||
DEFAULT_PYODIDE_DOCKER_IMAGE="${PYODIDE_IMAGE_REPO}/pyodide-env:${PYODIDE_IMAGE_TAG}"
|
||||
DEFAULT_PYODIDE_SYSTEM_PORT="none"
|
||||
|
|
Loading…
Reference in New Issue