From 126ed962d5119ae9b103b0a8593ab063bce81f5a Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Mon, 15 May 2023 16:53:57 -0400 Subject: [PATCH] tests: run on pyodide Signed-off-by: Henry Schreiner --- .github/workflows/emscripten.yaml | 58 ++++++++++++++++++++++++++++ include/pybind11/pybind11.h | 2 +- tests/CMakeLists.txt | 10 ++++- tests/pyproject.toml | 11 ++++++ tests/test_async.py | 5 +++ tests/test_callbacks.py | 3 ++ tests/test_embed/test_interpreter.py | 7 ++++ tests/test_embed/test_trampoline.py | 9 +++++ tests/test_exceptions.py | 2 +- tests/test_gil_scoped.py | 13 +++++-- tests/test_iostream.py | 4 ++ tests/test_thread.py | 5 +++ tests/test_virtual_functions.py | 3 ++ 13 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/emscripten.yaml create mode 100644 tests/pyproject.toml diff --git a/.github/workflows/emscripten.yaml b/.github/workflows/emscripten.yaml new file mode 100644 index 00000000000..9a84d3e12ae --- /dev/null +++ b/.github/workflows/emscripten.yaml @@ -0,0 +1,58 @@ +name: WASM + +on: + workflow_dispatch: + pull_request: + branches: + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-wasm-emscripten: + name: Pyodide wheel + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + with: + submodules: true + fetch-depth: 0 + + - uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Install pyodide-build + run: pip install pyodide-build==0.23.4 + + - name: Compute emsdk version + id: compute-emsdk-version + run: | + # Prepare xbuild environment (side-effect) + pyodide config list + # Save EMSDK version + EMSCRIPTEN_VERSION=$(pyodide config get emscripten_version) + echo "emsdk-version=$EMSCRIPTEN_VERSION" >> $GITHUB_OUTPUT + + - uses: mymindstorm/setup-emsdk@v12 + with: + version: ${{ steps.compute-emsdk-version.outputs.emsdk-version }} + actions-cache-folder: emsdk-cache + + - name: Build + run: PYODIDE_BUILD_EXPORTS=whole_archive CFLAGS=-fexceptions LDFLAGS=-fexceptions pyodide build + working-directory: tests + + - uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Set up Pyodide virtual environment + run: | + pyodide venv .venv-pyodide + .venv-pyodide/bin/pip install $(echo -n tests/dist/*.whl) + + - name: Test + run: .venv-pyodide/bin/pytest -o timeout=0 tests/test_*.py diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index b87fe66b27e..13ab1dbf2ab 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -265,7 +265,7 @@ class cpp_function : public function { /* Dispatch code which converts function arguments and performs the actual function call */ rec->impl = [](function_call &call) -> handle { - cast_in args_converter; + cast_in args_converter{}; /* Try to cast the function arguments into the C++ domain */ if (!args_converter.load_args(call)) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d68067df6d7..120e70bae82 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -88,7 +88,12 @@ set(PYBIND11_TEST_FILTER if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) # We're being loaded directly, i.e. not via add_subdirectory, so make this # work as its own project and load the pybind11Config to get the tools we need - find_package(pybind11 REQUIRED CONFIG) + + if(SKBUILD) + add_subdirectory(.. pybind11_src) + else() + find_package(pybind11 REQUIRED CONFIG) + endif() endif() if(NOT CMAKE_BUILD_TYPE AND NOT DEFINED CMAKE_CONFIGURATION_TYPES) @@ -489,6 +494,9 @@ foreach(target ${test_targets}) endforeach() endif() endif() + if(SKBUILD) + install(TARGETS ${target} LIBRARY DESTINATION .) + endif() endforeach() # Provide nice organisation in IDEs diff --git a/tests/pyproject.toml b/tests/pyproject.toml new file mode 100644 index 00000000000..97478b0b826 --- /dev/null +++ b/tests/pyproject.toml @@ -0,0 +1,11 @@ +# Warning: this is currently used for pyodide, and is not a general out-of-tree +# builder for the tests (yet). Specifically, wheels can't be built from SDists. + +[build-system] +requires = ["scikit-build-core[pyproject]"] +build-backend = "scikit_build_core.build" + +[project] +name = "pybind11_tests" +version = "0.0.1" +dependencies = ["pytest", "pytest-timeout", "numpy", "scipy"] diff --git a/tests/test_async.py b/tests/test_async.py index 83a4c5036ad..c23fb92a5b0 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -1,8 +1,13 @@ +import sys + import pytest asyncio = pytest.importorskip("asyncio") m = pytest.importorskip("pybind11_tests.async_module") +if sys.platform.startswith("emscripten"): + pytest.skip("Can't run a new event_loop in pyodide", allow_module_level=True) + @pytest.fixture() def event_loop(): diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index 86c76745556..5e74fd3d04f 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -1,3 +1,4 @@ +import sys import time from threading import Thread @@ -151,6 +152,7 @@ def test_python_builtins(): assert m.test_sum_builtin(sum, []) == 0 +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_async_callbacks(): # serves as state for async callback class Item: @@ -174,6 +176,7 @@ def gen_f(): assert sum(res) == sum(x + 3 for x in work) +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_async_async_callbacks(): t = Thread(target=test_async_callbacks) t.start() diff --git a/tests/test_embed/test_interpreter.py b/tests/test_embed/test_interpreter.py index f279449722e..ebe91335ced 100644 --- a/tests/test_embed/test_interpreter.py +++ b/tests/test_embed/test_interpreter.py @@ -1,5 +1,12 @@ import sys +import pytest + +if sys.platform.startswith("emscripten"): + pytest.skip( + "Test not implemented from single wheel on Pyodide", allow_module_level=True + ) + from widget_module import Widget diff --git a/tests/test_embed/test_trampoline.py b/tests/test_embed/test_trampoline.py index 8e14e8ef0b2..1ffdbb57a9b 100644 --- a/tests/test_embed/test_trampoline.py +++ b/tests/test_embed/test_trampoline.py @@ -1,3 +1,12 @@ +import sys + +import pytest + +if sys.platform.startswith("emscripten"): + pytest.skip( + "Test not implemented from single wheel on Pyodide", allow_module_level=True + ) + import trampoline_module diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 5d8e6292aaf..81e5b68d890 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -73,7 +73,7 @@ def test_cross_module_exceptions(msg): # TODO: FIXME @pytest.mark.xfail( - "env.MACOS and (env.PYPY or pybind11_tests.compiler_info.startswith('Homebrew Clang'))", + "env.MACOS and (env.PYPY or pybind11_tests.compiler_info.startswith('Homebrew Clang')) or sys.platform.startswith('emscripten')", raises=RuntimeError, reason="See Issue #2847, PR #2999, PR #4324", ) diff --git a/tests/test_gil_scoped.py b/tests/test_gil_scoped.py index fc8af9b77c4..d7ff74b0566 100644 --- a/tests/test_gil_scoped.py +++ b/tests/test_gil_scoped.py @@ -69,24 +69,28 @@ def test_cross_module_gil_inner_pybind11_acquired(): m.test_cross_module_gil_inner_pybind11_acquired() +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_cross_module_gil_nested_custom_released(): """Makes sure that the GIL can be nested acquired/released by another module from a GIL-released state using custom locking logic.""" m.test_cross_module_gil_nested_custom_released() +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_cross_module_gil_nested_custom_acquired(): """Makes sure that the GIL can be nested acquired/acquired by another module from a GIL-acquired state using custom locking logic.""" m.test_cross_module_gil_nested_custom_acquired() +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_cross_module_gil_nested_pybind11_released(): """Makes sure that the GIL can be nested acquired/released by another module from a GIL-released state using pybind11 locking logic.""" m.test_cross_module_gil_nested_pybind11_released() +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_cross_module_gil_nested_pybind11_acquired(): """Makes sure that the GIL can be nested acquired/acquired by another module from a GIL-acquired state using pybind11 locking logic.""" @@ -101,6 +105,7 @@ def test_nested_acquire(): assert m.test_nested_acquire(0xAB) == "171" +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_multi_acquire_release_cross_module(): for bits in range(16 * 8): internals_ids = m.test_multi_acquire_release_cross_module(bits) @@ -202,7 +207,7 @@ def _run_in_threads(test_fn, num_threads, parallel): thread.join() -# TODO: FIXME, sometimes returns -11 (segfault) instead of 0 on macOS Python 3.9 +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) def test_run_in_process_one_thread(test_fn): """Makes sure there is no GIL deadlock when running in a thread. @@ -212,7 +217,7 @@ def test_run_in_process_one_thread(test_fn): assert _run_in_process(_run_in_threads, test_fn, num_threads=1, parallel=False) == 0 -# TODO: FIXME on macOS Python 3.9 +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) def test_run_in_process_multiple_threads_parallel(test_fn): """Makes sure there is no GIL deadlock when running in a thread multiple times in parallel. @@ -222,7 +227,7 @@ def test_run_in_process_multiple_threads_parallel(test_fn): assert _run_in_process(_run_in_threads, test_fn, num_threads=8, parallel=True) == 0 -# TODO: FIXME on macOS Python 3.9 +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) def test_run_in_process_multiple_threads_sequential(test_fn): """Makes sure there is no GIL deadlock when running in a thread multiple times sequentially. @@ -232,7 +237,7 @@ def test_run_in_process_multiple_threads_sequential(test_fn): assert _run_in_process(_run_in_threads, test_fn, num_threads=8, parallel=False) == 0 -# TODO: FIXME on macOS Python 3.9 +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) def test_run_in_process_direct(test_fn): """Makes sure there is no GIL deadlock when using processes. diff --git a/tests/test_iostream.py b/tests/test_iostream.py index d283eb1520f..28e6725f68f 100644 --- a/tests/test_iostream.py +++ b/tests/test_iostream.py @@ -1,6 +1,9 @@ +import sys from contextlib import redirect_stderr, redirect_stdout from io import StringIO +import pytest + from pybind11_tests import iostream as m @@ -268,6 +271,7 @@ def test_redirect_both(capfd): assert stream2.getvalue() == msg2 +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_threading(): with m.ostream_redirect(stdout=True, stderr=False): # start some threads diff --git a/tests/test_thread.py b/tests/test_thread.py index e89991f9d62..7733aa4de56 100644 --- a/tests/test_thread.py +++ b/tests/test_thread.py @@ -1,5 +1,8 @@ +import sys import threading +import pytest + from pybind11_tests import thread as m @@ -22,6 +25,7 @@ def join(self): raise self.e +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_implicit_conversion(): a = Thread(m.test) b = Thread(m.test) @@ -32,6 +36,7 @@ def test_implicit_conversion(): x.join() +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_implicit_conversion_no_gil(): a = Thread(m.test_no_gil) b = Thread(m.test_no_gil) diff --git a/tests/test_virtual_functions.py b/tests/test_virtual_functions.py index c17af7df58b..3d503d9c638 100644 --- a/tests/test_virtual_functions.py +++ b/tests/test_virtual_functions.py @@ -1,3 +1,5 @@ +import sys + import pytest import env # noqa: F401 @@ -433,6 +435,7 @@ def lucky_number(self): assert obj.say_everything() == "BT -7" +@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") def test_issue_1454(): # Fix issue #1454 (crash when acquiring/releasing GIL on another thread in Python 2.7) m.test_gil()