11from __future__ import annotations
22
3+ import contextlib
34import os
45import pickle
56import sys
67
78import pytest
89
10+ CONCURRENT_INTERPRETERS_SUPPORT = sys .version_info >= (3 , 14 ) and (
11+ sys .version_info != (3 , 14 , 0 , "beta" , 1 )
12+ or sys .version_info != (3 , 14 , 0 , "beta" , 2 )
13+ )
14+
15+
16+ def get_interpreters (* , modern : bool ):
17+ if modern and CONCURRENT_INTERPRETERS_SUPPORT :
18+ from concurrent import interpreters
19+
20+ def create ():
21+ return contextlib .closing (interpreters .create ())
22+
23+ def run_string (
24+ interp : interpreters .Interpreter ,
25+ code : str ,
26+ * ,
27+ shared : dict [str , object ] | None = None ,
28+ ) -> Exception | None :
29+ if shared :
30+ interp .prepare_main (** shared )
31+ try :
32+ interp .exec (code )
33+ return None
34+ except interpreters .ExecutionFailed as err :
35+ return err
36+
37+ return run_string , create
38+
39+ if sys .version_info >= (3 , 12 ):
40+ interpreters = pytest .importorskip (
41+ "_interpreters" if sys .version_info >= (3 , 13 ) else "_xxsubinterpreters"
42+ )
43+
44+ @contextlib .contextmanager
45+ def create (config : str = "" ):
46+ try :
47+ if config :
48+ interp = interpreters .create (config )
49+ else :
50+ interp = interpreters .create ()
51+ except TypeError :
52+ pytest .skip (f"interpreters module needs to support { config } config" )
53+
54+ try :
55+ yield interp
56+ finally :
57+ interpreters .destroy (interp )
58+
59+ def run_string (
60+ interp : int , code : str , shared : dict [str , object ] | None = None
61+ ) -> Exception | None :
62+ kwargs = {"shared" : shared } if shared else {}
63+ return interpreters .run_string (interp , code , ** kwargs )
64+
65+ return run_string , create
66+
67+ pytest .skip ("Test requires the interpreters stdlib module" )
68+
969
1070@pytest .mark .skipif (
1171 sys .platform .startswith ("emscripten" ), reason = "Requires loadable modules"
@@ -15,15 +75,7 @@ def test_independent_subinterpreters():
1575
1676 sys .path .append ("." )
1777
18- # This is supposed to be added in 3.14.0b3
19- if sys .version_info >= (3 , 15 ):
20- import interpreters
21- elif sys .version_info >= (3 , 13 ):
22- interpreters = pytest .importorskip ("_interpreters" )
23- elif sys .version_info >= (3 , 12 ):
24- interpreters = pytest .importorskip ("_xxsubinterpreters" )
25- else :
26- pytest .skip ("Test requires the interpreters stdlib module" )
78+ run_string , create = get_interpreters (modern = True )
2779
2880 m = pytest .importorskip ("mod_per_interpreter_gil" )
2981
@@ -37,23 +89,21 @@ def test_independent_subinterpreters():
3789 pickle.dump(m.internals_at(), f)
3890"""
3991
40- interp1 = interpreters .create ()
41- interp2 = interpreters .create ()
42- try :
92+ with create () as interp1 , create () as interp2 :
4393 try :
44- res0 = interpreters . run_string (interp1 , "import mod_shared_interpreter_gil" )
94+ res0 = run_string (interp1 , "import mod_shared_interpreter_gil" )
4595 if res0 is not None :
46- res0 = res0 . msg
96+ res0 = str ( res0 )
4797 except Exception as e :
4898 res0 = str (e )
4999
50100 pipei , pipeo = os .pipe ()
51- interpreters . run_string (interp1 , code , shared = {"pipeo" : pipeo })
101+ run_string (interp1 , code , shared = {"pipeo" : pipeo })
52102 with open (pipei , "rb" ) as f :
53103 res1 = pickle .load (f )
54104
55105 pipei , pipeo = os .pipe ()
56- interpreters . run_string (interp2 , code , shared = {"pipeo" : pipeo })
106+ run_string (interp2 , code , shared = {"pipeo" : pipeo })
57107 with open (pipei , "rb" ) as f :
58108 res2 = pickle .load (f )
59109
@@ -63,9 +113,6 @@ def test_independent_subinterpreters():
63113 assert m .internals_at () == m2 .internals_at (), (
64114 "internals should be the same within the main interpreter"
65115 )
66- finally :
67- interpreters .destroy (interp1 )
68- interpreters .destroy (interp2 )
69116
70117 assert "does not support loading in subinterpreters" in res0 , (
71118 "cannot use shared_gil in a default subinterpreter"
@@ -90,14 +137,7 @@ def test_dependent_subinterpreters():
90137
91138 sys .path .append ("." )
92139
93- if sys .version_info >= (3 , 15 ):
94- import interpreters
95- elif sys .version_info >= (3 , 13 ):
96- interpreters = pytest .importorskip ("_interpreters" )
97- elif sys .version_info >= (3 , 12 ):
98- interpreters = pytest .importorskip ("_xxsubinterpreters" )
99- else :
100- pytest .skip ("Test requires the interpreters stdlib module" )
140+ run_string , create = get_interpreters (modern = False )
101141
102142 m = pytest .importorskip ("mod_shared_interpreter_gil" )
103143
@@ -111,14 +151,9 @@ def test_dependent_subinterpreters():
111151 pickle.dump(m.internals_at(), f)
112152"""
113153
114- try :
115- interp1 = interpreters .create ("legacy" )
116- except TypeError :
117- pytest .skip ("interpreters module needs to support legacy config" )
118-
119- try :
154+ with create ("legacy" ) as interp1 :
120155 pipei , pipeo = os .pipe ()
121- interpreters . run_string (interp1 , code , shared = {"pipeo" : pipeo })
156+ run_string (interp1 , code , shared = {"pipeo" : pipeo })
122157 with open (pipei , "rb" ) as f :
123158 res1 = pickle .load (f )
124159
@@ -128,8 +163,6 @@ def test_dependent_subinterpreters():
128163 assert m .internals_at () == m2 .internals_at (), (
129164 "internals should be the same within the main interpreter"
130165 )
131- finally :
132- interpreters .destroy (interp1 )
133166
134167 assert res1 != m .internals_at (), "internals should differ from main interpreter"
135168
0 commit comments