diff --git a/packages/nlopt/test_nlopt.py b/packages/nlopt/test_nlopt.py index 119f81bc9..b9bf24dc0 100644 --- a/packages/nlopt/test_nlopt.py +++ b/packages/nlopt/test_nlopt.py @@ -1,7 +1,12 @@ from pyodide_build.testing import run_in_pyodide -@run_in_pyodide(packages=["nlopt"]) +@run_in_pyodide( + packages=["nlopt"], + xfail_browsers={ + "chrome": "nlopt set_min_objective triggers a fatal runtime error in chrome 89 see #1493", + }, +) def test_nlopt(): import numpy as np import nlopt diff --git a/packages/numcodecs/test_numcodecs.py b/packages/numcodecs/test_numcodecs.py index 8d1dc1592..c234bdd3f 100644 --- a/packages/numcodecs/test_numcodecs.py +++ b/packages/numcodecs/test_numcodecs.py @@ -1,10 +1,15 @@ from pyodide_build.testing import run_in_pyodide -@run_in_pyodide(standalone=True, packages=["numcodecs", "numpy"]) +@run_in_pyodide( + standalone=True, + packages=["numcodecs", "numpy"], + xfail_browsers={ + "chrome": "test_numcodecs triggers a recursion error in chrome", + }, +) def test_blosc(): import numpy as np - from numcodecs import blosc from numcodecs.blosc import Blosc from numcodecs.zstd import Zstd from numcodecs.lz4 import LZ4 diff --git a/packages/pandas/test_pandas.py b/packages/pandas/test_pandas.py index 064fc04f5..b14f445d8 100644 --- a/packages/pandas/test_pandas.py +++ b/packages/pandas/test_pandas.py @@ -51,6 +51,10 @@ def test_extra_import(selenium, request): @pytest.mark.skip_refcount_check def test_load_largish_file(selenium_standalone, request, httpserver): selenium = selenium_standalone + if selenium.browser == "chrome": + pytest.xfail( + "test_load_largish_file triggers a fatal runtime error in Chrome 89 see #1495" + ) selenium.load_package("pandas") selenium.load_package("matplotlib") diff --git a/pyodide_build/testing.py b/pyodide_build/testing.py index 55d39e961..fa582bc1f 100644 --- a/pyodide_build/testing.py +++ b/pyodide_build/testing.py @@ -1,13 +1,27 @@ import pytest import inspect -from typing import Optional, List, Callable, Union +from typing import Callable, Dict, List, Optional, Union import contextlib +def _run_in_pyodide_get_source(f): + lines, start_line = inspect.getsourcelines(f) + num_decorator_lines = 0 + for line in lines: + if line.startswith("def"): + break + num_decorator_lines += 1 + start_line += num_decorator_lines - 1 + # Remove first line, which is the decorator. Then pad with empty lines to fix line number. + lines = ["\n"] * start_line + lines[num_decorator_lines:] + return "".join(lines) + + def run_in_pyodide( _function: Optional[Callable] = None, standalone: bool = False, packages: List[str] = [], + xfail_browsers: Dict[str, str] = {}, driver_timeout: Optional[Union[str, int]] = None, ) -> Callable: """ @@ -30,14 +44,12 @@ def run_in_pyodide( def decorator(f): def inner(selenium): + if selenium.browser in xfail_browsers: + xfail_message = xfail_browsers[selenium.browser] + pytest.xfail(xfail_message) with set_webdriver_script_timeout(selenium, driver_timeout): if len(packages) > 0: selenium.load_package(packages) - lines, start_line = inspect.getsourcelines(f) - # Remove first line, which is the decorator. Then pad with empty lines to fix line number. - lines = ["\n"] * start_line + lines[1:] - source = "".join(lines) - err = None try: # When writing the function, we set the filename to the file @@ -51,7 +63,7 @@ def run_in_pyodide( true, // quiet_trailing_semicolon {!r} // filename )""".format( - source, inspect.getsourcefile(f) + _run_in_pyodide_get_source(f), inspect.getsourcefile(f) ) ) # When invoking the function, use the default filename diff --git a/src/tests/test_testing.py b/src/tests/test_testing.py index 36f7f5558..b311490d6 100644 --- a/src/tests/test_testing.py +++ b/src/tests/test_testing.py @@ -1,5 +1,6 @@ import pathlib -from pyodide_build.testing import run_in_pyodide +from pyodide_build.testing import run_in_pyodide, _run_in_pyodide_get_source +from textwrap import dedent def test_web_server_secondary(selenium, web_server_secondary): @@ -13,6 +14,45 @@ def test_run_in_pyodide(): pass +def dummy_decorator(*args, **kwargs): + def func(f): + return f + + return func + + +@dummy_decorator( + packages=["nlopt"], + xfail_browsers={ + "chrome": "nlopt set_min_objective triggers a fatal runtime error in chrome 89 see #1493", + }, +) +def some_func(): + import numpy as np + import nlopt + + opt = nlopt.opt(nlopt.LD_SLSQP, 2) + opt.set_min_objective(f) + opt.set_lower_bounds(np.array([2.5, 7])) + + +def test_run_in_pyodide_multiline_decorator(): + assert ( + _run_in_pyodide_get_source(some_func).strip() + == dedent( + """ + def some_func(): + import numpy as np + import nlopt + + opt = nlopt.opt(nlopt.LD_SLSQP, 2) + opt.set_min_objective(f) + opt.set_lower_bounds(np.array([2.5, 7])) + """ + ).strip() + ) + + def test_assert(selenium): selenium.run_js( r"""