mirror of
https://github.com/pybind/pybind11.git
synced 2026-04-19 22:39:09 +00:00
Try add tests
This commit is contained in:
@@ -1381,11 +1381,22 @@ You can do that using ``py::custom_type_setup``:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
struct OwnsPythonObjects {
|
||||
py::object value = py::none();
|
||||
struct ContainerOwnsPythonObjects {
|
||||
std::vector<py::object> list;
|
||||
|
||||
void append(const py::object &obj) { list.emplace_back(obj); }
|
||||
py::object at(py::ssize_t index) const {
|
||||
if (index >= size() || index < 0) {
|
||||
throw py::index_error("Index out of range");
|
||||
}
|
||||
return list.at(py::size_t(index));
|
||||
}
|
||||
py::ssize_t size() const { return py::ssize_t_cast(list.size()); }
|
||||
void clear() { list.clear(); }
|
||||
};
|
||||
py::class_<OwnsPythonObjects> cls(
|
||||
m, "OwnsPythonObjects", py::custom_type_setup([](PyHeapTypeObject *heap_type) {
|
||||
|
||||
py::class_<ContainerOwnsPythonObjects> cls(
|
||||
m, "ContainerOwnsPythonObjects", py::custom_type_setup([](PyHeapTypeObject *heap_type) {
|
||||
auto *type = &heap_type->ht_type;
|
||||
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
|
||||
type->tp_traverse = [](PyObject *self_base, visitproc visit, void *arg) {
|
||||
@@ -1394,20 +1405,28 @@ You can do that using ``py::custom_type_setup``:
|
||||
Py_VISIT(Py_TYPE(self_base));
|
||||
#endif
|
||||
if (py::detail::is_holder_constructed(self_base)) {
|
||||
auto &self = py::cast<OwnsPythonObjects&>(py::handle(self_base));
|
||||
Py_VISIT(self.value.ptr());
|
||||
auto &self = py::cast<ContainerOwnsPythonObjects &>(py::handle(self_base));
|
||||
for (auto &item : self.list) {
|
||||
Py_VISIT(item.ptr());
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
type->tp_clear = [](PyObject *self_base) {
|
||||
if (py::detail::is_holder_constructed(self_base)) {
|
||||
auto &self = py::cast<OwnsPythonObjects&>(py::handle(self_base));
|
||||
self.value = py::none();
|
||||
auto &self = py::cast<ContainerOwnsPythonObjects &>(py::handle(self_base));
|
||||
for (auto &item : self.list) {
|
||||
Py_CLEAR(item.ptr());
|
||||
}
|
||||
self.list.clear();
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
}));
|
||||
cls.def(py::init<>());
|
||||
cls.def_readwrite("value", &OwnsPythonObjects::value);
|
||||
cls.def("append", &ContainerOwnsPythonObjects::append);
|
||||
cls.def("at", &ContainerOwnsPythonObjects::at);
|
||||
cls.def("size", &ContainerOwnsPythonObjects::size);
|
||||
cls.def("clear", &ContainerOwnsPythonObjects::clear);
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
28
tests/env.py
28
tests/env.py
@@ -29,3 +29,31 @@ TYPES_ARE_IMMORTAL = (
|
||||
or GRAALPY
|
||||
or (CPYTHON and PY_GIL_DISABLED and (3, 13) <= sys.version_info < (3, 14))
|
||||
)
|
||||
|
||||
|
||||
def check_script_success_in_subprocess(code: str, *, rerun: int = 8) -> None:
|
||||
"""Runs the given code in a subprocess."""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
code = textwrap.dedent(code).strip()
|
||||
try:
|
||||
for _ in range(rerun): # run flakily failing test multiple times
|
||||
subprocess.check_output(
|
||||
[sys.executable, "-c", code],
|
||||
cwd=os.getcwd(),
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as ex:
|
||||
raise RuntimeError(
|
||||
f"Subprocess failed with exit code {ex.returncode}.\n\n"
|
||||
f"Code:\n"
|
||||
f"```python\n"
|
||||
f"{code}\n"
|
||||
f"```\n\n"
|
||||
f"Output:\n"
|
||||
f"{ex.output}"
|
||||
) from None
|
||||
|
||||
@@ -7,22 +7,64 @@
|
||||
BSD-style license that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include <pybind11/detail/internals.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace {
|
||||
struct ContainerOwnsPythonObjects {
|
||||
std::vector<py::object> list;
|
||||
|
||||
struct OwnsPythonObjects {
|
||||
py::object value = py::none();
|
||||
void append(const py::object &obj) { list.emplace_back(obj); }
|
||||
py::object at(py::ssize_t index) const {
|
||||
if (index >= size() || index < 0) {
|
||||
throw py::index_error("Index out of range");
|
||||
}
|
||||
return list.at(py::size_t(index));
|
||||
}
|
||||
py::ssize_t size() const { return py::ssize_t_cast(list.size()); }
|
||||
void clear() { list.clear(); }
|
||||
};
|
||||
|
||||
void add_gc_checkers_with_weakrefs(const py::object &obj) {
|
||||
py::handle global_capsule = py::detail::get_internals_capsule();
|
||||
if (!global_capsule) {
|
||||
throw std::runtime_error("No global internals capsule found");
|
||||
}
|
||||
(void) py::weakref(obj, py::cpp_function([global_capsule, obj](py::handle weakref) -> void {
|
||||
py::handle new_global_capsule = py::detail::get_internals_capsule();
|
||||
if (!new_global_capsule.is(global_capsule)) {
|
||||
throw std::runtime_error(
|
||||
"Global internals capsule was destroyed prematurely");
|
||||
}
|
||||
weakref.dec_ref();
|
||||
}))
|
||||
.release();
|
||||
|
||||
py::handle local_capsule = py::detail::get_local_internals_capsule();
|
||||
if (!local_capsule) {
|
||||
throw std::runtime_error("No local internals capsule found");
|
||||
}
|
||||
(void) py::weakref(
|
||||
obj, py::cpp_function([local_capsule, obj](py::handle weakref) -> void {
|
||||
py::handle new_local_capsule = py::detail::get_local_internals_capsule();
|
||||
if (!new_local_capsule.is(local_capsule)) {
|
||||
throw std::runtime_error("Local internals capsule was destroyed prematurely");
|
||||
}
|
||||
weakref.dec_ref();
|
||||
}))
|
||||
.release();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_SUBMODULE(custom_type_setup, m) {
|
||||
py::class_<OwnsPythonObjects> cls(
|
||||
m, "OwnsPythonObjects", py::custom_type_setup([](PyHeapTypeObject *heap_type) {
|
||||
py::class_<ContainerOwnsPythonObjects> cls(
|
||||
m, "ContainerOwnsPythonObjects", py::custom_type_setup([](PyHeapTypeObject *heap_type) {
|
||||
auto *type = &heap_type->ht_type;
|
||||
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
|
||||
type->tp_traverse = [](PyObject *self_base, visitproc visit, void *arg) {
|
||||
@@ -31,19 +73,29 @@ TEST_SUBMODULE(custom_type_setup, m) {
|
||||
Py_VISIT(Py_TYPE(self_base));
|
||||
#endif
|
||||
if (py::detail::is_holder_constructed(self_base)) {
|
||||
auto &self = py::cast<OwnsPythonObjects &>(py::handle(self_base));
|
||||
Py_VISIT(self.value.ptr());
|
||||
auto &self = py::cast<ContainerOwnsPythonObjects &>(py::handle(self_base));
|
||||
for (auto &item : self.list) {
|
||||
Py_VISIT(item.ptr());
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
type->tp_clear = [](PyObject *self_base) {
|
||||
if (py::detail::is_holder_constructed(self_base)) {
|
||||
auto &self = py::cast<OwnsPythonObjects &>(py::handle(self_base));
|
||||
self.value = py::none();
|
||||
auto &self = py::cast<ContainerOwnsPythonObjects &>(py::handle(self_base));
|
||||
for (auto &item : self.list) {
|
||||
Py_CLEAR(item.ptr());
|
||||
}
|
||||
self.list.clear();
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
}));
|
||||
cls.def(py::init<>());
|
||||
cls.def_readwrite("value", &OwnsPythonObjects::value);
|
||||
cls.def("append", &ContainerOwnsPythonObjects::append);
|
||||
cls.def("at", &ContainerOwnsPythonObjects::at);
|
||||
cls.def("size", &ContainerOwnsPythonObjects::size);
|
||||
cls.def("clear", &ContainerOwnsPythonObjects::clear);
|
||||
|
||||
m.def("add_gc_checkers_with_weakrefs", &add_gc_checkers_with_weakrefs);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import weakref
|
||||
|
||||
import pytest
|
||||
|
||||
import env # noqa: F401
|
||||
import env
|
||||
from pybind11_tests import custom_type_setup as m
|
||||
|
||||
|
||||
@@ -36,15 +36,27 @@ def gc_tester():
|
||||
# PyPy does not seem to reliably garbage collect.
|
||||
@pytest.mark.skipif("env.PYPY or env.GRAALPY")
|
||||
def test_self_cycle(gc_tester):
|
||||
obj = m.OwnsPythonObjects()
|
||||
obj.value = obj
|
||||
obj = m.ContainerOwnsPythonObjects()
|
||||
obj.append(obj)
|
||||
gc_tester(obj)
|
||||
|
||||
|
||||
# PyPy does not seem to reliably garbage collect.
|
||||
@pytest.mark.skipif("env.PYPY or env.GRAALPY")
|
||||
def test_indirect_cycle(gc_tester):
|
||||
obj = m.OwnsPythonObjects()
|
||||
obj_list = [obj]
|
||||
obj.value = obj_list
|
||||
obj = m.ContainerOwnsPythonObjects()
|
||||
obj.append([obj])
|
||||
gc_tester(obj)
|
||||
|
||||
|
||||
@pytest.mark.skipif("env.PYPY or env.GRAALPY")
|
||||
def test_py_cast_useable_on_shutdown():
|
||||
env.check_script_success_in_subprocess(
|
||||
"""
|
||||
from pybind11_tests import custom_type_setup as m
|
||||
|
||||
obj = m.ContainerOwnsPythonObjects()
|
||||
obj.append(obj)
|
||||
m.add_gc_checkers_with_weakrefs(obj)
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
import contextlib
|
||||
import os
|
||||
import pickle
|
||||
import subprocess
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
@@ -269,36 +268,13 @@ def test_import_module_with_singleton_per_interpreter():
|
||||
interp.exec(code)
|
||||
|
||||
|
||||
def check_script_success_in_subprocess(code: str, *, rerun: int = 8) -> None:
|
||||
"""Runs the given code in a subprocess."""
|
||||
code = textwrap.dedent(code).strip()
|
||||
try:
|
||||
for _ in range(rerun): # run flakily failing test multiple times
|
||||
subprocess.check_output(
|
||||
[sys.executable, "-c", code],
|
||||
cwd=os.getcwd(),
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as ex:
|
||||
raise RuntimeError(
|
||||
f"Subprocess failed with exit code {ex.returncode}.\n\n"
|
||||
f"Code:\n"
|
||||
f"```python\n"
|
||||
f"{code}\n"
|
||||
f"```\n\n"
|
||||
f"Output:\n"
|
||||
f"{ex.output}"
|
||||
) from None
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.platform.startswith("emscripten"), reason="Requires loadable modules"
|
||||
)
|
||||
@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
|
||||
def test_import_in_subinterpreter_after_main():
|
||||
"""Tests that importing a module in a subinterpreter after the main interpreter works correctly"""
|
||||
check_script_success_in_subprocess(
|
||||
env.check_script_success_in_subprocess(
|
||||
PREAMBLE_CODE
|
||||
+ textwrap.dedent(
|
||||
"""
|
||||
@@ -319,7 +295,7 @@ def test_import_in_subinterpreter_after_main():
|
||||
)
|
||||
)
|
||||
|
||||
check_script_success_in_subprocess(
|
||||
env.check_script_success_in_subprocess(
|
||||
PREAMBLE_CODE
|
||||
+ textwrap.dedent(
|
||||
"""
|
||||
@@ -354,7 +330,7 @@ def test_import_in_subinterpreter_after_main():
|
||||
@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
|
||||
def test_import_in_subinterpreter_before_main():
|
||||
"""Tests that importing a module in a subinterpreter before the main interpreter works correctly"""
|
||||
check_script_success_in_subprocess(
|
||||
env.check_script_success_in_subprocess(
|
||||
PREAMBLE_CODE
|
||||
+ textwrap.dedent(
|
||||
"""
|
||||
@@ -375,7 +351,7 @@ def test_import_in_subinterpreter_before_main():
|
||||
)
|
||||
)
|
||||
|
||||
check_script_success_in_subprocess(
|
||||
env.check_script_success_in_subprocess(
|
||||
PREAMBLE_CODE
|
||||
+ textwrap.dedent(
|
||||
"""
|
||||
@@ -401,7 +377,7 @@ def test_import_in_subinterpreter_before_main():
|
||||
)
|
||||
)
|
||||
|
||||
check_script_success_in_subprocess(
|
||||
env.check_script_success_in_subprocess(
|
||||
PREAMBLE_CODE
|
||||
+ textwrap.dedent(
|
||||
"""
|
||||
@@ -434,7 +410,7 @@ def test_import_in_subinterpreter_before_main():
|
||||
@pytest.mark.skipif(not CONCURRENT_INTERPRETERS_SUPPORT, reason="Requires 3.14.0b3+")
|
||||
def test_import_in_subinterpreter_concurrently():
|
||||
"""Tests that importing a module in multiple subinterpreters concurrently works correctly"""
|
||||
check_script_success_in_subprocess(
|
||||
env.check_script_success_in_subprocess(
|
||||
PREAMBLE_CODE
|
||||
+ textwrap.dedent(
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user