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,
|
# Note: when updating the docker image version,
|
||||||
# make sure there are no extra old versions lying around.
|
# make sure there are no extra old versions lying around.
|
||||||
# (e.g. `rg -F --hidden <old_tag>`)
|
# (e.g. `rg -F --hidden <old_tag>`)
|
||||||
- image: pyodide/pyodide-env:20220504-py310-chrome101-firefox100
|
- image: pyodide/pyodide-env:20220525-py310-chrome102-firefox100
|
||||||
environment:
|
environment:
|
||||||
- EMSDK_NUM_CORES: 3
|
- EMSDK_NUM_CORES: 3
|
||||||
EMCC_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",
|
"cpp-exceptions-test",
|
||||||
"ssl",
|
"ssl",
|
||||||
"pytest",
|
"pytest",
|
||||||
|
"tblib",
|
||||||
}
|
}
|
||||||
|
|
||||||
CORE_SCIPY_PACKAGES = {
|
CORE_SCIPY_PACKAGES = {
|
||||||
|
|
|
@ -3,7 +3,6 @@ import pickle
|
||||||
import sys
|
import sys
|
||||||
from base64 import b64decode, b64encode
|
from base64 import b64decode, b64encode
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from traceback import TracebackException
|
|
||||||
from typing import Any, Callable, Collection
|
from typing import Any, Callable, Collection
|
||||||
|
|
||||||
from pyodide_test_runner.utils import package_is_built as _package_is_built
|
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
|
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
|
self._pytest_assert_rewrites = pytest_assert_rewrites
|
||||||
|
|
||||||
def _code_template(self, args: tuple) -> str:
|
def _code_template(self, args: tuple) -> str:
|
||||||
|
@ -186,15 +188,20 @@ class run_in_pyodide:
|
||||||
co = compile(mod, {self._module_filename!r}, "exec")
|
co = compile(mod, {self._module_filename!r}, "exec")
|
||||||
d = {{}}
|
d = {{}}
|
||||||
exec(co, d)
|
exec(co, d)
|
||||||
|
def encode(x):
|
||||||
|
return b64encode(pickle.dumps(x)).decode()
|
||||||
try:
|
try:
|
||||||
result = d[{self._func_name!r}](None, *args)
|
result = d[{self._func_name!r}](None, *args)
|
||||||
if {self._async_func}:
|
if {self._async_func}:
|
||||||
result = await result
|
result = await result
|
||||||
|
return [0, encode(result)]
|
||||||
except BaseException as e:
|
except BaseException as e:
|
||||||
import traceback
|
try:
|
||||||
tb = traceback.TracebackException(type(e), e, e.__traceback__)
|
from tblib import pickling_support
|
||||||
serialized_err = pickle.dumps(tb)
|
pickling_support.install()
|
||||||
return b64encode(serialized_err).decode()
|
except ImportError:
|
||||||
|
pass
|
||||||
|
return [1, encode(e)]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = await __tmp()
|
result = await __tmp()
|
||||||
|
@ -210,27 +217,14 @@ class run_in_pyodide:
|
||||||
if self._pkgs:
|
if self._pkgs:
|
||||||
selenium.load_package(self._pkgs)
|
selenium.load_package(self._pkgs)
|
||||||
|
|
||||||
result = selenium.run_async(code)
|
r = selenium.run_async(code)
|
||||||
|
[status, result] = r
|
||||||
|
|
||||||
if result:
|
result = pickle.loads(b64decode(result))
|
||||||
err: TracebackException = pickle.loads(b64decode(result))
|
if status:
|
||||||
err.stack.pop(0) # Get rid of __tmp in traceback
|
raise result
|
||||||
self._fail(err)
|
else:
|
||||||
|
return result
|
||||||
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)
|
|
||||||
|
|
||||||
def _generate_pyodide_ast(
|
def _generate_pyodide_ast(
|
||||||
self, module_ast: ast.Module, funcname: str, func_line_no: int
|
self, module_ast: ast.Module, funcname: str, func_line_no: int
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pyodide_test_runner.decorator import run_in_pyodide
|
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))
|
return asyncio.new_event_loop().run_until_complete(eval_code_async(code))
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
def test_local1():
|
||||||
class local_mocks_cls:
|
with pytest.raises(AssertionError, match="assert 6 == 7"):
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def local_mocks(monkeypatch):
|
|
||||||
mocks = local_mocks_cls()
|
|
||||||
monkeypatch.setattr(run_in_pyodide, "_fail", mocks._patched_fail)
|
|
||||||
return mocks
|
|
||||||
|
|
||||||
|
|
||||||
def test_local1(local_mocks):
|
|
||||||
example_func1(selenium_mock)
|
example_func1(selenium_mock)
|
||||||
local_mocks.check_err(AssertionError, "AssertionError: assert 6 == 7\n")
|
|
||||||
|
|
||||||
|
|
||||||
def test_local2(local_mocks):
|
def test_local2():
|
||||||
example_func1(selenium_mock)
|
with pytest.raises(AssertionError, match="assert 6 == 7"):
|
||||||
local_mocks.check_err(AssertionError, "AssertionError: assert 6 == 7\n")
|
example_func2(selenium_mock)
|
||||||
|
|
||||||
|
|
||||||
def test_local3(local_mocks):
|
def test_local3():
|
||||||
|
with pytest.raises(AssertionError, match="assert 6 == 7"):
|
||||||
async_example_func(selenium_mock)
|
async_example_func(selenium_mock)
|
||||||
local_mocks.check_err(AssertionError, "AssertionError: assert 6 == 7\n")
|
|
||||||
|
|
||||||
|
|
||||||
def test_local_inner_function():
|
def test_local_inner_function():
|
||||||
|
@ -129,7 +103,7 @@ def example_decorator_func(selenium):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_local4(local_mocks):
|
def test_local4():
|
||||||
example_decorator_func(selenium_mock)
|
example_decorator_func(selenium_mock)
|
||||||
assert example_decorator_func.dec_info == [
|
assert example_decorator_func.dec_info == [
|
||||||
("testdec1", "a"),
|
("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):
|
class selenium_mock_fail_load_package(selenium_mock):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_package(*args, **kwargs):
|
def load_package(*args, **kwargs):
|
||||||
raise OSError("STOP!")
|
raise OSError("STOP!")
|
||||||
|
|
||||||
|
|
||||||
def test_local_fail_load_package(local_mocks):
|
def test_local_fail_load_package():
|
||||||
exc = None
|
exc = None
|
||||||
try:
|
try:
|
||||||
example_func1(selenium_mock_fail_load_package)
|
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):
|
def test_selenium(selenium):
|
||||||
|
with pytest.raises(AssertionError, match="assert 6 == 7"):
|
||||||
example_func1(selenium)
|
example_func1(selenium)
|
||||||
|
|
||||||
local_mocks.check_err(AssertionError, "AssertionError: assert 6 == 7\n")
|
with pytest.raises(AssertionError, match="assert 6 == 7"):
|
||||||
|
|
||||||
example_func2(selenium)
|
example_func2(selenium)
|
||||||
local_mocks.check_err(AssertionError, "AssertionError: assert 6 == 7\n")
|
|
||||||
|
|
||||||
|
|
||||||
@run_in_pyodide
|
@run_in_pyodide
|
||||||
|
|
|
@ -15,5 +15,6 @@
|
||||||
pytest-rerunfailures
|
pytest-rerunfailures
|
||||||
pytest-xdist
|
pytest-xdist
|
||||||
selenium==4.1.0
|
selenium==4.1.0
|
||||||
|
tblib
|
||||||
# maintenance
|
# maintenance
|
||||||
bump2version
|
bump2version
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
PYODIDE_IMAGE_REPO="pyodide"
|
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"
|
PYODIDE_PREBUILT_IMAGE_TAG="0.20.0"
|
||||||
DEFAULT_PYODIDE_DOCKER_IMAGE="${PYODIDE_IMAGE_REPO}/pyodide-env:${PYODIDE_IMAGE_TAG}"
|
DEFAULT_PYODIDE_DOCKER_IMAGE="${PYODIDE_IMAGE_REPO}/pyodide-env:${PYODIDE_IMAGE_TAG}"
|
||||||
DEFAULT_PYODIDE_SYSTEM_PORT="none"
|
DEFAULT_PYODIDE_SYSTEM_PORT="none"
|
||||||
|
|
Loading…
Reference in New Issue