diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 491f215cef..105869866e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -125,6 +125,8 @@ set(PYBIND11_TEST_FILES test_const_name test_constants_and_functions test_copy_move + test_cross_module_exception_odr_1.py + test_cross_module_exception_odr_2.py test_custom_type_casters test_custom_type_setup test_docstring_options @@ -219,6 +221,8 @@ tests_extra_targets("test_exceptions.py;test_local_bindings.py;test_stl.py;test_ # And add additional targets for other tests. tests_extra_targets("test_exceptions.py" "cross_module_interleaved_error_already_set") tests_extra_targets("test_gil_scoped.py" "cross_module_gil_utils") +tests_extra_targets("test_cross_module_exception_odr_1.py" "cross_module_exception_odr_1") +tests_extra_targets("test_cross_module_exception_odr_2.py" "cross_module_exception_odr_2") set(PYBIND11_EIGEN_REPO "https://gitlab.com/libeigen/eigen.git" diff --git a/tests/conftest.py b/tests/conftest.py index 02ce263afc..e53ba43570 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,13 @@ Adds docstring and exceptions message sanitizers. """ +import sys + +if sys.platform == "linux": + sys.setdlopenflags(0x100 | 0x2) # RTLD_GLOBAL | RTLD_NOW +elif sys.platform == "darwin": + sys.setdlopenflags(0x8 | 0x2) # RTLD_GLOBAL | RTLD_NOW + import contextlib import difflib import gc diff --git a/tests/cross_module_exception_odr_1.cpp b/tests/cross_module_exception_odr_1.cpp new file mode 100644 index 0000000000..7d95c5e54b --- /dev/null +++ b/tests/cross_module_exception_odr_1.cpp @@ -0,0 +1,42 @@ +#include "pybind11/pybind11.h" + +#include + +namespace cross_module_exception_odr { + +#if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4275) +#endif + +class PYBIND11_EXPORT_EXCEPTION evolving : public std::runtime_error { +public: + explicit evolving(const std::string &msg) : std::runtime_error("v1:" + msg) {} +}; + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif + +} // namespace cross_module_exception_odr + +PYBIND11_MODULE(cross_module_exception_odr_1, m) { + using namespace cross_module_exception_odr; + namespace py = pybind11; + + py::register_exception_translator([](std::exception_ptr p) { + try { + if (p) { + std::rethrow_exception(p); + } + } catch (const evolving &e) { + PyErr_SetString(PyExc_RuntimeError, e.what()); + } + }); + + m.def("raise_evolving", [](const std::string &msg) { throw evolving(msg); }); + m.def("raise_evolving_from_module_2", [](const py::capsule &cap) { + auto f = reinterpret_cast(cap.get_pointer()); + f(); + }); +} diff --git a/tests/cross_module_exception_odr_2.cpp b/tests/cross_module_exception_odr_2.cpp new file mode 100644 index 0000000000..440b1f615e --- /dev/null +++ b/tests/cross_module_exception_odr_2.cpp @@ -0,0 +1,44 @@ +#include "pybind11/pybind11.h" + +#include + +namespace cross_module_exception_odr { + +#if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4275) +#endif + +class PYBIND11_EXPORT_EXCEPTION evolving : public std::exception { +public: + const char *what() const noexcept override { return "v2"; } +}; + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif + +void raise_evolving_from_module_2() { throw evolving(); } + +} // namespace cross_module_exception_odr + +PYBIND11_MODULE(cross_module_exception_odr_2, m) { + using namespace cross_module_exception_odr; + namespace py = pybind11; + + py::register_exception_translator([](std::exception_ptr p) { + try { + if (p) { + std::rethrow_exception(p); + } + } catch (const evolving &e) { + PyErr_SetString(PyExc_RuntimeError, e.what()); + } + }); + + m.def("raise_evolving", []() { throw evolving(); }); + m.def("get_raise_evolving_from_module_2_capsule", []() { + return py::capsule(reinterpret_cast(&raise_evolving_from_module_2), + "raise_evolving_from_module_2_function_pointer"); + }); +} diff --git a/tests/test_cross_module_exception_odr_1.py b/tests/test_cross_module_exception_odr_1.py new file mode 100644 index 0000000000..7d4a674595 --- /dev/null +++ b/tests/test_cross_module_exception_odr_1.py @@ -0,0 +1,14 @@ +import cross_module_exception_odr_1 as m +import cross_module_exception_odr_2 as m2 +import pytest + + +def test_raise_evolving(): + with pytest.raises(RuntimeError, match="v1:t1"): + m.raise_evolving("t1") + + +def test_raise_evolving_from_module_2(): + cap = m2.get_raise_evolving_from_module_2_capsule() + with pytest.raises(RuntimeError, match="v2"): + m.raise_evolving_from_module_2(cap) diff --git a/tests/test_cross_module_exception_odr_2.py b/tests/test_cross_module_exception_odr_2.py new file mode 100644 index 0000000000..36cdd9340f --- /dev/null +++ b/tests/test_cross_module_exception_odr_2.py @@ -0,0 +1,7 @@ +import cross_module_exception_odr_2 as m +import pytest + + +def test_raise_evolving(): + with pytest.raises(RuntimeError, match="v2"): + m.raise_evolving()