Skip to content

Commit 385f33e

Browse files
committed
Add a python-driven subinterpreter test
1 parent 78a1097 commit 385f33e

File tree

3 files changed

+81
-0
lines changed

3 files changed

+81
-0
lines changed

tests/CMakeLists.txt

+5
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ set(PYBIND11_TEST_FILES
148148
test_exceptions
149149
test_factory_constructors
150150
test_gil_scoped
151+
test_interpreters.py
151152
test_iostream
152153
test_kwargs_and_defaults
153154
test_local_bindings
@@ -562,6 +563,10 @@ add_custom_target(
562563
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
563564
USES_TERMINAL)
564565

566+
# This module doesn't get mixed with other test modules because those aren't subinterpreter safe.
567+
pybind11_add_module(mod_test_interpreters THIN_LTO mod_test_interpreters.cpp)
568+
add_dependencies(pytest mod_test_interpreters)
569+
565570
if(PYBIND11_TEST_OVERRIDE)
566571
add_custom_command(
567572
TARGET pytest

tests/mod_test_interpreters.cpp

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <pybind11/pybind11.h>
2+
3+
namespace py = pybind11;
4+
5+
/* Simple test module/test class to check that the referenced internals data of external pybind11
6+
* modules are different across subinterpreters
7+
*/
8+
9+
PYBIND11_MODULE(mod_test_interpreters,
10+
m,
11+
py::mod_multi_interpreter_one_gil(),
12+
py::mod_gil_not_used(),
13+
py::mod_per_interpreter_gil()) {
14+
m.def("internals_at",
15+
[]() { return reinterpret_cast<uintptr_t>(&py::detail::get_internals()); });
16+
}

tests/test_interpreters.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
import os
5+
import pickle
6+
7+
def test_interpreters():
8+
"""Makes sure the internals object differs across subinterpreters"""
9+
10+
import mod_test_interpreters as m
11+
12+
i = None
13+
try:
14+
# 3.14+
15+
import interpreters as i
16+
except ImportError:
17+
try:
18+
# 3.13
19+
import _interpreters as i
20+
except ImportError:
21+
try:
22+
# 3.12
23+
import _xxsubinterpreters as i
24+
except ImportError:
25+
# <= 3.11
26+
pytest.skip("Test requires a the interpreters stdlib module")
27+
return
28+
29+
code = """
30+
import mod_test_interpreters as m
31+
import pickle
32+
with open(pipeo, 'wb') as f:
33+
pickle.dump(m.internals_at(), f)
34+
"""
35+
36+
interp1 = i.create()
37+
interp2 = i.create()
38+
try:
39+
pipei, pipeo = os.pipe()
40+
i.run_string(interp1, code, shared={'pipeo':pipeo})
41+
with open(pipei, 'rb') as f:
42+
res1 = pickle.load(f)
43+
44+
pipei, pipeo = os.pipe()
45+
i.run_string(interp2, code, shared={'pipeo':pipeo})
46+
with open(pipei, 'rb') as f:
47+
res2 = pickle.load(f)
48+
49+
# do this while the two interpreters are active
50+
import mod_test_interpreters as m2
51+
print(dir(m))
52+
print(dir(m2))
53+
assert m.internals_at() == m2.internals_at(), "internals should be the same within the main interpreter"
54+
finally:
55+
i.destroy(interp1)
56+
i.destroy(interp2)
57+
58+
assert res1 != m.internals_at(), "internals should differ from main interpreter"
59+
assert res2 != m.internals_at(), "internals should differ from main interpreter"
60+
assert res1 != res2, "internals should differ between interpreters"

0 commit comments

Comments
 (0)