Add support for GraalPy (#5380)

* Initial support for GraalPy

* Mark tests that currently fail on GraalPy with xfail

* Add graalpy to CI

* Limit test deps on graalpy to available binary wheels

* Skip cmake test installed_function on GraalPy

CMake won't find libpython on GraalPy, it either fails or silently picks
CPython's libpython.

* Factor out setting function docstrings into a macro

* Try to narrow down skipped tests
This commit is contained in:
Michael Šimáček 2024-10-07 23:12:04 +02:00 committed by GitHub
parent 7e418f4924
commit c4a05f9344
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 211 additions and 66 deletions

View File

@ -39,6 +39,7 @@ jobs:
- 'pypy-3.8' - 'pypy-3.8'
- 'pypy-3.9' - 'pypy-3.9'
- 'pypy-3.10' - 'pypy-3.10'
- 'graalpy-24.1'
# Items in here will either be added to the build matrix (if not # Items in here will either be added to the build matrix (if not
# present), or add new keys to an existing matrix element if all the # present), or add new keys to an existing matrix element if all the
@ -67,6 +68,10 @@ jobs:
# Extra ubuntu latest job # Extra ubuntu latest job
- runs-on: ubuntu-latest - runs-on: ubuntu-latest
python: '3.11' python: '3.11'
exclude:
# The setup-python action currently doesn't have graalpy for windows
- python: 'graalpy-24.1'
runs-on: 'windows-2022'
name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}" name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}"

View File

@ -343,7 +343,7 @@ public:
#else #else
// Alternate approach for CPython: this does the same as the above, but optimized // Alternate approach for CPython: this does the same as the above, but optimized
// using the CPython API so as to avoid an unneeded attribute lookup. // using the CPython API so as to avoid an unneeded attribute lookup.
else if (auto *tp_as_number = src.ptr()->ob_type->tp_as_number) { else if (auto *tp_as_number = Py_TYPE(src.ptr())->tp_as_number) {
if (PYBIND11_NB_BOOL(tp_as_number)) { if (PYBIND11_NB_BOOL(tp_as_number)) {
res = (*PYBIND11_NB_BOOL(tp_as_number))(src.ptr()); res = (*PYBIND11_NB_BOOL(tp_as_number))(src.ptr());
} }

View File

@ -299,7 +299,7 @@ PYBIND11_WARNING_DISABLE_MSVC(4505)
# define PYBIND11_INTERNAL_NUMPY_1_ONLY_DETECTED # define PYBIND11_INTERNAL_NUMPY_1_ONLY_DETECTED
#endif #endif
#if defined(PYPY_VERSION) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) #if (defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT)
# define PYBIND11_SIMPLE_GIL_MANAGEMENT # define PYBIND11_SIMPLE_GIL_MANAGEMENT
#endif #endif
@ -387,6 +387,20 @@ PYBIND11_WARNING_POP
#define PYBIND11_CONCAT(first, second) first##second #define PYBIND11_CONCAT(first, second) first##second
#define PYBIND11_ENSURE_INTERNALS_READY pybind11::detail::get_internals(); #define PYBIND11_ENSURE_INTERNALS_READY pybind11::detail::get_internals();
#if !defined(GRAALVM_PYTHON)
# define PYBIND11_PYCFUNCTION_GET_DOC(func) ((func)->m_ml->ml_doc)
# define PYBIND11_PYCFUNCTION_SET_DOC(func, doc) \
do { \
(func)->m_ml->ml_doc = (doc); \
} while (0)
#else
# define PYBIND11_PYCFUNCTION_GET_DOC(func) (GraalPyCFunction_GetDoc((PyObject *) (func)))
# define PYBIND11_PYCFUNCTION_SET_DOC(func, doc) \
do { \
GraalPyCFunction_SetDoc((PyObject *) (func), (doc)); \
} while (0)
#endif
#define PYBIND11_CHECK_PYTHON_VERSION \ #define PYBIND11_CHECK_PYTHON_VERSION \
{ \ { \
const char *compiled_ver \ const char *compiled_ver \

View File

@ -454,7 +454,7 @@ inline void translate_local_exception(std::exception_ptr p) {
inline object get_python_state_dict() { inline object get_python_state_dict() {
object state_dict; object state_dict;
#if PYBIND11_INTERNALS_VERSION <= 4 || defined(PYPY_VERSION) #if PYBIND11_INTERNALS_VERSION <= 4 || defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
state_dict = reinterpret_borrow<object>(PyEval_GetBuiltins()); state_dict = reinterpret_borrow<object>(PyEval_GetBuiltins());
#else #else
# if PY_VERSION_HEX < 0x03090000 # if PY_VERSION_HEX < 0x03090000
@ -727,7 +727,8 @@ const char *c_str(Args &&...args) {
} }
inline const char *get_function_record_capsule_name() { inline const char *get_function_record_capsule_name() {
#if PYBIND11_INTERNALS_VERSION > 4 // On GraalPy, pointer equality of the names is currently not guaranteed
#if PYBIND11_INTERNALS_VERSION > 4 && !defined(GRAALVM_PYTHON)
return get_internals().function_record_capsule_name.c_str(); return get_internals().function_record_capsule_name.c_str();
#else #else
return nullptr; return nullptr;

View File

@ -459,7 +459,7 @@ PYBIND11_NOINLINE handle get_object_handle(const void *ptr, const detail::type_i
} }
inline PyThreadState *get_thread_state_unchecked() { inline PyThreadState *get_thread_state_unchecked() {
#if defined(PYPY_VERSION) #if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
return PyThreadState_GET(); return PyThreadState_GET();
#elif PY_VERSION_HEX < 0x030D0000 #elif PY_VERSION_HEX < 0x030D0000
return _PyThreadState_UncheckedGet(); return _PyThreadState_UncheckedGet();

View File

@ -94,18 +94,18 @@ void exec(const char (&s)[N], object global = globals(), object local = object()
eval<eval_statements>(s, std::move(global), std::move(local)); eval<eval_statements>(s, std::move(global), std::move(local));
} }
#if defined(PYPY_VERSION) #if defined(PYPY_VERSION) || defined(GRAALVM_PYTHON)
template <eval_mode mode = eval_statements> template <eval_mode mode = eval_statements>
object eval_file(str, object, object) { object eval_file(str, object, object) {
pybind11_fail("eval_file not supported in PyPy3. Use eval"); pybind11_fail("eval_file not supported in this interpreter. Use eval");
} }
template <eval_mode mode = eval_statements> template <eval_mode mode = eval_statements>
object eval_file(str, object) { object eval_file(str, object) {
pybind11_fail("eval_file not supported in PyPy3. Use eval"); pybind11_fail("eval_file not supported in this interpreter. Use eval");
} }
template <eval_mode mode = eval_statements> template <eval_mode mode = eval_statements>
object eval_file(str) { object eval_file(str) {
pybind11_fail("eval_file not supported in PyPy3. Use eval"); pybind11_fail("eval_file not supported in this interpreter. Use eval");
} }
#else #else
template <eval_mode mode = eval_statements> template <eval_mode mode = eval_statements>

View File

@ -573,8 +573,7 @@ protected:
// chain. // chain.
chain_start = rec; chain_start = rec;
rec->next = chain; rec->next = chain;
auto rec_capsule auto rec_capsule = reinterpret_borrow<capsule>(PyCFunction_GET_SELF(m_ptr));
= reinterpret_borrow<capsule>(((PyCFunctionObject *) m_ptr)->m_self);
rec_capsule.set_pointer(unique_rec.release()); rec_capsule.set_pointer(unique_rec.release());
guarded_strdup.release(); guarded_strdup.release();
} else { } else {
@ -634,12 +633,11 @@ protected:
} }
} }
/* Install docstring */
auto *func = (PyCFunctionObject *) m_ptr; auto *func = (PyCFunctionObject *) m_ptr;
std::free(const_cast<char *>(func->m_ml->ml_doc));
// Install docstring if it's non-empty (when at least one option is enabled) // Install docstring if it's non-empty (when at least one option is enabled)
func->m_ml->ml_doc auto *doc = signatures.empty() ? nullptr : PYBIND11_COMPAT_STRDUP(signatures.c_str());
= signatures.empty() ? nullptr : PYBIND11_COMPAT_STRDUP(signatures.c_str()); std::free(const_cast<char *>(PYBIND11_PYCFUNCTION_GET_DOC(func)));
PYBIND11_PYCFUNCTION_SET_DOC(func, doc);
if (rec->is_method) { if (rec->is_method) {
m_ptr = PYBIND11_INSTANCE_METHOD_NEW(m_ptr, rec->scope.ptr()); m_ptr = PYBIND11_INSTANCE_METHOD_NEW(m_ptr, rec->scope.ptr());
@ -2780,8 +2778,8 @@ get_type_override(const void *this_ptr, const type_info *this_type, const char *
} }
/* Don't call dispatch code if invoked from overridden function. /* Don't call dispatch code if invoked from overridden function.
Unfortunately this doesn't work on PyPy. */ Unfortunately this doesn't work on PyPy and GraalPy. */
#if !defined(PYPY_VERSION) #if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON)
# if PY_VERSION_HEX >= 0x03090000 # if PY_VERSION_HEX >= 0x03090000
PyFrameObject *frame = PyThreadState_GetFrame(PyThreadState_Get()); PyFrameObject *frame = PyThreadState_GetFrame(PyThreadState_Get());
if (frame != nullptr) { if (frame != nullptr) {

View File

@ -643,7 +643,7 @@ struct error_fetch_and_normalize {
bool have_trace = false; bool have_trace = false;
if (m_trace) { if (m_trace) {
#if !defined(PYPY_VERSION) #if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON)
auto *tb = reinterpret_cast<PyTracebackObject *>(m_trace.ptr()); auto *tb = reinterpret_cast<PyTracebackObject *>(m_trace.ptr());
// Get the deepest trace possible. // Get the deepest trace possible.
@ -1356,7 +1356,7 @@ inline bool PyUnicode_Check_Permissive(PyObject *o) {
# define PYBIND11_STR_CHECK_FUN PyUnicode_Check # define PYBIND11_STR_CHECK_FUN PyUnicode_Check
#endif #endif
inline bool PyStaticMethod_Check(PyObject *o) { return o->ob_type == &PyStaticMethod_Type; } inline bool PyStaticMethod_Check(PyObject *o) { return Py_TYPE(o) == &PyStaticMethod_Type; }
class kwargs_proxy : public handle { class kwargs_proxy : public handle {
public: public:

View File

@ -28,8 +28,8 @@ except Exception:
@pytest.fixture(scope="session", autouse=True) @pytest.fixture(scope="session", autouse=True)
def use_multiprocessing_forkserver_on_linux(): def use_multiprocessing_forkserver_on_linux():
if sys.platform != "linux": if sys.platform != "linux" or sys.implementation.name == "graalpy":
# The default on Windows and macOS is "spawn": If it's not broken, don't fix it. # The default on Windows, macOS and GraalPy is "spawn": If it's not broken, don't fix it.
return return
# Full background: https://github.com/pybind/pybind11/issues/4105#issuecomment-1301004592 # Full background: https://github.com/pybind/pybind11/issues/4105#issuecomment-1301004592

View File

@ -12,6 +12,7 @@ WIN = sys.platform.startswith("win32") or sys.platform.startswith("cygwin")
CPYTHON = platform.python_implementation() == "CPython" CPYTHON = platform.python_implementation() == "CPython"
PYPY = platform.python_implementation() == "PyPy" PYPY = platform.python_implementation() == "PyPy"
GRAALPY = sys.implementation.name == "graalpy"
PY_GIL_DISABLED = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) PY_GIL_DISABLED = bool(sysconfig.get_config_var("Py_GIL_DISABLED"))

View File

@ -2,11 +2,12 @@
build~=1.0; python_version>="3.8" build~=1.0; python_version>="3.8"
numpy~=1.23.0; python_version=="3.8" and platform_python_implementation=="PyPy" numpy~=1.23.0; python_version=="3.8" and platform_python_implementation=="PyPy"
numpy~=1.25.0; python_version=="3.9" and platform_python_implementation=='PyPy' numpy~=1.25.0; python_version=="3.9" and platform_python_implementation=='PyPy'
numpy~=1.21.5; platform_python_implementation!="PyPy" and python_version>="3.8" and python_version<"3.10" numpy~=1.26.0; platform_python_implementation=="GraalVM" and sys_platform=="linux"
numpy~=1.22.2; platform_python_implementation!="PyPy" and python_version=="3.10" numpy~=1.21.5; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version>="3.8" and python_version<"3.10"
numpy~=1.26.0; platform_python_implementation!="PyPy" and python_version>="3.11" and python_version<"3.13" numpy~=1.22.2; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version=="3.10"
numpy~=1.26.0; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version>="3.11" and python_version<"3.13"
pytest~=7.0 pytest~=7.0
pytest-timeout pytest-timeout
scipy~=1.5.4; platform_python_implementation!="PyPy" and python_version<"3.10" scipy~=1.5.4; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version<"3.10"
scipy~=1.8.0; platform_python_implementation!="PyPy" and python_version=="3.10" and sys_platform!='win32' scipy~=1.8.0; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version=="3.10" and sys_platform!='win32'
scipy~=1.11.1; platform_python_implementation!="PyPy" and python_version>="3.11" and python_version<"3.13" and sys_platform!='win32' scipy~=1.11.1; platform_python_implementation!="PyPy" and platform_python_implementation!="GraalVM" and python_version>="3.11" and python_version<"3.13" and sys_platform!='win32'

View File

@ -82,6 +82,8 @@ def test_from_python():
for j in range(m4.cols()): for j in range(m4.cols()):
assert m3[i, j] == m4[i, j] assert m3[i, j] == m4[i, j]
if env.GRAALPY:
pytest.skip("ConstructorStats is incompatible with GraalPy.")
cstats = ConstructorStats.get(m.Matrix) cstats = ConstructorStats.get(m.Matrix)
assert cstats.alive() == 1 assert cstats.alive() == 1
del m3, m4 del m3, m4
@ -118,6 +120,8 @@ def test_to_python():
mat2[2, 3] = 5 mat2[2, 3] = 5
assert mat2[2, 3] == 5 assert mat2[2, 3] == 5
if env.GRAALPY:
pytest.skip("ConstructorStats is incompatible with GraalPy.")
cstats = ConstructorStats.get(m.Matrix) cstats = ConstructorStats.get(m.Matrix)
assert cstats.alive() == 1 assert cstats.alive() == 1
del mat del mat

View File

@ -95,8 +95,8 @@ TEST_SUBMODULE(call_policies, m) {
}, },
py::call_guard<DependentGuard, CustomGuard>()); py::call_guard<DependentGuard, CustomGuard>());
#if !defined(PYPY_VERSION) #if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON)
// `py::call_guard<py::gil_scoped_release>()` should work in PyPy as well, // `py::call_guard<py::gil_scoped_release>()` should work in PyPy/GraalPy as well,
// but it's unclear how to test it without `PyGILState_GetThisThreadState`. // but it's unclear how to test it without `PyGILState_GetThisThreadState`.
auto report_gil_status = []() { auto report_gil_status = []() {
auto is_gil_held = false; auto is_gil_held = false;

View File

@ -8,6 +8,7 @@ from pybind11_tests import call_policies as m
@pytest.mark.xfail("env.PYPY", reason="sometimes comes out 1 off on PyPy", strict=False) @pytest.mark.xfail("env.PYPY", reason="sometimes comes out 1 off on PyPy", strict=False)
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_keep_alive_argument(capture): def test_keep_alive_argument(capture):
n_inst = ConstructorStats.detail_reg_inst() n_inst = ConstructorStats.detail_reg_inst()
with capture: with capture:
@ -60,6 +61,7 @@ def test_keep_alive_argument(capture):
assert str(excinfo.value) == "Could not activate keep_alive!" assert str(excinfo.value) == "Could not activate keep_alive!"
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_keep_alive_return_value(capture): def test_keep_alive_return_value(capture):
n_inst = ConstructorStats.detail_reg_inst() n_inst = ConstructorStats.detail_reg_inst()
with capture: with capture:
@ -118,6 +120,7 @@ def test_keep_alive_return_value(capture):
# https://foss.heptapod.net/pypy/pypy/-/issues/2447 # https://foss.heptapod.net/pypy/pypy/-/issues/2447
@pytest.mark.xfail("env.PYPY", reason="_PyObject_GetDictPtr is unimplemented") @pytest.mark.xfail("env.PYPY", reason="_PyObject_GetDictPtr is unimplemented")
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_alive_gc(capture): def test_alive_gc(capture):
n_inst = ConstructorStats.detail_reg_inst() n_inst = ConstructorStats.detail_reg_inst()
p = m.ParentGC() p = m.ParentGC()
@ -137,6 +140,7 @@ def test_alive_gc(capture):
) )
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_alive_gc_derived(capture): def test_alive_gc_derived(capture):
class Derived(m.Parent): class Derived(m.Parent):
pass pass
@ -159,6 +163,7 @@ def test_alive_gc_derived(capture):
) )
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_alive_gc_multi_derived(capture): def test_alive_gc_multi_derived(capture):
class Derived(m.Parent, m.Child): class Derived(m.Parent, m.Child):
def __init__(self): def __init__(self):

View File

@ -270,7 +270,7 @@ TEST_SUBMODULE(callbacks, m) {
m.add_object("custom_function", PyCFunction_New(custom_def, rec_capsule.ptr())); m.add_object("custom_function", PyCFunction_New(custom_def, rec_capsule.ptr()));
// This test requires a new ABI version to pass // This test requires a new ABI version to pass
#if PYBIND11_INTERNALS_VERSION > 4 #if PYBIND11_INTERNALS_VERSION > 4 && !defined(GRAALVM_PYTHON)
// rec_capsule with nullptr name // rec_capsule with nullptr name
py::capsule rec_capsule2(std::malloc(1), [](void *data) { std::free(data); }); py::capsule rec_capsule2(std::malloc(1), [](void *data) { std::free(data); });
m.add_object("custom_function2", PyCFunction_New(custom_def, rec_capsule2.ptr())); m.add_object("custom_function2", PyCFunction_New(custom_def, rec_capsule2.ptr()));

View File

@ -90,6 +90,7 @@ def test_keyword_args_and_generalized_unpacking():
) )
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_lambda_closure_cleanup(): def test_lambda_closure_cleanup():
m.test_lambda_closure_cleanup() m.test_lambda_closure_cleanup()
cstats = m.payload_cstats() cstats = m.payload_cstats()
@ -98,6 +99,7 @@ def test_lambda_closure_cleanup():
assert cstats.move_constructions >= 1 assert cstats.move_constructions >= 1
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_cpp_callable_cleanup(): def test_cpp_callable_cleanup():
alive_counts = m.test_cpp_callable_cleanup() alive_counts = m.test_cpp_callable_cleanup()
assert alive_counts == [0, 1, 2, 1, 2, 1, 0] assert alive_counts == [0, 1, 2, 1, 2, 1, 0]

View File

@ -361,7 +361,7 @@ def test_brace_initialization():
assert b.vec == [123, 456] assert b.vec == [123, 456]
@pytest.mark.xfail("env.PYPY") @pytest.mark.xfail("env.PYPY or env.GRAALPY")
def test_class_refcount(): def test_class_refcount():
"""Instances must correctly increase/decrease the reference count of their types (#1029)""" """Instances must correctly increase/decrease the reference count of their types (#1029)"""
from sys import getrefcount from sys import getrefcount

View File

@ -55,8 +55,10 @@ possibly_uninitialized(PYTHON_MODULE_EXTENSION Python_INTERPRETER_ID)
pybind11_add_build_test(subdirectory_function) pybind11_add_build_test(subdirectory_function)
pybind11_add_build_test(subdirectory_target) pybind11_add_build_test(subdirectory_target)
if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy") if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy"
message(STATUS "Skipping embed test on PyPy") OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy"
OR "${PYTHON_MODULE_EXTENSION}" MATCHES "graalpy")
message(STATUS "Skipping embed test on PyPy or GraalPy")
else() else()
pybind11_add_build_test(subdirectory_embed) pybind11_add_build_test(subdirectory_embed)
endif() endif()
@ -66,10 +68,14 @@ if(PYBIND11_INSTALL)
mock_install ${CMAKE_COMMAND} "-DCMAKE_INSTALL_PREFIX=${pybind11_BINARY_DIR}/mock_install" -P mock_install ${CMAKE_COMMAND} "-DCMAKE_INSTALL_PREFIX=${pybind11_BINARY_DIR}/mock_install" -P
"${pybind11_BINARY_DIR}/cmake_install.cmake") "${pybind11_BINARY_DIR}/cmake_install.cmake")
if(NOT "${PYTHON_MODULE_EXTENSION}" MATCHES "graalpy")
pybind11_add_build_test(installed_function INSTALL) pybind11_add_build_test(installed_function INSTALL)
endif()
pybind11_add_build_test(installed_target INSTALL) pybind11_add_build_test(installed_target INSTALL)
if(NOT ("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy" if(NOT
)) ("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy"
OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy"
OR "${PYTHON_MODULE_EXTENSION}" MATCHES "graalpy"))
pybind11_add_build_test(installed_embed INSTALL) pybind11_add_build_test(installed_embed INSTALL)
endif() endif()
endif() endif()

View File

@ -7,6 +7,7 @@ import exo_planet_pybind11
import home_planet_very_lonely_traveler import home_planet_very_lonely_traveler
import pytest import pytest
import env
from pybind11_tests import cpp_conduit as home_planet from pybind11_tests import cpp_conduit as home_planet
@ -27,7 +28,10 @@ def test_call_cpp_conduit_success():
home_planet.cpp_type_info_capsule_Traveler, home_planet.cpp_type_info_capsule_Traveler,
b"raw_pointer_ephemeral", b"raw_pointer_ephemeral",
) )
assert cap.__class__.__name__ == "PyCapsule" assert cap.__class__.__name__ == "PyCapsule" or (
# Note: this will become unnecessary in the next GraalPy release
env.GRAALPY and cap.__class__.__name__ == "capsule"
)
def test_call_cpp_conduit_platform_abi_id_mismatch(): def test_call_cpp_conduit_platform_abi_id_mismatch():

View File

@ -34,7 +34,7 @@ def gc_tester():
# PyPy does not seem to reliably garbage collect. # PyPy does not seem to reliably garbage collect.
@pytest.mark.skipif("env.PYPY") @pytest.mark.skipif("env.PYPY or env.GRAALPY")
def test_self_cycle(gc_tester): def test_self_cycle(gc_tester):
obj = m.OwnsPythonObjects() obj = m.OwnsPythonObjects()
obj.value = obj obj.value = obj
@ -42,7 +42,7 @@ def test_self_cycle(gc_tester):
# PyPy does not seem to reliably garbage collect. # PyPy does not seem to reliably garbage collect.
@pytest.mark.skipif("env.PYPY") @pytest.mark.skipif("env.PYPY or env.GRAALPY")
def test_indirect_cycle(gc_tester): def test_indirect_cycle(gc_tester):
obj = m.OwnsPythonObjects() obj = m.OwnsPythonObjects()
obj_list = [obj] obj_list = [obj]

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import pytest import pytest
import env # noqa: F401
from pybind11_tests import ConstructorStats from pybind11_tests import ConstructorStats
np = pytest.importorskip("numpy") np = pytest.importorskip("numpy")
@ -409,6 +410,7 @@ def assert_keeps_alive(cl, method, *args):
assert cstats.alive() == start_with assert cstats.alive() == start_with
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_eigen_keepalive(): def test_eigen_keepalive():
a = m.ReturnTester() a = m.ReturnTester()
cstats = ConstructorStats.get(m.ReturnTester) cstats = ConstructorStats.get(m.ReturnTester)

View File

@ -4,6 +4,8 @@ import sys
import pytest import pytest
import env # noqa: F401
np = pytest.importorskip("numpy") np = pytest.importorskip("numpy")
eigen_tensor = pytest.importorskip("pybind11_tests.eigen_tensor") eigen_tensor = pytest.importorskip("pybind11_tests.eigen_tensor")
submodules = [eigen_tensor.c_style, eigen_tensor.f_style] submodules = [eigen_tensor.c_style, eigen_tensor.f_style]
@ -61,6 +63,7 @@ def assert_equal_tensor_ref(mat, writeable=True, modified=None):
@pytest.mark.parametrize("m", submodules) @pytest.mark.parametrize("m", submodules)
@pytest.mark.parametrize("member_name", ["member", "member_view"]) @pytest.mark.parametrize("member_name", ["member", "member_view"])
@pytest.mark.skipif("env.GRAALPY", reason="Different refcounting mechanism")
def test_reference_internal(m, member_name): def test_reference_internal(m, member_name):
if not hasattr(sys, "getrefcount"): if not hasattr(sys, "getrefcount"):
pytest.skip("No reference counting") pytest.skip("No reference counting")

View File

@ -1,8 +1,10 @@
possibly_uninitialized(PYTHON_MODULE_EXTENSION Python_INTERPRETER_ID) possibly_uninitialized(PYTHON_MODULE_EXTENSION Python_INTERPRETER_ID)
if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy") if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy"
message(STATUS "Skipping embed test on PyPy") OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy"
add_custom_target(cpptest) # Dummy target on PyPy. Embedding is not supported. OR "${PYTHON_MODULE_EXTENSION}" MATCHES "graalpy")
message(STATUS "Skipping embed test on PyPy or GraalPy")
add_custom_target(cpptest) # Dummy target on PyPy or GraalPy. Embedding is not supported.
set(_suppress_unused_variable_warning "${DOWNLOAD_CATCH}") set(_suppress_unused_variable_warning "${DOWNLOAD_CATCH}")
return() return()
endif() endif()

View File

@ -3,9 +3,11 @@ from __future__ import annotations
import pytest import pytest
import env # noqa: F401
from pybind11_tests import enums as m from pybind11_tests import enums as m
@pytest.mark.xfail("env.GRAALPY", reason="TODO should get fixed on GraalPy side")
def test_unscoped_enum(): def test_unscoped_enum():
assert str(m.UnscopedEnum.EOne) == "UnscopedEnum.EOne" assert str(m.UnscopedEnum.EOne) == "UnscopedEnum.EOne"
assert str(m.UnscopedEnum.ETwo) == "UnscopedEnum.ETwo" assert str(m.UnscopedEnum.ETwo) == "UnscopedEnum.ETwo"
@ -193,6 +195,7 @@ def test_implicit_conversion():
assert repr(x) == "{<EMode.EFirstMode: 1>: 3, <EMode.ESecondMode: 2>: 4}" assert repr(x) == "{<EMode.EFirstMode: 1>: 3, <EMode.ESecondMode: 2>: 4}"
@pytest.mark.xfail("env.GRAALPY", reason="TODO should get fixed on GraalPy side")
def test_binary_operators(): def test_binary_operators():
assert int(m.Flags.Read) == 4 assert int(m.Flags.Read) == 4
assert int(m.Flags.Write) == 2 assert int(m.Flags.Write) == 2

View File

@ -19,7 +19,7 @@ def test_evals(capture):
assert m.test_eval_failure() assert m.test_eval_failure()
@pytest.mark.xfail("env.PYPY", raises=RuntimeError) @pytest.mark.xfail("env.PYPY or env.GRAALPY", raises=RuntimeError)
def test_eval_file(): def test_eval_file():
filename = os.path.join(os.path.dirname(__file__), "test_eval_call.py") filename = os.path.join(os.path.dirname(__file__), "test_eval_call.py")
assert m.test_eval_file(filename) assert m.test_eval_file(filename)

View File

@ -201,6 +201,7 @@ def test_custom(msg):
assert msg(excinfo.value) == "[PythonMyException7]: abc" assert msg(excinfo.value) == "[PythonMyException7]: abc"
@pytest.mark.xfail("env.GRAALPY", reason="TODO should get fixed on GraalPy side")
def test_nested_throws(capture): def test_nested_throws(capture):
"""Tests nested (e.g. C++ -> Python -> C++) exception handling""" """Tests nested (e.g. C++ -> Python -> C++) exception handling"""
@ -369,6 +370,7 @@ def _test_flaky_exception_failure_point_init_py_3_12():
"env.PYPY and sys.version_info[:2] < (3, 12)", "env.PYPY and sys.version_info[:2] < (3, 12)",
reason="PyErr_NormalizeException Segmentation fault", reason="PyErr_NormalizeException Segmentation fault",
) )
@pytest.mark.xfail("env.GRAALPY", reason="TODO should be fixed on GraalPy side")
def test_flaky_exception_failure_point_init(): def test_flaky_exception_failure_point_init():
if sys.version_info[:2] < (3, 12): if sys.version_info[:2] < (3, 12):
_test_flaky_exception_failure_point_init_before_py_3_12() _test_flaky_exception_failure_point_init_before_py_3_12()
@ -376,6 +378,7 @@ def test_flaky_exception_failure_point_init():
_test_flaky_exception_failure_point_init_py_3_12() _test_flaky_exception_failure_point_init_py_3_12()
@pytest.mark.xfail("env.GRAALPY", reason="TODO should be fixed on GraalPy side")
def test_flaky_exception_failure_point_str(): def test_flaky_exception_failure_point_str():
what, py_err_set_after_what = m.error_already_set_what( what, py_err_set_after_what = m.error_already_set_what(
FlakyException, ("failure_point_str",) FlakyException, ("failure_point_str",)

View File

@ -4,11 +4,13 @@ import re
import pytest import pytest
import env # noqa: F401
from pybind11_tests import ConstructorStats from pybind11_tests import ConstructorStats
from pybind11_tests import factory_constructors as m from pybind11_tests import factory_constructors as m
from pybind11_tests.factory_constructors import tag from pybind11_tests.factory_constructors import tag
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_init_factory_basic(): def test_init_factory_basic():
"""Tests py::init_factory() wrapper around various ways of returning the object""" """Tests py::init_factory() wrapper around various ways of returning the object"""
@ -102,6 +104,7 @@ def test_init_factory_signature(msg):
) )
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_init_factory_casting(): def test_init_factory_casting():
"""Tests py::init_factory() wrapper with various upcasting and downcasting returns""" """Tests py::init_factory() wrapper with various upcasting and downcasting returns"""
@ -150,6 +153,7 @@ def test_init_factory_casting():
] ]
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_init_factory_alias(): def test_init_factory_alias():
"""Tests py::init_factory() wrapper with value conversions and alias types""" """Tests py::init_factory() wrapper with value conversions and alias types"""
@ -220,6 +224,7 @@ def test_init_factory_alias():
] ]
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_init_factory_dual(): def test_init_factory_dual():
"""Tests init factory functions with dual main/alias factory functions""" """Tests init factory functions with dual main/alias factory functions"""
from pybind11_tests.factory_constructors import TestFactory7 from pybind11_tests.factory_constructors import TestFactory7
@ -302,6 +307,7 @@ def test_init_factory_dual():
] ]
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_no_placement_new(capture): def test_no_placement_new(capture):
"""Prior to 2.2, `py::init<...>` relied on the type supporting placement """Prior to 2.2, `py::init<...>` relied on the type supporting placement
new; this tests a class without placement new support.""" new; this tests a class without placement new support."""
@ -350,6 +356,7 @@ def strip_comments(s):
return re.sub(r"\s+#.*", "", s) return re.sub(r"\s+#.*", "", s)
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_reallocation_a(capture, msg): def test_reallocation_a(capture, msg):
"""When the constructor is overloaded, previous overloads can require a preallocated value. """When the constructor is overloaded, previous overloads can require a preallocated value.
This test makes sure that such preallocated values only happen when they might be necessary, This test makes sure that such preallocated values only happen when they might be necessary,
@ -372,6 +379,7 @@ def test_reallocation_a(capture, msg):
) )
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_reallocation_b(capture, msg): def test_reallocation_b(capture, msg):
with capture: with capture:
create_and_destroy(1.5) create_and_destroy(1.5)
@ -388,6 +396,7 @@ def test_reallocation_b(capture, msg):
) )
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_reallocation_c(capture, msg): def test_reallocation_c(capture, msg):
with capture: with capture:
create_and_destroy(2, 3) create_and_destroy(2, 3)
@ -402,6 +411,7 @@ def test_reallocation_c(capture, msg):
) )
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_reallocation_d(capture, msg): def test_reallocation_d(capture, msg):
with capture: with capture:
create_and_destroy(2.5, 3) create_and_destroy(2.5, 3)
@ -417,6 +427,7 @@ def test_reallocation_d(capture, msg):
) )
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_reallocation_e(capture, msg): def test_reallocation_e(capture, msg):
with capture: with capture:
create_and_destroy(3.5, 4.5) create_and_destroy(3.5, 4.5)
@ -432,6 +443,7 @@ def test_reallocation_e(capture, msg):
) )
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_reallocation_f(capture, msg): def test_reallocation_f(capture, msg):
with capture: with capture:
create_and_destroy(4, 0.5) create_and_destroy(4, 0.5)
@ -448,6 +460,7 @@ def test_reallocation_f(capture, msg):
) )
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_reallocation_g(capture, msg): def test_reallocation_g(capture, msg):
with capture: with capture:
create_and_destroy(5, "hi") create_and_destroy(5, "hi")

View File

@ -211,6 +211,10 @@ def _run_in_threads(test_fn, num_threads, parallel):
@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads")
@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
@pytest.mark.skipif(
"env.GRAALPY",
reason="GraalPy transiently complains about unfinished threads at process exit",
)
def test_run_in_process_one_thread(test_fn): def test_run_in_process_one_thread(test_fn):
"""Makes sure there is no GIL deadlock when running in a thread. """Makes sure there is no GIL deadlock when running in a thread.
@ -221,6 +225,10 @@ def test_run_in_process_one_thread(test_fn):
@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads")
@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
@pytest.mark.skipif(
"env.GRAALPY",
reason="GraalPy transiently complains about unfinished threads at process exit",
)
def test_run_in_process_multiple_threads_parallel(test_fn): 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. """Makes sure there is no GIL deadlock when running in a thread multiple times in parallel.
@ -231,6 +239,10 @@ def test_run_in_process_multiple_threads_parallel(test_fn):
@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads")
@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
@pytest.mark.skipif(
"env.GRAALPY",
reason="GraalPy transiently complains about unfinished threads at process exit",
)
def test_run_in_process_multiple_threads_sequential(test_fn): 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. """Makes sure there is no GIL deadlock when running in a thread multiple times sequentially.
@ -241,6 +253,10 @@ def test_run_in_process_multiple_threads_sequential(test_fn):
@pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads") @pytest.mark.skipif(sys.platform.startswith("emscripten"), reason="Requires threads")
@pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK) @pytest.mark.parametrize("test_fn", ALL_BASIC_TESTS_PLUS_INTENTIONAL_DEADLOCK)
@pytest.mark.skipif(
"env.GRAALPY",
reason="GraalPy transiently complains about unfinished threads at process exit",
)
def test_run_in_process_direct(test_fn): def test_run_in_process_direct(test_fn):
"""Makes sure there is no GIL deadlock when using processes. """Makes sure there is no GIL deadlock when using processes.

View File

@ -6,8 +6,14 @@ from io import StringIO
import pytest import pytest
import env # noqa: F401
from pybind11_tests import iostream as m from pybind11_tests import iostream as m
pytestmark = pytest.mark.skipif(
"env.GRAALPY",
reason="Delayed prints from finalizers from other tests can end up in the output",
)
def test_captured(capsys): def test_captured(capsys):
msg = "I've been redirected to Python, I hope!" msg = "I've been redirected to Python, I hope!"

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import pytest import pytest
import env # noqa: F401
from pybind11_tests import kwargs_and_defaults as m from pybind11_tests import kwargs_and_defaults as m
@ -383,6 +384,7 @@ def test_signatures():
) )
@pytest.mark.skipif("env.GRAALPY", reason="Different refcounting mechanism")
def test_args_refcount(): def test_args_refcount():
"""Issue/PR #1216 - py::args elements get double-inc_ref()ed when combined with regular """Issue/PR #1216 - py::args elements get double-inc_ref()ed when combined with regular
arguments""" arguments"""

View File

@ -4,7 +4,7 @@ import sys
import pytest import pytest
import env # noqa: F401 import env
from pybind11_tests import ConstructorStats from pybind11_tests import ConstructorStats
from pybind11_tests import methods_and_attributes as m from pybind11_tests import methods_and_attributes as m
@ -68,6 +68,9 @@ def test_methods_and_attributes():
instance1.value = 100 instance1.value = 100
assert str(instance1) == "ExampleMandA[value=100]" assert str(instance1) == "ExampleMandA[value=100]"
if env.GRAALPY:
pytest.skip("ConstructorStats is incompatible with GraalPy.")
cstats = ConstructorStats.get(m.ExampleMandA) cstats = ConstructorStats.get(m.ExampleMandA)
assert cstats.alive() == 2 assert cstats.alive() == 2
del instance1, instance2 del instance1, instance2
@ -316,6 +319,8 @@ def test_dynamic_attributes():
instance.__dict__ = [] instance.__dict__ = []
assert str(excinfo.value) == "__dict__ must be set to a dictionary, not a 'list'" assert str(excinfo.value) == "__dict__ must be set to a dictionary, not a 'list'"
if env.GRAALPY:
pytest.skip("ConstructorStats is incompatible with GraalPy.")
cstats = ConstructorStats.get(m.DynamicClass) cstats = ConstructorStats.get(m.DynamicClass)
assert cstats.alive() == 1 assert cstats.alive() == 1
del instance del instance
@ -337,6 +342,7 @@ def test_dynamic_attributes():
# https://foss.heptapod.net/pypy/pypy/-/issues/2447 # https://foss.heptapod.net/pypy/pypy/-/issues/2447
@pytest.mark.xfail("env.PYPY") @pytest.mark.xfail("env.PYPY")
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_cyclic_gc(): def test_cyclic_gc():
# One object references itself # One object references itself
instance = m.DynamicClass() instance = m.DynamicClass()

View File

@ -39,6 +39,9 @@ def test_reference_internal():
assert str(b.get_a2()) == "A[43]" assert str(b.get_a2()) == "A[43]"
assert str(b.a2) == "A[43]" assert str(b.a2) == "A[43]"
if env.GRAALPY:
pytest.skip("ConstructorStats is incompatible with GraalPy.")
astats, bstats = ConstructorStats.get(ms.A), ConstructorStats.get(ms.B) astats, bstats = ConstructorStats.get(ms.A), ConstructorStats.get(ms.B)
assert astats.alive() == 2 assert astats.alive() == 2
assert bstats.alive() == 1 assert bstats.alive() == 1
@ -97,9 +100,9 @@ def test_def_submodule_failures():
sm = m.def_submodule(m, b"ScratchSubModuleName") # Using bytes to show it works. sm = m.def_submodule(m, b"ScratchSubModuleName") # Using bytes to show it works.
assert sm.__name__ == m.__name__ + "." + "ScratchSubModuleName" assert sm.__name__ == m.__name__ + "." + "ScratchSubModuleName"
malformed_utf8 = b"\x80" malformed_utf8 = b"\x80"
if env.PYPY: if env.PYPY or env.GRAALPY:
# It is not worth the effort finding a trigger for a failure when running with PyPy. # It is not worth the effort finding a trigger for a failure when running with PyPy.
pytest.skip("Sufficiently exercised on platforms other than PyPy.") pytest.skip("Sufficiently exercised on platforms other than PyPy/GraalPy.")
else: else:
# Meant to trigger PyModule_GetName() failure: # Meant to trigger PyModule_GetName() failure:
sm_name_orig = sm.__name__ sm_name_orig = sm.__name__

View File

@ -2,7 +2,7 @@ from __future__ import annotations
import pytest import pytest
import env # noqa: F401 import env
from pybind11_tests import ConstructorStats from pybind11_tests import ConstructorStats
from pybind11_tests import multiple_inheritance as m from pybind11_tests import multiple_inheritance as m
@ -279,6 +279,7 @@ def test_mi_unaligned_base():
c = m.I801C() c = m.I801C()
d = m.I801D() d = m.I801D()
if not env.GRAALPY:
# + 4 below because we have the two instances, and each instance has offset base I801B2 # + 4 below because we have the two instances, and each instance has offset base I801B2
assert ConstructorStats.detail_reg_inst() == n_inst + 4 assert ConstructorStats.detail_reg_inst() == n_inst + 4
b1c = m.i801b1_c(c) b1c = m.i801b1_c(c)
@ -290,6 +291,9 @@ def test_mi_unaligned_base():
b2d = m.i801b2_d(d) b2d = m.i801b2_d(d)
assert b2d is d assert b2d is d
if env.GRAALPY:
pytest.skip("ConstructorStats is incompatible with GraalPy.")
assert ConstructorStats.detail_reg_inst() == n_inst + 4 # no extra instances assert ConstructorStats.detail_reg_inst() == n_inst + 4 # no extra instances
del c, b1c, b2c del c, b1c, b2c
assert ConstructorStats.detail_reg_inst() == n_inst + 2 assert ConstructorStats.detail_reg_inst() == n_inst + 2
@ -312,6 +316,7 @@ def test_mi_base_return():
assert d1.a == 1 assert d1.a == 1
assert d1.b == 2 assert d1.b == 2
if not env.GRAALPY:
assert ConstructorStats.detail_reg_inst() == n_inst + 4 assert ConstructorStats.detail_reg_inst() == n_inst + 4
c2 = m.i801c_b2() c2 = m.i801c_b2()
@ -324,6 +329,7 @@ def test_mi_base_return():
assert d2.a == 1 assert d2.a == 1
assert d2.b == 2 assert d2.b == 2
if not env.GRAALPY:
assert ConstructorStats.detail_reg_inst() == n_inst + 8 assert ConstructorStats.detail_reg_inst() == n_inst + 8
del c2 del c2

View File

@ -242,6 +242,7 @@ def test_wrap():
assert_references(a1m, a2, a1) assert_references(a1m, a2, a1)
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_numpy_view(capture): def test_numpy_view(capture):
with capture: with capture:
ac = m.ArrayClass() ac = m.ArrayClass()
@ -465,7 +466,7 @@ def test_array_resize():
assert b.shape == (8, 8) assert b.shape == (8, 8)
@pytest.mark.xfail("env.PYPY") @pytest.mark.xfail("env.PYPY or env.GRAALPY")
def test_array_create_and_resize(): def test_array_create_and_resize():
a = m.create_and_resize(2) a = m.create_and_resize(2)
assert a.size == 4 assert a.size == 4

View File

@ -2,10 +2,12 @@ from __future__ import annotations
import pytest import pytest
import env # noqa: F401
from pybind11_tests import ConstructorStats from pybind11_tests import ConstructorStats
from pybind11_tests import operators as m from pybind11_tests import operators as m
@pytest.mark.xfail("env.GRAALPY", reason="TODO should get fixed on GraalPy side")
def test_operator_overloading(): def test_operator_overloading():
v1 = m.Vector2(1, 2) v1 = m.Vector2(1, 2)
v2 = m.Vector(3, -1) v2 = m.Vector(3, -1)
@ -83,6 +85,7 @@ def test_operator_overloading():
assert cstats.move_assignments == 0 assert cstats.move_assignments == 0
@pytest.mark.xfail("env.GRAALPY", reason="TODO should get fixed on GraalPy side")
def test_operators_notimplemented(): def test_operators_notimplemented():
"""#393: need to return NotSupported to ensure correct arithmetic operator behavior""" """#393: need to return NotSupported to ensure correct arithmetic operator behavior"""

View File

@ -20,7 +20,7 @@ def test_pickle_simple_callable():
# all C Python versions. # all C Python versions.
with pytest.raises(TypeError) as excinfo: with pytest.raises(TypeError) as excinfo:
pickle.dumps(m.simple_callable) pickle.dumps(m.simple_callable)
assert re.search("can.*t pickle .*PyCapsule.* object", str(excinfo.value)) assert re.search("can.*t pickle .*[Cc]apsule.* object", str(excinfo.value))
@pytest.mark.parametrize("cls_name", ["Pickleable", "PickleableNew"]) @pytest.mark.parametrize("cls_name", ["Pickleable", "PickleableNew"])

View File

@ -262,6 +262,7 @@ def test_str(doc):
m.str_from_std_string_input, m.str_from_std_string_input,
], ],
) )
@pytest.mark.xfail("env.GRAALPY", reason="TODO should be fixed on GraalPy side")
def test_surrogate_pairs_unicode_error(func): def test_surrogate_pairs_unicode_error(func):
input_str = "\ud83d\ude4f".encode("utf-8", "surrogatepass") input_str = "\ud83d\ude4f".encode("utf-8", "surrogatepass")
with pytest.raises(UnicodeDecodeError): with pytest.raises(UnicodeDecodeError):
@ -420,6 +421,7 @@ def test_accessor_moves():
pytest.skip("Not defined: PYBIND11_HANDLE_REF_DEBUG") pytest.skip("Not defined: PYBIND11_HANDLE_REF_DEBUG")
@pytest.mark.xfail("env.GRAALPY", reason="TODO should be fixed on GraalPy side")
def test_constructors(): def test_constructors():
"""C++ default and converting constructors are equivalent to type calls in Python""" """C++ default and converting constructors are equivalent to type calls in Python"""
types = [bytes, bytearray, str, bool, int, float, tuple, list, dict, set] types = [bytes, bytearray, str, bool, int, float, tuple, list, dict, set]
@ -712,6 +714,7 @@ def test_pass_bytes_or_unicode_to_string_types():
m.pass_to_pybind11_str(malformed_utf8) m.pass_to_pybind11_str(malformed_utf8)
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
@pytest.mark.parametrize( @pytest.mark.parametrize(
("create_weakref", "create_weakref_with_callback"), ("create_weakref", "create_weakref_with_callback"),
[ [
@ -765,7 +768,10 @@ def test_weakref_err(create_weakref, has_callback):
ob = C() ob = C()
# Should raise TypeError on CPython # Should raise TypeError on CPython
with pytest.raises(TypeError) if not env.PYPY else contextlib.nullcontext(): cm = pytest.raises(TypeError)
if env.PYPY or env.GRAALPY:
cm = contextlib.nullcontext()
with cm:
_ = create_weakref(ob, callback) if has_callback else create_weakref(ob) _ = create_weakref(ob, callback) if has_callback else create_weakref(ob)

View File

@ -3,6 +3,7 @@ from __future__ import annotations
import pytest import pytest
from pytest import approx # noqa: PT013 from pytest import approx # noqa: PT013
import env
from pybind11_tests import ConstructorStats from pybind11_tests import ConstructorStats
from pybind11_tests import sequences_and_iterators as m from pybind11_tests import sequences_and_iterators as m
@ -110,6 +111,7 @@ def test_sequence():
cstats = ConstructorStats.get(m.Sequence) cstats = ConstructorStats.get(m.Sequence)
s = m.Sequence(5) s = m.Sequence(5)
if not env.GRAALPY:
assert cstats.values() == ["of size", "5"] assert cstats.values() == ["of size", "5"]
assert "Sequence" in repr(s) assert "Sequence" in repr(s)
@ -123,15 +125,18 @@ def test_sequence():
assert s[3] == approx(56.78, rel=1e-05) assert s[3] == approx(56.78, rel=1e-05)
rev = reversed(s) rev = reversed(s)
if not env.GRAALPY:
assert cstats.values() == ["of size", "5"] assert cstats.values() == ["of size", "5"]
rev2 = s[::-1] rev2 = s[::-1]
if not env.GRAALPY:
assert cstats.values() == ["of size", "5"] assert cstats.values() == ["of size", "5"]
it = iter(m.Sequence(0)) it = iter(m.Sequence(0))
for _ in range(3): # __next__ must continue to raise StopIteration for _ in range(3): # __next__ must continue to raise StopIteration
with pytest.raises(StopIteration): with pytest.raises(StopIteration):
next(it) next(it)
if not env.GRAALPY:
assert cstats.values() == ["of size", "0"] assert cstats.values() == ["of size", "0"]
expected = [0, 56.78, 0, 0, 12.34] expected = [0, 56.78, 0, 0, 12.34]
@ -140,10 +145,14 @@ def test_sequence():
assert rev == rev2 assert rev == rev2
rev[0::2] = m.Sequence([2.0, 2.0, 2.0]) rev[0::2] = m.Sequence([2.0, 2.0, 2.0])
if not env.GRAALPY:
assert cstats.values() == ["of size", "3", "from std::vector"] assert cstats.values() == ["of size", "3", "from std::vector"]
assert rev == approx([2, 56.78, 2, 0, 2], rel=1e-05) assert rev == approx([2, 56.78, 2, 0, 2], rel=1e-05)
if env.GRAALPY:
pytest.skip("ConstructorStats is incompatible with GraalPy.")
assert cstats.alive() == 4 assert cstats.alive() == 4
del it del it
assert cstats.alive() == 3 assert cstats.alive() == 3

View File

@ -2,10 +2,13 @@ from __future__ import annotations
import pytest import pytest
import env # noqa: F401
m = pytest.importorskip("pybind11_tests.smart_ptr") m = pytest.importorskip("pybind11_tests.smart_ptr")
from pybind11_tests import ConstructorStats # noqa: E402 from pybind11_tests import ConstructorStats # noqa: E402
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_smart_ptr(capture): def test_smart_ptr(capture):
# Object1 # Object1
for i, o in enumerate( for i, o in enumerate(
@ -118,6 +121,7 @@ def test_smart_ptr_refcounting():
assert m.test_object1_refcounting() assert m.test_object1_refcounting()
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_unique_nodelete(): def test_unique_nodelete():
o = m.MyObject4(23) o = m.MyObject4(23)
assert o.value == 23 assert o.value == 23
@ -129,6 +133,7 @@ def test_unique_nodelete():
assert cstats.alive() == 0 assert cstats.alive() == 0
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_unique_nodelete4a(): def test_unique_nodelete4a():
o = m.MyObject4a(23) o = m.MyObject4a(23)
assert o.value == 23 assert o.value == 23
@ -140,6 +145,7 @@ def test_unique_nodelete4a():
assert cstats.alive() == 0 assert cstats.alive() == 0
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_unique_deleter(): def test_unique_deleter():
m.MyObject4a(0) m.MyObject4a(0)
o = m.MyObject4b(23) o = m.MyObject4b(23)
@ -156,6 +162,7 @@ def test_unique_deleter():
assert cstats4b.alive() == 0 assert cstats4b.alive() == 0
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_large_holder(): def test_large_holder():
o = m.MyObject5(5) o = m.MyObject5(5)
assert o.value == 5 assert o.value == 5
@ -165,6 +172,7 @@ def test_large_holder():
assert cstats.alive() == 0 assert cstats.alive() == 0
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_shared_ptr_and_references(): def test_shared_ptr_and_references():
s = m.SharedPtrRef() s = m.SharedPtrRef()
stats = ConstructorStats.get(m.A) stats = ConstructorStats.get(m.A)
@ -196,6 +204,7 @@ def test_shared_ptr_and_references():
assert stats.alive() == 0 assert stats.alive() == 0
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_shared_ptr_from_this_and_references(): def test_shared_ptr_from_this_and_references():
s = m.SharedFromThisRef() s = m.SharedFromThisRef()
stats = ConstructorStats.get(m.B) stats = ConstructorStats.get(m.B)
@ -242,6 +251,7 @@ def test_shared_ptr_from_this_and_references():
assert y is z assert y is z
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_move_only_holder(): def test_move_only_holder():
a = m.TypeWithMoveOnlyHolder.make() a = m.TypeWithMoveOnlyHolder.make()
b = m.TypeWithMoveOnlyHolder.make_as_object() b = m.TypeWithMoveOnlyHolder.make_as_object()
@ -253,6 +263,7 @@ def test_move_only_holder():
assert stats.alive() == 0 assert stats.alive() == 0
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_holder_with_addressof_operator(): def test_holder_with_addressof_operator():
# this test must not throw exception from c++ # this test must not throw exception from c++
a = m.TypeForHolderWithAddressOf.make() a = m.TypeForHolderWithAddressOf.make()
@ -283,6 +294,7 @@ def test_holder_with_addressof_operator():
assert stats.alive() == 0 assert stats.alive() == 0
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_move_only_holder_with_addressof_operator(): def test_move_only_holder_with_addressof_operator():
a = m.TypeForMoveOnlyHolderWithAddressOf.make() a = m.TypeForMoveOnlyHolderWithAddressOf.make()
a.print_object() a.print_object()

View File

@ -2,6 +2,7 @@ from __future__ import annotations
import pytest import pytest
import env # noqa: F401
from pybind11_tests import ConstructorStats, UserType from pybind11_tests import ConstructorStats, UserType
from pybind11_tests import stl as m from pybind11_tests import stl as m
@ -362,6 +363,7 @@ def test_function_with_string_and_vector_string_arg():
assert m.func_with_string_or_vector_string_arg_overload("A") == 3 assert m.func_with_string_or_vector_string_arg_overload("A") == 3
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_stl_ownership(): def test_stl_ownership():
cstats = ConstructorStats.get(m.Placeholder) cstats = ConstructorStats.get(m.Placeholder)
assert cstats.alive() == 0 assert cstats.alive() == 0

View File

@ -37,7 +37,8 @@ struct WithPyObjectPtrReturnTrampoline : WithPyObjectPtrReturn {
std::string call_return_pyobject_ptr(const WithPyObjectPtrReturn *base_class_ptr) { std::string call_return_pyobject_ptr(const WithPyObjectPtrReturn *base_class_ptr) {
PyObject *returned_obj = base_class_ptr->return_pyobject_ptr(); PyObject *returned_obj = base_class_ptr->return_pyobject_ptr();
#if !defined(PYPY_VERSION) // It is not worth the trouble doing something special for PyPy. // It is not worth the trouble doing something special for PyPy/GraalPy
#if !defined(PYPY_VERSION) && !defined(GRAALVM_PYTHON)
if (Py_REFCNT(returned_obj) != 1) { if (Py_REFCNT(returned_obj) != 1) {
py::pybind11_fail(__FILE__ ":" PYBIND11_TOSTRING(__LINE__)); py::pybind11_fail(__FILE__ ":" PYBIND11_TOSTRING(__LINE__));
} }

View File

@ -4,7 +4,7 @@ import sys
import pytest import pytest
import env # noqa: F401 import env
m = pytest.importorskip("pybind11_tests.virtual_functions") m = pytest.importorskip("pybind11_tests.virtual_functions")
from pybind11_tests import ConstructorStats # noqa: E402 from pybind11_tests import ConstructorStats # noqa: E402
@ -82,6 +82,9 @@ def test_override(capture, msg):
""" """
) )
if env.GRAALPY:
pytest.skip("ConstructorStats is incompatible with GraalPy.")
cstats = ConstructorStats.get(m.ExampleVirt) cstats = ConstructorStats.get(m.ExampleVirt)
assert cstats.alive() == 3 assert cstats.alive() == 3
del ex12, ex12p, ex12p2 del ex12, ex12p, ex12p2
@ -91,6 +94,7 @@ def test_override(capture, msg):
assert cstats.move_constructions >= 0 assert cstats.move_constructions >= 0
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_alias_delay_initialization1(capture): def test_alias_delay_initialization1(capture):
"""`A` only initializes its trampoline class when we inherit from it """`A` only initializes its trampoline class when we inherit from it
@ -130,6 +134,7 @@ def test_alias_delay_initialization1(capture):
) )
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC")
def test_alias_delay_initialization2(capture): def test_alias_delay_initialization2(capture):
"""`A2`, unlike the above, is configured to always initialize the alias """`A2`, unlike the above, is configured to always initialize the alias
@ -188,7 +193,7 @@ def test_alias_delay_initialization2(capture):
# PyPy: Reference count > 1 causes call with noncopyable instance # PyPy: Reference count > 1 causes call with noncopyable instance
# to fail in ncv1.print_nc() # to fail in ncv1.print_nc()
@pytest.mark.xfail("env.PYPY") @pytest.mark.xfail("env.PYPY or env.GRAALPY")
@pytest.mark.skipif( @pytest.mark.skipif(
not hasattr(m, "NCVirt"), reason="NCVirt does not work on Intel/PGI/NVCC compilers" not hasattr(m, "NCVirt"), reason="NCVirt does not work on Intel/PGI/NVCC compilers"
) )