| #include <pybind11/critical_section.h> | |
| #include <pybind11/embed.h> | |
| #include <pybind11/pybind11.h> | |
| #include <pybind11/stl_bind.h> | |
| // Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to | |
| // catch 2.0.1; this should be fixed in the next catch release after 2.0.1). | |
| PYBIND11_WARNING_DISABLE_MSVC(4996) | |
| #include "catch_skip.h" | |
| #include <catch.hpp> | |
| #include <cstdlib> | |
| #include <fstream> | |
| #include <functional> | |
| #include <thread> | |
| #include <utility> | |
| namespace py = pybind11; | |
| using namespace py::literals; | |
| struct ImplicitReturnItem { | |
| explicit ImplicitReturnItem(int value) : value(value) {} | |
| int value; | |
| }; | |
| struct ImplicitReturnValues : std::vector<std::shared_ptr<ImplicitReturnItem>> { | |
| using std::vector<std::shared_ptr<ImplicitReturnItem>>::vector; | |
| static int instances; | |
| struct Counter { | |
| Counter() { ++instances; } | |
| Counter(const Counter &) { ++instances; } | |
| ~Counter() { --instances; } | |
| } counter; | |
| }; | |
| int ImplicitReturnValues::instances = 0; | |
| PYBIND11_MAKE_OPAQUE(ImplicitReturnValues) | |
| struct ImplicitReturnProvider { | |
| virtual ~ImplicitReturnProvider() = default; | |
| virtual ImplicitReturnValues values() = 0; | |
| virtual const ImplicitReturnValues &reference() = 0; | |
| virtual ImplicitReturnValues *pointer() = 0; | |
| #ifdef PYBIND11_HAS_STRING_VIEW | |
| virtual std::u16string_view view() = 0; | |
| #endif | |
| }; | |
| struct PyImplicitReturnProvider : ImplicitReturnProvider { | |
| ImplicitReturnValues values() override { | |
| PYBIND11_OVERRIDE_PURE(ImplicitReturnValues, ImplicitReturnProvider, values, ); | |
| } | |
| const ImplicitReturnValues &reference() override { | |
| PYBIND11_OVERRIDE_PURE(const ImplicitReturnValues &, ImplicitReturnProvider, reference, ); | |
| } | |
| ImplicitReturnValues *pointer() override { | |
| PYBIND11_OVERRIDE_PURE(ImplicitReturnValues *, ImplicitReturnProvider, pointer, ); | |
| } | |
| #ifdef PYBIND11_HAS_STRING_VIEW | |
| std::u16string_view view() override { | |
| PYBIND11_OVERRIDE_PURE(std::u16string_view, ImplicitReturnProvider, view, ); | |
| } | |
| #endif | |
| }; | |
| PYBIND11_EMBEDDED_MODULE(implicit_return_module, m, py::multiple_interpreters::not_supported()) { | |
| py::class_<ImplicitReturnItem, std::shared_ptr<ImplicitReturnItem>>(m, "Item").def( | |
| py::init<int>()); | |
| py::bind_vector<ImplicitReturnValues>(m, "Values"); | |
| py::implicitly_convertible<py::list, ImplicitReturnValues>(); | |
| py::class_<ImplicitReturnProvider, PyImplicitReturnProvider>(m, "Provider").def(py::init<>()); | |
| } | |
| TEST_CASE("Implicit conversions in native trampoline returns") { | |
| py::dict locals; | |
| py::exec(R"( | |
| from implicit_return_module import Item, Provider | |
| class PythonProvider(Provider): | |
| def __init__(self): | |
| super().__init__() | |
| self.result = [Item(17), Item(-4), Item(99)] | |
| self.fresh = False | |
| def values(self): | |
| return list(self.result) if self.fresh else self.result | |
| def reference(self): | |
| return self.result | |
| def pointer(self): | |
| return self.result | |
| def view(self): | |
| return "hello" | |
| provider = PythonProvider() | |
| )", | |
| locals); | |
| py::object provider = locals["provider"]; | |
| auto *cpp_provider = provider.cast<ImplicitReturnProvider *>(); | |
| SECTION("Copy from a retained Python return value") { provider.attr("fresh") = false; } | |
| SECTION("Move from a temporary Python return value") { provider.attr("fresh") = true; } | |
| REQUIRE(ImplicitReturnValues::instances == 0); | |
| // An enclosing bound call already owns its conversion temporaries. They must | |
| // not be destroyed earlier by a nested return-conversion frame. | |
| auto bound_call = py::cpp_function([&]() { | |
| auto result = cpp_provider->values(); | |
| return ImplicitReturnValues::instances; | |
| }); | |
| REQUIRE(bound_call().cast<int>() == 2); | |
| REQUIRE(ImplicitReturnValues::instances == 0); | |
| // A short-lived return-conversion frame cannot support a borrowed result. | |
| REQUIRE_THROWS_AS(cpp_provider->reference(), py::cast_error); | |
| REQUIRE_THROWS_AS(cpp_provider->pointer(), py::cast_error); | |
| #ifdef PYBIND11_HAS_STRING_VIEW | |
| REQUIRE_THROWS_AS(cpp_provider->view(), py::cast_error); | |
| #endif | |
| // Calling from native C++ is essential: a bound function would already provide | |
| // a loader frame and hide the missing life support during return conversion. | |
| for (int i = 0; i < 3; ++i) { | |
| auto result = cpp_provider->values(); | |
| REQUIRE(ImplicitReturnValues::instances == 1); | |
| REQUIRE(result.size() == 3); | |
| REQUIRE(result[0]->value == 17); | |
| REQUIRE(result[1]->value == -4); | |
| REQUIRE(result[2]->value == 99); | |
| } | |
| REQUIRE_THROWS_AS(cpp_provider->reference(), py::cast_error); | |
| auto retained = cpp_provider->values(); | |
| provider.attr("result").attr("clear")(); | |
| REQUIRE(retained[0]->value == 17); | |
| REQUIRE(retained[1]->value == -4); | |
| REQUIRE(retained[2]->value == 99); | |
| provider.attr("result") = py::make_tuple("invalid"); | |
| REQUIRE_THROWS_AS(cpp_provider->values(), py::cast_error); | |
| } | |
| size_t get_sys_path_size() { | |
| auto sys_path = py::module::import("sys").attr("path"); | |
| return py::len(sys_path); | |
| } | |
| bool has_state_dict_internals_obj() { | |
| py::dict state = py::detail::get_python_state_dict(); | |
| return state.contains(PYBIND11_INTERNALS_ID); | |
| } | |
| uintptr_t get_details_as_uintptr() { | |
| return reinterpret_cast<uintptr_t>(py::detail::get_internals_pp_manager().get_pp()->get()); | |
| } | |
| class Widget { | |
| public: | |
| explicit Widget(std::string message) : message(std::move(message)) {} | |
| virtual ~Widget() = default; | |
| std::string the_message() const { return message; } | |
| virtual int the_answer() const = 0; | |
| virtual std::string argv0() const = 0; | |
| private: | |
| std::string message; | |
| }; | |
| class PyWidget final : public Widget { | |
| using Widget::Widget; | |
| int the_answer() const override { PYBIND11_OVERRIDE_PURE(int, Widget, the_answer, ); } | |
| std::string argv0() const override { PYBIND11_OVERRIDE_PURE(std::string, Widget, argv0, ); } | |
| }; | |
| class test_override_cache_helper { | |
| public: | |
| virtual int func() { return 0; } | |
| test_override_cache_helper() = default; | |
| virtual ~test_override_cache_helper() = default; | |
| // Non-copyable | |
| test_override_cache_helper &operator=(test_override_cache_helper const &Right) = delete; | |
| test_override_cache_helper(test_override_cache_helper const &Copy) = delete; | |
| }; | |
| class test_override_cache_helper_trampoline : public test_override_cache_helper { | |
| int func() override { PYBIND11_OVERRIDE(int, test_override_cache_helper, func, ); } | |
| }; | |
| PYBIND11_EMBEDDED_MODULE(widget_module, m, py::multiple_interpreters::per_interpreter_gil()) { | |
| py::class_<Widget, PyWidget>(m, "Widget") | |
| .def(py::init<std::string>()) | |
| .def_property_readonly("the_message", &Widget::the_message); | |
| m.def("add", [](int i, int j) { return i + j; }); | |
| auto sub = m.def_submodule("sub"); | |
| sub.def("add", [](int i, int j) { return i + j; }); | |
| } | |
| PYBIND11_EMBEDDED_MODULE(trampoline_module, m, py::multiple_interpreters::not_supported()) { | |
| py::class_<test_override_cache_helper, | |
| test_override_cache_helper_trampoline, | |
| std::shared_ptr<test_override_cache_helper>>(m, "test_override_cache_helper") | |
| .def(py::init_alias<>()) | |
| .def("func", &test_override_cache_helper::func); | |
| } | |
| enum class SomeEnum { value1, value2 }; // Added in PR #6015 | |
| PYBIND11_EMBEDDED_MODULE(enum_module, m, py::multiple_interpreters::per_interpreter_gil()) { | |
| py::enum_<SomeEnum>(m, "SomeEnum") | |
| .value("value1", SomeEnum::value1) | |
| .value("value2", SomeEnum::value2); | |
| } | |
| PYBIND11_EMBEDDED_MODULE(throw_exception, , py::multiple_interpreters::not_supported()) { | |
| throw std::runtime_error("C++ Error"); | |
| } | |
| PYBIND11_EMBEDDED_MODULE(throw_error_already_set, , py::multiple_interpreters::not_supported()) { | |
| auto d = py::dict(); | |
| d["missing"].cast<py::object>(); | |
| } | |
| static int internals_init_module_executions = 0; | |
| PYBIND11_EMBEDDED_MODULE(internals_init_module, | |
| m, | |
| py::mod_gil_not_used(), | |
| py::multiple_interpreters::per_interpreter_gil()) { | |
| ++internals_init_module_executions; | |
| m.def("answer", []() { return 42; }); | |
| } | |
| TEST_CASE("PYTHONPATH is used to update sys.path") { | |
| // The setup for this TEST_CASE is in catch.cpp! | |
| auto sys_path = py::str(py::module_::import("sys").attr("path")).cast<std::string>(); | |
| REQUIRE_THAT( | |
| sys_path, | |
| Catch::Matchers::Contains("pybind11_test_with_catch_PYTHONPATH_2099743835476552")); | |
| } | |
| TEST_CASE("Pass classes and data between modules defined in C++ and Python") { | |
| auto module_ = py::module_::import("test_interpreter"); | |
| REQUIRE(py::hasattr(module_, "DerivedWidget")); | |
| auto locals = py::dict("hello"_a = "Hello, World!", "x"_a = 5, **module_.attr("__dict__")); | |
| py::exec(R"( | |
| widget = DerivedWidget("{} - {}".format(hello, x)) | |
| message = widget.the_message | |
| )", | |
| py::globals(), | |
| locals); | |
| REQUIRE(locals["message"].cast<std::string>() == "Hello, World! - 5"); | |
| auto py_widget = module_.attr("DerivedWidget")("The question"); | |
| auto message = py_widget.attr("the_message"); | |
| REQUIRE(message.cast<std::string>() == "The question"); | |
| const auto &cpp_widget = py_widget.cast<const Widget &>(); | |
| REQUIRE(cpp_widget.the_answer() == 42); | |
| } | |
| TEST_CASE("Override cache") { | |
| auto module_ = py::module_::import("test_trampoline"); | |
| REQUIRE(py::hasattr(module_, "func")); | |
| REQUIRE(py::hasattr(module_, "func2")); | |
| auto locals = py::dict(**module_.attr("__dict__")); | |
| int i = 0; | |
| for (; i < 1500; ++i) { | |
| std::shared_ptr<test_override_cache_helper> p_obj; | |
| std::shared_ptr<test_override_cache_helper> p_obj2; | |
| py::object loc_inst = locals["func"](); | |
| p_obj = py::cast<std::shared_ptr<test_override_cache_helper>>(loc_inst); | |
| int ret = p_obj->func(); | |
| REQUIRE(ret == 42); | |
| loc_inst = locals["func2"](); | |
| p_obj2 = py::cast<std::shared_ptr<test_override_cache_helper>>(loc_inst); | |
| p_obj2->func(); | |
| } | |
| } | |
| TEST_CASE("Import error handling") { | |
| REQUIRE_NOTHROW(py::module_::import("widget_module")); | |
| REQUIRE_THROWS_WITH(py::module_::import("throw_exception"), "ImportError: C++ Error"); | |
| REQUIRE_THROWS_WITH(py::module_::import("throw_error_already_set"), | |
| Catch::Contains("ImportError: initialization failed")); | |
| auto locals = py::dict("is_keyerror"_a = false, "message"_a = "not set"); | |
| py::exec(R"( | |
| try: | |
| import throw_error_already_set | |
| except ImportError as e: | |
| is_keyerror = type(e.__cause__) == KeyError | |
| message = str(e.__cause__) | |
| )", | |
| py::globals(), | |
| locals); | |
| REQUIRE(locals["is_keyerror"].cast<bool>() == true); | |
| REQUIRE(locals["message"].cast<std::string>() == "'missing'"); | |
| } | |
| TEST_CASE("There can be only one interpreter") { | |
| static_assert(std::is_move_constructible<py::scoped_interpreter>::value, ""); | |
| static_assert(!std::is_move_assignable<py::scoped_interpreter>::value, ""); | |
| static_assert(!std::is_copy_constructible<py::scoped_interpreter>::value, ""); | |
| static_assert(!std::is_copy_assignable<py::scoped_interpreter>::value, ""); | |
| REQUIRE_THROWS_WITH(py::initialize_interpreter(), "The interpreter is already running"); | |
| REQUIRE_THROWS_WITH(py::scoped_interpreter(), "The interpreter is already running"); | |
| py::finalize_interpreter(); | |
| REQUIRE_NOTHROW(py::scoped_interpreter()); | |
| { | |
| auto pyi1 = py::scoped_interpreter(); | |
| auto pyi2 = std::move(pyi1); | |
| } | |
| py::initialize_interpreter(); | |
| } | |
| TEST_CASE("Custom PyConfig") { | |
| py::finalize_interpreter(); | |
| PyConfig config; | |
| PyConfig_InitPythonConfig(&config); | |
| REQUIRE_NOTHROW(py::scoped_interpreter{&config}); | |
| { | |
| py::scoped_interpreter p{&config}; | |
| REQUIRE(py::module_::import("widget_module").attr("add")(1, 41).cast<int>() == 42); | |
| } | |
| py::initialize_interpreter(); | |
| } | |
| TEST_CASE("scoped_interpreter with PyConfig_InitIsolatedConfig and argv") { | |
| std::vector<std::string> path; | |
| for (auto p : py::module::import("sys").attr("path")) { | |
| path.emplace_back(py::str(p)); | |
| } | |
| py::finalize_interpreter(); | |
| { | |
| PyConfig config; | |
| PyConfig_InitIsolatedConfig(&config); | |
| char *argv[] = {strdup("a.out")}; | |
| py::scoped_interpreter argv_scope{&config, 1, argv, true}; | |
| std::free(argv[0]); | |
| // Because this config is isolated, setting the path during init will not work, we have to | |
| // set it manually. If we don't set it, then we can't import "test_interpreter" | |
| for (auto &&p : path) { | |
| py::list(py::module::import("sys").attr("path")).append(p); | |
| } | |
| try { | |
| auto module = py::module::import("test_interpreter"); | |
| auto py_widget = module.attr("DerivedWidget")("The question"); | |
| const auto &cpp_widget = py_widget.cast<const Widget &>(); | |
| REQUIRE(cpp_widget.argv0() == "a.out"); | |
| } catch (py::error_already_set &e) { | |
| // catch here so that the exception doesn't escape the interpreter that owns it | |
| FAIL(e.what()); | |
| } | |
| } | |
| py::initialize_interpreter(); | |
| } | |
| TEST_CASE("scoped_interpreter with PyConfig_InitPythonConfig and argv") { | |
| py::finalize_interpreter(); | |
| { | |
| PyConfig config; | |
| PyConfig_InitPythonConfig(&config); | |
| // `initialize_interpreter() overrides the default value for config.parse_argv (`1`) by | |
| // changing it to `0`. This test exercises `scoped_interpreter` with the default config. | |
| char *argv[] = {strdup("a.out"), strdup("arg1")}; | |
| py::scoped_interpreter argv_scope(&config, 2, argv); | |
| std::free(argv[0]); | |
| std::free(argv[1]); | |
| auto module = py::module::import("test_interpreter"); | |
| auto py_widget = module.attr("DerivedWidget")("The question"); | |
| const auto &cpp_widget = py_widget.cast<const Widget &>(); | |
| REQUIRE(cpp_widget.argv0() == "arg1"); | |
| } | |
| py::initialize_interpreter(); | |
| } | |
| TEST_CASE("Add program dir to path without PyConfig") { | |
| py::finalize_interpreter(); | |
| size_t path_size_add_program_dir_to_path_false = 0; | |
| { | |
| py::scoped_interpreter scoped_interp{true, 0, nullptr, false}; | |
| path_size_add_program_dir_to_path_false = get_sys_path_size(); | |
| } | |
| { | |
| py::scoped_interpreter scoped_interp{}; | |
| REQUIRE(get_sys_path_size() == path_size_add_program_dir_to_path_false + 1); | |
| } | |
| py::initialize_interpreter(); | |
| } | |
| TEST_CASE("Add program dir to path using PyConfig") { | |
| py::finalize_interpreter(); | |
| size_t path_size_add_program_dir_to_path_false = 0; | |
| { | |
| PyConfig config; | |
| PyConfig_InitPythonConfig(&config); | |
| py::scoped_interpreter scoped_interp{&config, 0, nullptr, false}; | |
| path_size_add_program_dir_to_path_false = get_sys_path_size(); | |
| } | |
| { | |
| PyConfig config; | |
| PyConfig_InitPythonConfig(&config); | |
| py::scoped_interpreter scoped_interp{&config}; | |
| REQUIRE(get_sys_path_size() == path_size_add_program_dir_to_path_false + 1); | |
| } | |
| py::initialize_interpreter(); | |
| } | |
| TEST_CASE("Internals are initialized when a module executes", "[internals_init]") { | |
| // Use a fresh interpreter so that another module cannot have populated its internals. | |
| py::finalize_interpreter(); | |
| py::initialize_interpreter(); | |
| internals_init_module_executions = 0; | |
| REQUIRE_FALSE(has_state_dict_internals_obj()); | |
| REQUIRE(py::detail::get_local_internals_capsule() == nullptr); | |
| auto *definition = PyInit_internals_init_module(); | |
| REQUIRE(definition != nullptr); | |
| REQUIRE_FALSE(has_state_dict_internals_obj()); | |
| REQUIRE(py::detail::get_local_internals_capsule() == nullptr); | |
| auto spec = py::module_::import("importlib.util") | |
| .attr("spec_from_loader")("internals_init_module", py::none()); | |
| auto *module_def = reinterpret_cast<PyModuleDef *>(definition); | |
| auto module | |
| = py::reinterpret_steal<py::module_>(PyModule_FromDefAndSpec(module_def, spec.ptr())); | |
| REQUIRE(module); | |
| module.attr("__spec__") = spec; | |
| REQUIRE_FALSE(has_state_dict_internals_obj()); | |
| REQUIRE(py::detail::get_local_internals_capsule() == nullptr); | |
| REQUIRE(internals_init_module_executions == 0); | |
| REQUIRE(PyModule_ExecDef(module.ptr(), module_def) == 0); | |
| REQUIRE(has_state_dict_internals_obj()); | |
| REQUIRE(py::detail::get_local_internals_capsule() != nullptr); | |
| REQUIRE(internals_init_module_executions == 1); | |
| REQUIRE(module.attr("answer")().cast<int>() == 42); | |
| } | |
| TEST_CASE("Restart the interpreter", "[internals_init]") { | |
| // Verify pre-restart state. | |
| REQUIRE(py::module_::import("widget_module").attr("add")(1, 2).cast<int>() == 3); | |
| REQUIRE(has_state_dict_internals_obj()); | |
| REQUIRE(py::module_::import("external_module").attr("A")(123).attr("value").cast<int>() | |
| == 123); | |
| // local and foreign module internals should point to the same internals: | |
| REQUIRE(get_details_as_uintptr() | |
| == py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>()); | |
| // Restart the interpreter. | |
| py::finalize_interpreter(); | |
| REQUIRE(Py_IsInitialized() == 0); | |
| py::initialize_interpreter(); | |
| REQUIRE(Py_IsInitialized() == 1); | |
| // Internals are deleted after a restart. | |
| REQUIRE_FALSE(has_state_dict_internals_obj()); | |
| REQUIRE(get_details_as_uintptr() == 0); | |
| pybind11::detail::get_internals(); | |
| REQUIRE(has_state_dict_internals_obj()); | |
| REQUIRE(get_details_as_uintptr() != 0); | |
| REQUIRE(get_details_as_uintptr() | |
| == py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>()); | |
| REQUIRE(py::module_::import("external_module").attr("A")(321).attr("value").cast<int>() | |
| == 321); | |
| // Make sure that an interpreter with no get_internals() created until finalize still gets the | |
| // internals destroyed | |
| py::finalize_interpreter(); | |
| py::initialize_interpreter(); | |
| bool ran = false; | |
| py::module_::import("__main__").attr("internals_destroy_test") | |
| = py::capsule(&ran, [](void *ran) { | |
| py::detail::get_internals(); | |
| REQUIRE(has_state_dict_internals_obj()); | |
| *static_cast<bool *>(ran) = true; | |
| }); | |
| REQUIRE_FALSE(has_state_dict_internals_obj()); | |
| REQUIRE_FALSE(ran); | |
| py::finalize_interpreter(); | |
| REQUIRE(ran); | |
| py::initialize_interpreter(); | |
| REQUIRE_FALSE(has_state_dict_internals_obj()); | |
| REQUIRE(get_details_as_uintptr() == 0); | |
| // C++ modules can be reloaded. | |
| auto cpp_module = py::module_::import("widget_module"); | |
| REQUIRE(cpp_module.attr("add")(1, 2).cast<int>() == 3); | |
| // Also verify submodules work | |
| REQUIRE(cpp_module.attr("sub").attr("add")(1, 41).cast<int>() == 42); | |
| // C++ type information is reloaded and can be used in python modules. | |
| auto py_module = py::module_::import("test_interpreter"); | |
| auto py_widget = py_module.attr("DerivedWidget")("Hello after restart"); | |
| REQUIRE(py_widget.attr("the_message").cast<std::string>() == "Hello after restart"); | |
| } | |
| TEST_CASE("Enum module survives restart") { // Added in PR #6015 | |
| // Regression test for gh-5976: py::enum_ uses def_property_static, which | |
| // calls process_attributes::init after initialize_generic's strdup loop, | |
| // leaving arg names as string literals. Without the fix, destruct() would | |
| // call free() on those literals during interpreter finalization. | |
| PYBIND11_CATCH2_SKIP_IF(PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION == 12, | |
| "Pre-existing crash in enum cleanup during finalize on Python 3.12"); | |
| auto enum_mod = py::module_::import("enum_module"); | |
| REQUIRE(enum_mod.attr("SomeEnum").attr("value1").attr("name").cast<std::string>() == "value1"); | |
| py::finalize_interpreter(); | |
| py::initialize_interpreter(); | |
| enum_mod = py::module_::import("enum_module"); | |
| REQUIRE(enum_mod.attr("SomeEnum").attr("value2").attr("name").cast<std::string>() == "value2"); | |
| } | |
| TEST_CASE("Execution frame") { | |
| // When the interpreter is embedded, there is no execution frame, but `py::exec` | |
| // should still function by using reasonable globals: `__main__.__dict__`. | |
| py::exec("var = dict(number=42)"); | |
| REQUIRE(py::globals()["var"]["number"].cast<int>() == 42); | |
| } | |
| TEST_CASE("Threads") { | |
| // Restart interpreter to ensure threads are not initialized | |
| py::finalize_interpreter(); | |
| py::initialize_interpreter(); | |
| constexpr auto num_threads = 10; | |
| auto locals = py::dict("count"_a = 0); | |
| { | |
| py::gil_scoped_release gil_release{}; | |
| #if defined(Py_GIL_DISABLED) && PY_VERSION_HEX < 0x030E0000 | |
| std::mutex mutex; | |
| #endif | |
| auto threads = std::vector<std::thread>(); | |
| for (auto i = 0; i < num_threads; ++i) { | |
| threads.emplace_back([&]() { | |
| py::gil_scoped_acquire gil{}; | |
| #ifdef Py_GIL_DISABLED | |
| # if PY_VERSION_HEX < 0x030E0000 | |
| // This will not run with the GIL, so it won't deadlock. That's | |
| // because of how we run our tests. Be more careful of | |
| // deadlocks if the "free-threaded" GIL could be enabled (at | |
| // runtime). | |
| std::lock_guard<std::mutex> lock(mutex); | |
| # else | |
| // CPython's thread-safe API in no-GIL mode. | |
| py::scoped_critical_section lock(locals); | |
| # endif | |
| #endif | |
| locals["count"] = locals["count"].cast<int>() + 1; | |
| }); | |
| } | |
| for (auto &thread : threads) { | |
| thread.join(); | |
| } | |
| } | |
| REQUIRE(locals["count"].cast<int>() == num_threads); | |
| } | |
| // Scope exit utility https://stackoverflow.com/a/36644501/7255855 | |
| struct scope_exit { | |
| std::function<void()> f_; | |
| explicit scope_exit(std::function<void()> f) noexcept : f_(std::move(f)) {} | |
| ~scope_exit() { | |
| if (f_) { | |
| f_(); | |
| } | |
| } | |
| }; | |
| TEST_CASE("Reload module from file") { | |
| // Disable generation of cached bytecode (.pyc files) for this test, otherwise | |
| // Python might pick up an old version from the cache instead of the new versions | |
| // of the .py files generated below | |
| auto sys = py::module_::import("sys"); | |
| bool dont_write_bytecode = sys.attr("dont_write_bytecode").cast<bool>(); | |
| sys.attr("dont_write_bytecode") = true; | |
| // Reset the value at scope exit | |
| scope_exit reset_dont_write_bytecode( | |
| [&]() { sys.attr("dont_write_bytecode") = dont_write_bytecode; }); | |
| std::string module_name = "test_module_reload"; | |
| std::string module_file = module_name + ".py"; | |
| // Create the module .py file | |
| std::ofstream test_module(module_file); | |
| test_module << "def test():\n"; | |
| test_module << " return 1\n"; | |
| test_module.close(); | |
| // Delete the file at scope exit | |
| scope_exit delete_module_file([&]() { std::remove(module_file.c_str()); }); | |
| // Import the module from file | |
| auto module_ = py::module_::import(module_name.c_str()); | |
| int result = module_.attr("test")().cast<int>(); | |
| REQUIRE(result == 1); | |
| // Update the module .py file with a small change | |
| test_module.open(module_file); | |
| test_module << "def test():\n"; | |
| test_module << " return 2\n"; | |
| test_module.close(); | |
| // Reload the module | |
| module_.reload(); | |
| result = module_.attr("test")().cast<int>(); | |
| REQUIRE(result == 2); | |
| } | |
| TEST_CASE("sys.argv gets initialized properly") { | |
| py::finalize_interpreter(); | |
| { | |
| py::scoped_interpreter default_scope; | |
| auto module = py::module::import("test_interpreter"); | |
| auto py_widget = module.attr("DerivedWidget")("The question"); | |
| const auto &cpp_widget = py_widget.cast<const Widget &>(); | |
| REQUIRE(cpp_widget.argv0().empty()); | |
| } | |
| { | |
| char *argv[] = {strdup("a.out")}; | |
| py::scoped_interpreter argv_scope(true, 1, argv); | |
| std::free(argv[0]); | |
| auto module = py::module::import("test_interpreter"); | |
| auto py_widget = module.attr("DerivedWidget")("The question"); | |
| const auto &cpp_widget = py_widget.cast<const Widget &>(); | |
| REQUIRE(cpp_widget.argv0() == "a.out"); | |
| } | |
| py::initialize_interpreter(); | |
| } | |
| TEST_CASE("make_iterator can be called before then after finalizing an interpreter") { | |
| // Reproduction of issue #2101 (https://github.com/pybind/pybind11/issues/2101) | |
| py::finalize_interpreter(); | |
| std::vector<int> container; | |
| { | |
| pybind11::scoped_interpreter g; | |
| auto iter = pybind11::make_iterator(container.begin(), container.end()); | |
| } | |
| REQUIRE_NOTHROW([&]() { | |
| pybind11::scoped_interpreter g; | |
| auto iter = pybind11::make_iterator(container.begin(), container.end()); | |
| }()); | |
| py::initialize_interpreter(); | |
| } | |
| #ifdef PYBIND11_HAS_STRING_VIEW | |
| TEST_CASE("Casting to a string_view outside a bound function") { | |
| // Regression for PR #6092: view casters add the source to loader_life_support, but | |
| // outside a bound function there is no frame. The caller owns the source's lifetime | |
| // here, so the cast must succeed rather than throw. | |
| py::str unicode("hello"); | |
| py::bytes bytes_obj("world", 5); | |
| auto bytearray_obj | |
| = py::reinterpret_steal<py::object>(PyByteArray_FromStringAndSize("bytes", 5)); | |
| REQUIRE(py::cast<std::string_view>(unicode) == "hello"); | |
| REQUIRE(py::cast<std::string_view>(bytes_obj) == "world"); | |
| REQUIRE(py::cast<std::string_view>(bytearray_obj) == "bytes"); | |
| // Wide string views require an encoded temporary. With no loader life-support | |
| // frame, returning a view into that temporary must fail. | |
| REQUIRE_THROWS_AS(py::cast<std::u16string_view>(unicode), py::cast_error); | |
| REQUIRE_THROWS_AS(py::cast<std::u32string_view>(unicode), py::cast_error); | |
| // Bound-function dispatch provides a frame that keeps both temporaries alive. | |
| auto accepts_wide_views | |
| = py::cpp_function([](std::u16string_view value16, std::u32string_view value32) { | |
| return value16 == std::u16string_view(u"hello") | |
| && value32 == std::u32string_view(U"hello"); | |
| }); | |
| REQUIRE(accepts_wide_views(unicode, unicode).cast<bool>()); | |
| } | |
| #endif |