Skip to content

bpo-32604: Clean up test.support.interpreters. #20926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 93 additions & 79 deletions Lib/test/support/interpreters.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Subinterpreters High Level Module."""

import time
import _xxsubinterpreters as _interpreters

# aliases:
Expand All @@ -19,165 +20,178 @@


def create(*, isolated=True):
"""
Initialize a new (idle) Python interpreter.
"""
"""Return a new (idle) Python interpreter."""
id = _interpreters.create(isolated=isolated)
return Interpreter(id, isolated=isolated)


def list_all():
"""
Get all existing interpreters.
"""
return [Interpreter(id) for id in
_interpreters.list_all()]
"""Return all existing interpreters."""
return [Interpreter(id) for id in _interpreters.list_all()]


def get_current():
"""
Get the currently running interpreter.
"""
"""Return the currently running interpreter."""
id = _interpreters.get_current()
return Interpreter(id)


def get_main():
"""
Get the main interpreter.
"""
"""Return the main interpreter."""
id = _interpreters.get_main()
return Interpreter(id)


class Interpreter:
"""
The Interpreter object represents
a single interpreter.
"""
"""A single Python interpreter."""

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

def __repr__(self):
data = dict(id=int(self._id), isolated=self._isolated)
kwargs = (f'{k}={v!r}' for k, v in data.items())
return f'{type(self).__name__}({", ".join(kwargs)})'

def __hash__(self):
return hash(self._id)

def __eq__(self, other):
if not isinstance(other, Interpreter):
return NotImplemented
else:
return other._id == self._id

@property
def id(self):
return self._id

@property
def isolated(self):
if self._isolated is None:
# XXX The low-level function has not been added yet.
# See bpo-....
self._isolated = _interpreters.is_isolated(self._id)
return self._isolated

def is_running(self):
"""
Return whether or not the identified
interpreter is running.
"""
"""Return whether or not the identified interpreter is running."""
return _interpreters.is_running(self._id)

def close(self):
"""
Finalize and destroy the interpreter.
"""Finalize and destroy the interpreter.

Attempting to destroy the current
interpreter results in a RuntimeError.
Attempting to destroy the current interpreter results
in a RuntimeError.
"""
return _interpreters.destroy(self._id)

def run(self, src_str, /, *, channels=None):
"""
Run the given source code in the interpreter.
"""Run the given source code in the interpreter.

This blocks the current Python thread until done.
"""
_interpreters.run_string(self._id, src_str)
_interpreters.run_string(self._id, src_str, channels)


def create_channel():
"""
Create a new channel for passing data between
interpreters.
"""
"""Return (recv, send) for a new cross-interpreter channel.

The channel may be used to pass data safely between interpreters.
"""
cid = _interpreters.channel_create()
return (RecvChannel(cid), SendChannel(cid))
recv, send = RecvChannel(cid), SendChannel(cid)
return recv, send


def list_all_channels():
"""
Get all open channels.
"""
"""Return a list of (recv, send) for all open channels."""
return [(RecvChannel(cid), SendChannel(cid))
for cid in _interpreters.channel_list_all()]


class _ChannelEnd:
"""The base class for RecvChannel and SendChannel."""

def __init__(self, id):
if not isinstance(id, (int, _interpreters.ChannelID)):
raise TypeError(f'id must be an int, got {id!r}')
self._id = id

def __repr__(self):
return f'{type(self).__name__}(id={int(self._id)})'

def __hash__(self):
return hash(self._id)

def __eq__(self, other):
if isinstance(self, RecvChannel):
if not isinstance(other, RecvChannel):
return NotImplemented
elif not isinstance(other, SendChannel):
return NotImplemented
return other._id == self._id

@property
def id(self):
return self._id


_NOT_SET = object()


class RecvChannel:
"""
The RecvChannel object represents
a receiving channel.
"""
class RecvChannel(_ChannelEnd):
"""The receiving end of a cross-interpreter channel."""

def __init__(self, id):
self._id = id
def recv(self, *, _sentinel=object(), _delay=10 / 1000): # 10 milliseconds
"""Return the next object from the channel.

def recv(self, *, _delay=10 / 1000): # 10 milliseconds
"""
Get the next object from the channel,
and wait if none have been sent.
Associate the interpreter with the channel.
This blocks until an object has been sent, if none have been
sent already.
"""
import time
sentinel = object()
obj = _interpreters.channel_recv(self._id, sentinel)
while obj is sentinel:
obj = _interpreters.channel_recv(self._id, _sentinel)
while obj is _sentinel:
time.sleep(_delay)
obj = _interpreters.channel_recv(self._id, sentinel)
obj = _interpreters.channel_recv(self._id, _sentinel)
return obj

def recv_nowait(self, default=_NOT_SET):
"""
Like recv(), but return the default
instead of waiting.
"""Return the next object from the channel.

This function is blocked by a missing low-level
implementation of channel_recv_wait().
If none have been sent then return the default if one
is provided or fail with ChannelEmptyError. Otherwise this
is the same as recv().
"""
if default is _NOT_SET:
return _interpreters.channel_recv(self._id)
else:
return _interpreters.channel_recv(self._id, default)


class SendChannel:
"""
The SendChannel object represents
a sending channel.
"""

def __init__(self, id):
self._id = id
class SendChannel(_ChannelEnd):
"""The sending end of a cross-interpreter channel."""

def send(self, obj):
"""Send the object (i.e. its data) to the channel's receiving end.

This blocks until the object is received.
"""
Send the object (i.e. its data) to the receiving
end of the channel and wait. Associate the interpreter
with the channel.
"""
import time
_interpreters.channel_send(self._id, obj)
# XXX We are missing a low-level channel_send_wait().
# See bpo-32604 and gh-19829.
# Until that shows up we fake it:
time.sleep(2)

def send_nowait(self, obj):
"""
Like send(), but return False if not received.
"""Send the object to the channel's receiving end.

This function is blocked by a missing low-level
implementation of channel_send_wait().
If the object is immediately received then return True
(else False). Otherwise this is the same as send().
"""

_interpreters.channel_send(self._id, obj)
return False
# XXX Note that at the moment channel_send() only ever returns
# None. This should be fixed when channel_send_wait() is added.
# See bpo-32604 and gh-19829.
return _interpreters.channel_send(self._id, obj)
12 changes: 0 additions & 12 deletions Lib/test/test__xxsubinterpreters.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,21 +759,9 @@ def test_still_running(self):

class RunStringTests(TestBase):

SCRIPT = dedent("""
with open('{}', 'w') as out:
out.write('{}')
""")
FILENAME = 'spam'

def setUp(self):
super().setUp()
self.id = interpreters.create()
self._fs = None

def tearDown(self):
if self._fs is not None:
self._fs.close()
super().tearDown()

def test_success(self):
script, file = _captured_script('print("it worked!", end="")')
Expand Down
Loading