mirror of
https://github.com/pybind/pybind11.git
synced 2026-03-14 20:27:47 +00:00
tests: handle 3.12 and 3.13 implementations and 3.14.0b3+ (#5732)
* Use pytest.importorskip to get _xxsubinterpreters * tests: use modern interpreter API Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * tests: try to fix beta2 Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * fix: remove debug printout Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * tests: drop useless checks Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * tests: improve check for 3.14.0b3 and b4+ Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * style: pre-commit fixes --------- Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> Co-authored-by: Henry Schreiner <henryschreineriii@gmail.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -1,11 +1,80 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
import pickle
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
# 3.14.0b3+, though sys.implementation.supports_isolated_interpreters is being added in b4
|
||||
# Can be simplified when we drop support for the first three betas
|
||||
CONCURRENT_INTERPRETERS_SUPPORT = (
|
||||
sys.version_info >= (3, 14)
|
||||
and (
|
||||
sys.version_info != (3, 14, 0, "beta", 1)
|
||||
and sys.version_info != (3, 14, 0, "beta", 2)
|
||||
)
|
||||
and (
|
||||
sys.version_info == (3, 14, 0, "beta", 3)
|
||||
or sys.implementation.supports_isolated_interpreters
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def get_interpreters(*, modern: bool):
|
||||
if modern and CONCURRENT_INTERPRETERS_SUPPORT:
|
||||
from concurrent import interpreters
|
||||
|
||||
def create():
|
||||
return contextlib.closing(interpreters.create())
|
||||
|
||||
def run_string(
|
||||
interp: interpreters.Interpreter,
|
||||
code: str,
|
||||
*,
|
||||
shared: dict[str, object] | None = None,
|
||||
) -> Exception | None:
|
||||
if shared:
|
||||
interp.prepare_main(**shared)
|
||||
try:
|
||||
interp.exec(code)
|
||||
return None
|
||||
except interpreters.ExecutionFailed as err:
|
||||
return err
|
||||
|
||||
return run_string, create
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
interpreters = pytest.importorskip(
|
||||
"_interpreters" if sys.version_info >= (3, 13) else "_xxsubinterpreters"
|
||||
)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def create(config: str = ""):
|
||||
try:
|
||||
if config:
|
||||
interp = interpreters.create(config)
|
||||
else:
|
||||
interp = interpreters.create()
|
||||
except TypeError:
|
||||
pytest.skip(f"interpreters module needs to support {config} config")
|
||||
|
||||
try:
|
||||
yield interp
|
||||
finally:
|
||||
interpreters.destroy(interp)
|
||||
|
||||
def run_string(
|
||||
interp: int, code: str, shared: dict[str, object] | None = None
|
||||
) -> Exception | None:
|
||||
kwargs = {"shared": shared} if shared else {}
|
||||
return interpreters.run_string(interp, code, **kwargs)
|
||||
|
||||
return run_string, create
|
||||
|
||||
pytest.skip("Test requires the interpreters stdlib module")
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.platform.startswith("emscripten"), reason="Requires loadable modules"
|
||||
@@ -15,15 +84,7 @@ def test_independent_subinterpreters():
|
||||
|
||||
sys.path.append(".")
|
||||
|
||||
# This is supposed to be added in 3.14.0b3
|
||||
if sys.version_info >= (3, 15):
|
||||
import interpreters
|
||||
elif sys.version_info >= (3, 13):
|
||||
import _interpreters as interpreters
|
||||
elif sys.version_info >= (3, 12):
|
||||
import _xxsubinterpreters as interpreters
|
||||
else:
|
||||
pytest.skip("Test requires the interpreters stdlib module")
|
||||
run_string, create = get_interpreters(modern=True)
|
||||
|
||||
m = pytest.importorskip("mod_per_interpreter_gil")
|
||||
|
||||
@@ -37,36 +98,24 @@ with open(pipeo, 'wb') as f:
|
||||
pickle.dump(m.internals_at(), f)
|
||||
"""
|
||||
|
||||
interp1 = interpreters.create()
|
||||
interp2 = interpreters.create()
|
||||
try:
|
||||
with create() as interp1, create() as interp2:
|
||||
try:
|
||||
res0 = interpreters.run_string(interp1, "import mod_shared_interpreter_gil")
|
||||
res0 = run_string(interp1, "import mod_shared_interpreter_gil")
|
||||
if res0 is not None:
|
||||
res0 = res0.msg
|
||||
res0 = str(res0)
|
||||
except Exception as e:
|
||||
res0 = str(e)
|
||||
|
||||
pipei, pipeo = os.pipe()
|
||||
interpreters.run_string(interp1, code, shared={"pipeo": pipeo})
|
||||
run_string(interp1, code, shared={"pipeo": pipeo})
|
||||
with open(pipei, "rb") as f:
|
||||
res1 = pickle.load(f)
|
||||
|
||||
pipei, pipeo = os.pipe()
|
||||
interpreters.run_string(interp2, code, shared={"pipeo": pipeo})
|
||||
run_string(interp2, code, shared={"pipeo": pipeo})
|
||||
with open(pipei, "rb") as f:
|
||||
res2 = pickle.load(f)
|
||||
|
||||
# do this while the two interpreters are active
|
||||
import mod_per_interpreter_gil as m2
|
||||
|
||||
assert m.internals_at() == m2.internals_at(), (
|
||||
"internals should be the same within the main interpreter"
|
||||
)
|
||||
finally:
|
||||
interpreters.destroy(interp1)
|
||||
interpreters.destroy(interp2)
|
||||
|
||||
assert "does not support loading in subinterpreters" in res0, (
|
||||
"cannot use shared_gil in a default subinterpreter"
|
||||
)
|
||||
@@ -74,12 +123,50 @@ with open(pipeo, 'wb') as f:
|
||||
assert res2 != m.internals_at(), "internals should differ from main interpreter"
|
||||
assert res1 != res2, "internals should differ between interpreters"
|
||||
|
||||
# do this after the two interpreters are destroyed and only one remains
|
||||
import mod_per_interpreter_gil as m3
|
||||
|
||||
assert m.internals_at() == m3.internals_at(), (
|
||||
"internals should be the same within the main interpreter"
|
||||
)
|
||||
@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_independent_subinterpreters_modern():
|
||||
"""Makes sure the internals object differs across independent subinterpreters. Modern (3.14+) syntax."""
|
||||
|
||||
sys.path.append(".")
|
||||
|
||||
m = pytest.importorskip("mod_per_interpreter_gil")
|
||||
|
||||
if not m.defined_PYBIND11_HAS_SUBINTERPRETER_SUPPORT:
|
||||
pytest.skip("Does not have subinterpreter support compiled in")
|
||||
|
||||
from concurrent import interpreters
|
||||
|
||||
code = """
|
||||
import mod_per_interpreter_gil as m
|
||||
|
||||
values.put_nowait(m.internals_at())
|
||||
"""
|
||||
|
||||
with contextlib.closing(interpreters.create()) as interp1, contextlib.closing(
|
||||
interpreters.create()
|
||||
) as interp2:
|
||||
with pytest.raises(
|
||||
interpreters.ExecutionFailed,
|
||||
match="does not support loading in subinterpreters",
|
||||
):
|
||||
interp1.exec("import mod_shared_interpreter_gil")
|
||||
|
||||
values = interpreters.create_queue()
|
||||
interp1.prepare_main(values=values)
|
||||
interp1.exec(code)
|
||||
res1 = values.get_nowait()
|
||||
|
||||
interp2.prepare_main(values=values)
|
||||
interp2.exec(code)
|
||||
res2 = values.get_nowait()
|
||||
|
||||
assert res1 != m.internals_at(), "internals should differ from main interpreter"
|
||||
assert res2 != m.internals_at(), "internals should differ from main interpreter"
|
||||
assert res1 != res2, "internals should differ between interpreters"
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
@@ -90,14 +177,7 @@ def test_dependent_subinterpreters():
|
||||
|
||||
sys.path.append(".")
|
||||
|
||||
if sys.version_info >= (3, 15):
|
||||
import interpreters
|
||||
elif sys.version_info >= (3, 13):
|
||||
import _interpreters as interpreters
|
||||
elif sys.version_info >= (3, 12):
|
||||
import _xxsubinterpreters as interpreters
|
||||
else:
|
||||
pytest.skip("Test requires the interpreters stdlib module")
|
||||
run_string, create = get_interpreters(modern=False)
|
||||
|
||||
m = pytest.importorskip("mod_shared_interpreter_gil")
|
||||
|
||||
@@ -111,31 +191,10 @@ with open(pipeo, 'wb') as f:
|
||||
pickle.dump(m.internals_at(), f)
|
||||
"""
|
||||
|
||||
try:
|
||||
interp1 = interpreters.create("legacy")
|
||||
except TypeError:
|
||||
pytest.skip("interpreters module needs to support legacy config")
|
||||
|
||||
try:
|
||||
with create("legacy") as interp1:
|
||||
pipei, pipeo = os.pipe()
|
||||
interpreters.run_string(interp1, code, shared={"pipeo": pipeo})
|
||||
run_string(interp1, code, shared={"pipeo": pipeo})
|
||||
with open(pipei, "rb") as f:
|
||||
res1 = pickle.load(f)
|
||||
|
||||
# do this while the other interpreter is active
|
||||
import mod_shared_interpreter_gil as m2
|
||||
|
||||
assert m.internals_at() == m2.internals_at(), (
|
||||
"internals should be the same within the main interpreter"
|
||||
)
|
||||
finally:
|
||||
interpreters.destroy(interp1)
|
||||
|
||||
assert res1 != m.internals_at(), "internals should differ from main interpreter"
|
||||
|
||||
# do this after the other interpreters are destroyed and only one remains
|
||||
import mod_shared_interpreter_gil as m3
|
||||
|
||||
assert m.internals_at() == m3.internals_at(), (
|
||||
"internals should be the same within the main interpreter"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user