Skip to content

Commit 818f5b5

Browse files
bpo-32604: Clean up test.support.interpreters. (gh-20926)
There were some minor adjustments needed and a few tests were missing. https://bugs.python.org/issue32604
1 parent c4862e3 commit 818f5b5

File tree

3 files changed

+413
-204
lines changed

3 files changed

+413
-204
lines changed

Lib/test/support/interpreters.py

Lines changed: 93 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Subinterpreters High Level Module."""
22

3+
import time
34
import _xxsubinterpreters as _interpreters
45

56
# aliases:
@@ -19,165 +20,178 @@
1920

2021

2122
def create(*, isolated=True):
22-
"""
23-
Initialize a new (idle) Python interpreter.
24-
"""
23+
"""Return a new (idle) Python interpreter."""
2524
id = _interpreters.create(isolated=isolated)
2625
return Interpreter(id, isolated=isolated)
2726

2827

2928
def list_all():
30-
"""
31-
Get all existing interpreters.
32-
"""
33-
return [Interpreter(id) for id in
34-
_interpreters.list_all()]
29+
"""Return all existing interpreters."""
30+
return [Interpreter(id) for id in _interpreters.list_all()]
3531

3632

3733
def get_current():
38-
"""
39-
Get the currently running interpreter.
40-
"""
34+
"""Return the currently running interpreter."""
4135
id = _interpreters.get_current()
4236
return Interpreter(id)
4337

4438

4539
def get_main():
46-
"""
47-
Get the main interpreter.
48-
"""
40+
"""Return the main interpreter."""
4941
id = _interpreters.get_main()
5042
return Interpreter(id)
5143

5244

5345
class Interpreter:
54-
"""
55-
The Interpreter object represents
56-
a single interpreter.
57-
"""
46+
"""A single Python interpreter."""
5847

5948
def __init__(self, id, *, isolated=None):
49+
if not isinstance(id, (int, _interpreters.InterpreterID)):
50+
raise TypeError(f'id must be an int, got {id!r}')
6051
self._id = id
6152
self._isolated = isolated
6253

54+
def __repr__(self):
55+
data = dict(id=int(self._id), isolated=self._isolated)
56+
kwargs = (f'{k}={v!r}' for k, v in data.items())
57+
return f'{type(self).__name__}({", ".join(kwargs)})'
58+
59+
def __hash__(self):
60+
return hash(self._id)
61+
62+
def __eq__(self, other):
63+
if not isinstance(other, Interpreter):
64+
return NotImplemented
65+
else:
66+
return other._id == self._id
67+
6368
@property
6469
def id(self):
6570
return self._id
6671

6772
@property
6873
def isolated(self):
6974
if self._isolated is None:
75+
# XXX The low-level function has not been added yet.
76+
# See bpo-....
7077
self._isolated = _interpreters.is_isolated(self._id)
7178
return self._isolated
7279

7380
def is_running(self):
74-
"""
75-
Return whether or not the identified
76-
interpreter is running.
77-
"""
81+
"""Return whether or not the identified interpreter is running."""
7882
return _interpreters.is_running(self._id)
7983

8084
def close(self):
81-
"""
82-
Finalize and destroy the interpreter.
85+
"""Finalize and destroy the interpreter.
8386
84-
Attempting to destroy the current
85-
interpreter results in a RuntimeError.
87+
Attempting to destroy the current interpreter results
88+
in a RuntimeError.
8689
"""
8790
return _interpreters.destroy(self._id)
8891

8992
def run(self, src_str, /, *, channels=None):
90-
"""
91-
Run the given source code in the interpreter.
93+
"""Run the given source code in the interpreter.
94+
9295
This blocks the current Python thread until done.
9396
"""
94-
_interpreters.run_string(self._id, src_str)
97+
_interpreters.run_string(self._id, src_str, channels)
9598

9699

97100
def create_channel():
98-
"""
99-
Create a new channel for passing data between
100-
interpreters.
101-
"""
101+
"""Return (recv, send) for a new cross-interpreter channel.
102102
103+
The channel may be used to pass data safely between interpreters.
104+
"""
103105
cid = _interpreters.channel_create()
104-
return (RecvChannel(cid), SendChannel(cid))
106+
recv, send = RecvChannel(cid), SendChannel(cid)
107+
return recv, send
105108

106109

107110
def list_all_channels():
108-
"""
109-
Get all open channels.
110-
"""
111+
"""Return a list of (recv, send) for all open channels."""
111112
return [(RecvChannel(cid), SendChannel(cid))
112113
for cid in _interpreters.channel_list_all()]
113114

114115

116+
class _ChannelEnd:
117+
"""The base class for RecvChannel and SendChannel."""
118+
119+
def __init__(self, id):
120+
if not isinstance(id, (int, _interpreters.ChannelID)):
121+
raise TypeError(f'id must be an int, got {id!r}')
122+
self._id = id
123+
124+
def __repr__(self):
125+
return f'{type(self).__name__}(id={int(self._id)})'
126+
127+
def __hash__(self):
128+
return hash(self._id)
129+
130+
def __eq__(self, other):
131+
if isinstance(self, RecvChannel):
132+
if not isinstance(other, RecvChannel):
133+
return NotImplemented
134+
elif not isinstance(other, SendChannel):
135+
return NotImplemented
136+
return other._id == self._id
137+
138+
@property
139+
def id(self):
140+
return self._id
141+
142+
115143
_NOT_SET = object()
116144

117145

118-
class RecvChannel:
119-
"""
120-
The RecvChannel object represents
121-
a receiving channel.
122-
"""
146+
class RecvChannel(_ChannelEnd):
147+
"""The receiving end of a cross-interpreter channel."""
123148

124-
def __init__(self, id):
125-
self._id = id
149+
def recv(self, *, _sentinel=object(), _delay=10 / 1000): # 10 milliseconds
150+
"""Return the next object from the channel.
126151
127-
def recv(self, *, _delay=10 / 1000): # 10 milliseconds
128-
"""
129-
Get the next object from the channel,
130-
and wait if none have been sent.
131-
Associate the interpreter with the channel.
152+
This blocks until an object has been sent, if none have been
153+
sent already.
132154
"""
133-
import time
134-
sentinel = object()
135-
obj = _interpreters.channel_recv(self._id, sentinel)
136-
while obj is sentinel:
155+
obj = _interpreters.channel_recv(self._id, _sentinel)
156+
while obj is _sentinel:
137157
time.sleep(_delay)
138-
obj = _interpreters.channel_recv(self._id, sentinel)
158+
obj = _interpreters.channel_recv(self._id, _sentinel)
139159
return obj
140160

141161
def recv_nowait(self, default=_NOT_SET):
142-
"""
143-
Like recv(), but return the default
144-
instead of waiting.
162+
"""Return the next object from the channel.
145163
146-
This function is blocked by a missing low-level
147-
implementation of channel_recv_wait().
164+
If none have been sent then return the default if one
165+
is provided or fail with ChannelEmptyError. Otherwise this
166+
is the same as recv().
148167
"""
149168
if default is _NOT_SET:
150169
return _interpreters.channel_recv(self._id)
151170
else:
152171
return _interpreters.channel_recv(self._id, default)
153172

154173

155-
class SendChannel:
156-
"""
157-
The SendChannel object represents
158-
a sending channel.
159-
"""
160-
161-
def __init__(self, id):
162-
self._id = id
174+
class SendChannel(_ChannelEnd):
175+
"""The sending end of a cross-interpreter channel."""
163176

164177
def send(self, obj):
178+
"""Send the object (i.e. its data) to the channel's receiving end.
179+
180+
This blocks until the object is received.
165181
"""
166-
Send the object (i.e. its data) to the receiving
167-
end of the channel and wait. Associate the interpreter
168-
with the channel.
169-
"""
170-
import time
171182
_interpreters.channel_send(self._id, obj)
183+
# XXX We are missing a low-level channel_send_wait().
184+
# See bpo-32604 and gh-19829.
185+
# Until that shows up we fake it:
172186
time.sleep(2)
173187

174188
def send_nowait(self, obj):
175-
"""
176-
Like send(), but return False if not received.
189+
"""Send the object to the channel's receiving end.
177190
178-
This function is blocked by a missing low-level
179-
implementation of channel_send_wait().
191+
If the object is immediately received then return True
192+
(else False). Otherwise this is the same as send().
180193
"""
181-
182-
_interpreters.channel_send(self._id, obj)
183-
return False
194+
# XXX Note that at the moment channel_send() only ever returns
195+
# None. This should be fixed when channel_send_wait() is added.
196+
# See bpo-32604 and gh-19829.
197+
return _interpreters.channel_send(self._id, obj)

Lib/test/test__xxsubinterpreters.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -759,21 +759,9 @@ def test_still_running(self):
759759

760760
class RunStringTests(TestBase):
761761

762-
SCRIPT = dedent("""
763-
with open('{}', 'w') as out:
764-
out.write('{}')
765-
""")
766-
FILENAME = 'spam'
767-
768762
def setUp(self):
769763
super().setUp()
770764
self.id = interpreters.create()
771-
self._fs = None
772-
773-
def tearDown(self):
774-
if self._fs is not None:
775-
self._fs.close()
776-
super().tearDown()
777765

778766
def test_success(self):
779767
script, file = _captured_script('print("it worked!", end="")')

0 commit comments

Comments
 (0)