Content-Length: 72978 | pFad | http://github.com/python/cpython/pull/20611.patch
thub.com
From 02086f767dd1f5cafb9d9de12b5f17023ea8e0e9 Mon Sep 17 00:00:00 2001
From: nanjekyejoannah
Date: Thu, 7 May 2020 18:51:49 -0300
Subject: [PATCH 01/15] PEP 554 for use in test suite
---
Doc/library/_interpreters.rst | 147 ++++++++
Lib/test/support/_interpreters.py | 235 ++++++++++++
Lib/test/test_interpreters.py | 569 +++++++++++++++++++++++++++++
Modules/_xxsubinterpretersmodule.c | 23 ++
4 files changed, 974 insertions(+)
create mode 100644 Doc/library/_interpreters.rst
create mode 100644 Lib/test/support/_interpreters.py
create mode 100644 Lib/test/test_interpreters.py
diff --git a/Doc/library/_interpreters.rst b/Doc/library/_interpreters.rst
new file mode 100644
index 00000000000000..f1840450799830
--- /dev/null
+++ b/Doc/library/_interpreters.rst
@@ -0,0 +1,147 @@
+:mod:`_interpreters` --- High-level Subinterpreters Module
+==========================================================
+
+.. module:: _interpreters
+ :synopsis: High-level Subinterpreters Module.
+
+**Source code:** :source:`Lib/test/support/_interpreters.py`
+
+--------------
+
+This module provides high-level tools for working with sub-interpreters,
+such as creating them, running code in them, or sending data between them.
+It is a wrapper around the low-level `_xxsubinterpreters` module.
+
+.. versionchanged:: added in 3.9
+
+Interpreter Objects
+-------------------
+
+The Interpreter object represents a single interpreter.
+.. class:: Interpreter(id)
+
+ The class implementing a subinterpreter object.
+
+ .. method:: is_running()
+
+ Return `True` if the identified interpreter is running.
+
+ .. method:: close()
+
+ Destroy the interpreter. Attempting to destroy the current
+ interpreter results in a `RuntimeError`.
+
+ .. method:: run(self, src_str, /, *, channels=None):
+
+ Run the given source code in the interpreter. This blocks
+ the current thread until done. `channels` should be in
+ the form : `(RecvChannel, SendChannel)`.
+
+RecvChannel Objects
+-------------------
+
+The RecvChannel object represents a recieving channel.
+
+.. class:: RecvChannel(id)
+
+ This class represents the receiving end of a channel.
+
+ .. method:: recv()
+
+ Get the next object from the channel, and wait if
+ none have been sent. Associate the interpreter
+ with the channel.
+
+ .. method:: recv_nowait(default=None)
+
+ Like ``recv()``, but return the default result
+ instead of waiting.
+
+
+SendChannel Objects
+--------------------
+
+The ``SendChannel`` object represents a sending channel.
+
+.. class:: SendChannel(id)
+
+ This class represents the sending end of a channel.
+
+ .. method:: send(obj)
+
+ Send the object ``obj`` to the receiving end of the channel
+ and wait. Associate the interpreter with the channel.
+
+ .. method:: send_nowait(obj)
+
+ Similar to ``send()``, but returns ``False`` if
+ *obj* is not immediately received instead of blocking.
+
+
+This module defines the following global functions:
+
+
+.. function:: is_shareable(obj)
+
+ Return ``True`` if the object's data can be shared between
+ interpreters.
+
+.. function:: create_channel()
+
+ Create a new channel for passing data between interpreters.
+
+.. function:: list_all_channels()
+
+ Return all open channels.
+
+.. function:: create(*, isolated=True)
+
+ Initialize a new (idle) Python interpreter. Get the currently
+ running interpreter. This method returns an ``Interpreter`` object.
+
+.. function:: get_current()
+
+ Get the currently running interpreter. This method returns
+ an ``Interpreter`` object.
+
+.. function:: get_main()
+
+ Get the main interpreter. This method returns
+ an ``Interpreter`` object.
+
+.. function:: list_all()
+
+ Get all existing interpreters. Returns a list
+ of ``Interpreter`` objects.
+
+This module also defines the following exceptions.
+
+.. exception:: RunFailedError
+
+ This exception, a subclass of :exc:`RuntimeError`, is raised when the
+ ``Interpreter.run()`` results in an uncaught exception.
+
+.. exception:: ChannelError
+
+ This exception is a subclass of :exc:`Exception`, and is the base
+ class for all channel-related exceptions.
+
+.. exception:: ChannelNotFoundError
+
+ This exception is a subclass of :exc:`ChannelError`, and is raised
+ when the the identified channel is not found.
+
+.. exception:: ChannelEmptyError
+
+ This exception is a subclass of :exc:`ChannelError`, and is raised when
+ the channel is unexpectedly empty.
+
+.. exception:: ChannelNotEmptyError
+
+ This exception is a subclass of :exc:`ChannelError`, and is raised when
+ the channel is unexpectedly not empty.
+
+.. exception:: NotReceivedError
+
+ This exception is a subclass of :exc:`ChannelError`, and is raised when
+ nothing was waiting to receive a sent object.
diff --git a/Lib/test/support/_interpreters.py b/Lib/test/support/_interpreters.py
new file mode 100644
index 00000000000000..865996fb7405d3
--- /dev/null
+++ b/Lib/test/support/_interpreters.py
@@ -0,0 +1,235 @@
+"""Subinterpreters High Level Module."""
+
+import logging
+import _xxsubinterpreters as _interpreters
+
+__all__ = ['Interpreter', 'SendChannel', 'RecvChannel',
+ 'is_shareable', 'create_channel',
+ 'list_all_channels', 'get_current',
+ 'get_current', 'create']
+
+
+def create(*, isolated=True):
+ """ create() -> Interpreter
+
+ Initialize a new (idle) Python interpreter.
+ """
+ id = _interpreters.create()
+ return Interpreter(id)
+
+def list_all():
+ """ list_all() -> [Interpreter]
+
+ Get all existing interpreters.
+ """
+ return [Interpreter(id) for id in
+ _interpreters.list_all()]
+
+def get_current():
+ """ get_current() -> Interpreter
+
+ Get the currently running interpreter.
+ """
+ id = _interpreters.get_current()
+ return Interpreter(id)
+
+def get_main():
+ """ get_main() -> Interpreter
+
+ Get the main interpreter.
+ """
+ id = _interpreters.get_main()
+ return Interpreter(id)
+
+
+class Interpreter:
+
+ def __init__(self, id):
+ self._id = id
+
+ @property
+ def id(self):
+ return self._id
+
+ def is_running(self):
+ """is_running() -> bool
+
+ Return whether or not the identified
+ interpreter is running.
+ """
+ return _interpreters.is_running(self._id)
+
+ def close(self):
+ """close()
+
+ Finalize and destroy the interpreter.
+
+ Attempting to destroy the current
+ interpreter results in a RuntimeError.
+ """
+ return _interpreters.destroy(self._id)
+
+ def run(self, src_str, /, *, channels=None):
+ """run(src_str, /, *, channels=None)
+
+ Run the given source code in the interpreter.
+ This blocks the current thread until done.
+ """
+ try:
+ _interpreters.run_string(self._id, src_str)
+ except RunFailedError as err:
+ logger.error(err)
+ raise
+
+
+def is_shareable(obj):
+ """ is_shareable(obj) -> Bool
+
+ Return `True` if the object's data can be
+ shared between interpreters and `False` otherwise.
+ """
+ return _interpreters.is_shareable(obj)
+
+def create_channel():
+ """ create_channel() -> (RecvChannel, SendChannel)
+
+ Create a new channel for passing data between
+ interpreters.
+ """
+
+ cid = _interpreters.channel_create()
+ return (RecvChannel(cid), SendChannel(cid))
+
+def list_all_channels():
+ """ list_all_channels() -> [(RecvChannel, SendChannel)]
+
+ Get all open channels.
+ """
+ return [(RecvChannel(cid), SendChannel(cid))
+ for cid in _interpreters.channel_list_all()]
+
+def wait(timeout):
+ #The implementation for wait
+ # will be non trivial to be useful
+ import time
+ time.sleep(timeout)
+
+class RecvChannel:
+
+ def __init__(self, id):
+ self.id = id
+
+ def recv(self):
+ """ channel_recv() -> obj
+
+ Get the next object from the channel,
+ and wait if none have been sent.
+ Associate the interpreter with the channel.
+ """
+ try:
+ obj = _interpreters.channel_recv(self.id)
+ if obj == None:
+ wait(2)
+ obj = _interpreters.channel_recv(self.id)
+ except _interpreters.ChannelEmptyError:
+ raise ChannelEmptyError
+ except _interpreters.ChannelNotFoundError:
+ raise ChannelNotFoundError
+ except _interpreters.ChannelClosedError:
+ raise ChannelClosedError
+ except _interpreters.RunFailedError:
+ raise RunFailedError
+ return obj
+
+ def recv_nowait(self, default=None):
+ """recv_nowait(default=None) -> object
+
+ Like recv(), but return the default
+ instead of waiting.
+ """
+ try:
+ obj = _interpreters.channel_recv(self.id)
+ except _interpreters.ChannelEmptyError:
+ raise ChannelEmptyError
+ except _interpreters.ChannelNotFoundError:
+ raise ChannelNotFoundError
+ except _interpreters.ChannelClosedError:
+ raise ChannelClosedError
+ except _interpreters.RunFailedError:
+ raise RunFailedError
+ return obj
+
+
+class SendChannel:
+
+ def __init__(self, id):
+ self.id = id
+
+ def send(self, obj):
+ """ send(obj)
+
+ Send the object (i.e. its data) to the receiving
+ end of the channel and wait. Associate the interpreter
+ with the channel.
+ """
+ try:
+ _interpreters.channel_send(self.id, obj)
+ wait(2)
+ except _interpreters.ChannelNotFoundError:
+ raise ChannelNotFoundError
+ except _interpreters.ChannelClosedError:
+ raise ChannelClosedError
+ except _interpreters.RunFailedError:
+ raise RunFailedError
+
+ def send_nowait(self, obj):
+ """ send_nowait(obj)
+
+ Like send(), but return False if not received.
+ """
+ try:
+ _interpreters.channel_send(self.id, obj)
+ except _interpreters.ChannelNotFoundError:
+ raise ChannelNotFoundError
+ except _interpreters.ChannelClosedError:
+ raise ChannelClosedError
+ except _interpreters.RunFailedError:
+ raise RunFailedError
+
+ recv_obj = _interpreters.channel_recv(self.id)
+ if recv_obj:
+ return obj
+ else:
+ return False
+
+
+class ChannelError(Exception):
+ pass
+
+
+class ChannelNotFoundError(ChannelError):
+ pass
+
+
+class ChannelEmptyError(ChannelError):
+ pass
+
+
+class ChannelNotEmptyError(ChannelError):
+ pass
+
+
+class NotReceivedError(ChannelError):
+ pass
+
+
+class ChannelClosedError(ChannelError):
+ pass
+
+
+class ChannelReleasedError(ChannelClosedError):
+ pass
+
+
+class RunFailedError(RuntimeError):
+ pass
\ No newline at end of file
diff --git a/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py
new file mode 100644
index 00000000000000..db658202659bbf
--- /dev/null
+++ b/Lib/test/test_interpreters.py
@@ -0,0 +1,569 @@
+import contextlib
+import os
+import threading
+from textwrap import dedent
+import unittest
+import time
+
+import _xxsubinterpreters as _interpreters
+from test.support import _interpreters as interpreters
+
+def _captured_script(script):
+ r, w = os.pipe()
+ indented = script.replace('\n', '\n ')
+ wrapped = dedent(f"""
+ import contextlib
+ with open({w}, 'w') as spipe:
+ with contextlib.redirect_stdout(spipe):
+ {indented}
+ """)
+ return wrapped, open(r)
+
+def clean_up_interpreters():
+ for interp in interpreters.list_all():
+ if interp.id == 0: # main
+ continue
+ try:
+ interp.close()
+ except RuntimeError:
+ pass # already destroyed
+
+def _run_output(interp, request, shared=None):
+ script, rpipe = _captured_script(request)
+ with rpipe:
+ interp.run(script)
+ return rpipe.read()
+
+@contextlib.contextmanager
+def _running(interp):
+ r, w = os.pipe()
+ def run():
+ interp.run(dedent(f"""
+ # wait for "signal"
+ with open({r}) as rpipe:
+ rpipe.read()
+ """))
+
+ t = threading.Thread(target=run)
+ t.start()
+
+ yield
+
+ with open(w, 'w') as spipe:
+ spipe.write('done')
+ t.join()
+
+
+class TestBase(unittest.TestCase):
+
+ def tearDown(self):
+ clean_up_interpreters()
+
+
+class CreateTests(TestBase):
+
+ def test_in_main(self):
+ interp = interpreters.create()
+ lst = interpreters.list_all()
+ self.assertEqual(interp.id, lst[1].id)
+
+ def test_in_thread(self):
+ lock = threading.Lock()
+ id = None
+ interp = interpreters.create()
+ lst = interpreters.list_all()
+ def f():
+ nonlocal id
+ id = interp.id
+ lock.acquire()
+ lock.release()
+
+ t = threading.Thread(target=f)
+ with lock:
+ t.start()
+ t.join()
+ self.assertEqual(interp.id, lst[1].id)
+
+ def test_in_subinterpreter(self):
+ main, = interpreters.list_all()
+ interp = interpreters.create()
+ out = _run_output(interp, dedent("""
+ from test.support import _interpreters as interpreters
+ interp = interpreters.create()
+ print(interp)
+ """))
+ interp2 = out.strip()
+
+ self.assertEqual(len(set(interpreters.list_all())), len({main, interp, interp2}))
+
+ def test_in_threaded_subinterpreter(self):
+ main, = interpreters.list_all()
+ interp = interpreters.create()
+ interp2 = None
+ def f():
+ nonlocal interp2
+ out = _run_output(interp, dedent("""
+ from test.support import _interpreters as interpreters
+ interp = interpreters.create()
+ print(interp)
+ """))
+ interp2 = int(out.strip())
+
+ t = threading.Thread(target=f)
+ t.start()
+ t.join()
+
+ self.assertEqual(len(set(interpreters.list_all())), len({main, interp, interp2}))
+
+ def test_after_destroy_all(self):
+ before = set(interpreters.list_all())
+ # Create 3 subinterpreters.
+ interp_lst = []
+ for _ in range(3):
+ interps = interpreters.create()
+ interp_lst.append(interps)
+ # Now destroy them.
+ for interp in interp_lst:
+ interp.close()
+ # Finally, create another.
+ interp = interpreters.create()
+ self.assertEqual(len(set(interpreters.list_all())), len(before | {interp}))
+
+ def test_after_destroy_some(self):
+ before = set(interpreters.list_all())
+ # Create 3 subinterpreters.
+ interp1 = interpreters.create()
+ interp2 = interpreters.create()
+ interp3 = interpreters.create()
+ # Now destroy 2 of them.
+ interp1.close()
+ interp2.close()
+ # Finally, create another.
+ interp = interpreters.create()
+ self.assertEqual(len(set(interpreters.list_all())), len(before | {interp3, interp}))
+
+
+class GetCurrentTests(TestBase):
+
+ def test_main(self):
+ main_interp_id = _interpreters.get_main()
+ cur_interp_id = interpreters.get_current().id
+ self.assertEqual(cur_interp_id, main_interp_id)
+
+ def test_subinterpreter(self):
+ main = _interpreters.get_main()
+ interp = interpreters.create()
+ out = _run_output(interp, dedent("""
+ from test.support import _interpreters as interpreters
+ cur = interpreters.get_current()
+ print(cur)
+ """))
+ cur = out.strip()
+ self.assertNotEqual(cur, main)
+
+
+class ListAllTests(TestBase):
+
+ def test_initial(self):
+ interps = interpreters.list_all()
+ self.assertEqual(1, len(interps))
+
+ def test_after_creating(self):
+ main = interpreters.get_current()
+ first = interpreters.create()
+ second = interpreters.create()
+
+ ids = []
+ for interp in interpreters.list_all():
+ ids.append(interp.id)
+
+ self.assertEqual(ids, [main.id, first.id, second.id])
+
+ def test_after_destroying(self):
+ main = interpreters.get_current()
+ first = interpreters.create()
+ second = interpreters.create()
+ first.close()
+
+ ids = []
+ for interp in interpreters.list_all():
+ ids.append(interp.id)
+
+ self.assertEqual(ids, [main.id, second.id])
+
+
+class TestInterpreterId(TestBase):
+
+ def test_in_main(self):
+ main = interpreters.get_current()
+ self.assertEqual(0, main.id)
+
+ def test_with_custom_num(self):
+ interp = interpreters.Interpreter(1)
+ self.assertEqual(1, interp.id)
+
+ def test_for_readonly_property(self):
+ interp = interpreters.Interpreter(1)
+ with self.assertRaises(AttributeError):
+ interp.id = 2
+
+
+class TestInterpreterIsRunning(TestBase):
+
+ def test_main(self):
+ main = interpreters.get_current()
+ self.assertTrue(main.is_running())
+
+ def test_subinterpreter(self):
+ interp = interpreters.create()
+ self.assertFalse(interp.is_running())
+
+ with _running(interp):
+ self.assertTrue(interp.is_running())
+ self.assertFalse(interp.is_running())
+
+ def test_from_subinterpreter(self):
+ interp = interpreters.create()
+ out = _run_output(interp, dedent(f"""
+ import _xxsubinterpreters as _interpreters
+ if _interpreters.is_running({interp.id}):
+ print(True)
+ else:
+ print(False)
+ """))
+ self.assertEqual(out.strip(), 'True')
+
+ def test_already_destroyed(self):
+ interp = interpreters.create()
+ interp.close()
+ with self.assertRaises(RuntimeError):
+ interp.is_running()
+
+
+class TestInterpreterDestroy(TestBase):
+
+ def test_basic(self):
+ interp1 = interpreters.create()
+ interp2 = interpreters.create()
+ interp3 = interpreters.create()
+ self.assertEqual(4, len(interpreters.list_all()))
+ interp2.close()
+ self.assertEqual(3, len(interpreters.list_all()))
+
+ def test_all(self):
+ before = set(interpreters.list_all())
+ interps = set()
+ for _ in range(3):
+ interp = interpreters.create()
+ interps.add(interp)
+ self.assertEqual(len(set(interpreters.list_all())), len(before | interps))
+ for interp in interps:
+ interp.close()
+ self.assertEqual(len(set(interpreters.list_all())), len(before))
+
+ def test_main(self):
+ main, = interpreters.list_all()
+ with self.assertRaises(RuntimeError):
+ main.close()
+
+ def f():
+ with self.assertRaises(RuntimeError):
+ main.close()
+
+ t = threading.Thread(target=f)
+ t.start()
+ t.join()
+
+ def test_already_destroyed(self):
+ interp = interpreters.create()
+ interp.close()
+ with self.assertRaises(RuntimeError):
+ interp.close()
+
+ def test_from_current(self):
+ main, = interpreters.list_all()
+ interp = interpreters.create()
+ script = dedent(f"""
+ from test.support import _interpreters as interpreters
+ try:
+ main = interpreters.get_current()
+ main.close()
+ except RuntimeError:
+ pass
+ """)
+
+ interp.run(script)
+ self.assertEqual(len(set(interpreters.list_all())), len({main, interp}))
+
+ def test_from_sibling(self):
+ main, = interpreters.list_all()
+ interp1 = interpreters.create()
+ script = dedent(f"""
+ from test.support import _interpreters as interpreters
+ interp2 = interpreters.create()
+ interp2.close()
+ """)
+ interp1.run(script)
+
+ self.assertEqual(len(set(interpreters.list_all())), len({main, interp1}))
+
+ def test_from_other_thread(self):
+ interp = interpreters.create()
+ def f():
+ interp.close()
+
+ t = threading.Thread(target=f)
+ t.start()
+ t.join()
+
+ def test_still_running(self):
+ main, = interpreters.list_all()
+ interp = interpreters.create()
+ with _running(interp):
+ with self.assertRaises(RuntimeError):
+ interp.close()
+ self.assertTrue(interp.is_running())
+
+
+class TestInterpreterRun(TestBase):
+
+ SCRIPT = dedent("""
+ with open('{}', 'w') as out:
+ out.write('{}')
+ """)
+ FILENAME = 'spam'
+
+ def setUp(self):
+ super().setUp()
+ self.interp = interpreters.create()
+ self._fs = None
+
+ def tearDown(self):
+ if self._fs is not None:
+ self._fs.close()
+ super().tearDown()
+
+ @property
+ def fs(self):
+ if self._fs is None:
+ self._fs = FSFixture(self)
+ return self._fs
+
+ def test_success(self):
+ script, file = _captured_script('print("it worked!", end="")')
+ with file:
+ self.interp.run(script)
+ out = file.read()
+
+ self.assertEqual(out, 'it worked!')
+
+ def test_in_thread(self):
+ script, file = _captured_script('print("it worked!", end="")')
+ with file:
+ def f():
+ self.interp.run(script)
+
+ t = threading.Thread(target=f)
+ t.start()
+ t.join()
+ out = file.read()
+
+ self.assertEqual(out, 'it worked!')
+
+ @unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
+ def test_fork(self):
+ import tempfile
+ with tempfile.NamedTemporaryFile('w+') as file:
+ file.write('')
+ file.flush()
+
+ expected = 'spam spam spam spam spam'
+ script = dedent(f"""
+ import os
+ try:
+ os.fork()
+ except RuntimeError:
+ with open('{file.name}', 'w') as out:
+ out.write('{expected}')
+ """)
+ self.interp.run(script)
+
+ file.seek(0)
+ content = file.read()
+ self.assertEqual(content, expected)
+
+ def test_already_running(self):
+ with _running(self.interp):
+ with self.assertRaises(RuntimeError):
+ self.interp.run('print("spam")')
+
+ def test_bad_script(self):
+ with self.assertRaises(TypeError):
+ self.interp.run(10)
+
+ def test_bytes_for_script(self):
+ with self.assertRaises(TypeError):
+ self.interp.run(b'print("spam")')
+
+
+class TestIsShareable(TestBase):
+
+ def test_default_shareables(self):
+ shareables = [
+ # singletons
+ None,
+ # builtin objects
+ b'spam',
+ 'spam',
+ 10,
+ -10,
+ ]
+ for obj in shareables:
+ with self.subTest(obj):
+ self.assertTrue(
+ interpreters.is_shareable(obj))
+
+ def test_not_shareable(self):
+ class Cheese:
+ def __init__(self, name):
+ self.name = name
+ def __str__(self):
+ return self.name
+
+ class SubBytes(bytes):
+ """A subclass of a shareable type."""
+
+ not_shareables = [
+ # singletons
+ True,
+ False,
+ NotImplemented,
+ ...,
+ # builtin types and objects
+ type,
+ object,
+ object(),
+ Exception(),
+ 100.0,
+ # user-defined types and objects
+ Cheese,
+ Cheese('Wensleydale'),
+ SubBytes(b'spam'),
+ ]
+ for obj in not_shareables:
+ with self.subTest(repr(obj)):
+ self.assertFalse(
+ interpreters.is_shareable(obj))
+
+
+class TestChannel(TestBase):
+
+ def test_create_cid(self):
+ r, s = interpreters.create_channel()
+ self.assertIsInstance(r, interpreters.RecvChannel)
+ self.assertIsInstance(s, interpreters.SendChannel)
+
+ def test_sequential_ids(self):
+ before = interpreters.list_all_channels()
+ channels1 = interpreters.create_channel()
+ channels2 = interpreters.create_channel()
+ channels3 = interpreters.create_channel()
+ after = interpreters.list_all_channels()
+
+ self.assertEqual(len(set(after) - set(before)),
+ len({channels1, channels2, channels3}))
+
+class TestSendRecv(TestBase):
+
+ def test_send_recv_main(self):
+ r, s = interpreters.create_channel()
+ orig = b'spam'
+ s.send(orig)
+ obj = r.recv()
+
+ self.assertEqual(obj, orig)
+ self.assertIsNot(obj, orig)
+
+ def test_send_recv_same_interpreter(self):
+ interp = interpreters.create()
+ out = _run_output(interp, dedent("""
+ from test.support import _interpreters as interpreters
+ r, s = interpreters.create_channel()
+ orig = b'spam'
+ s.send(orig)
+ obj = r.recv()
+ assert obj is not orig
+ assert obj == orig
+ """))
+
+ def test_send_recv_different_threads(self):
+ r, s = interpreters.create_channel()
+
+ def f():
+ while True:
+ try:
+ obj = r.recv()
+ break
+ except interpreters.ChannelEmptyError:
+ time.sleep(0.1)
+ s.send(obj)
+ t = threading.Thread(target=f)
+ t.start()
+
+ s.send(b'spam')
+ t.join()
+ obj = r.recv()
+
+ self.assertEqual(obj, b'spam')
+
+ def test_recv_empty(self):
+ r, s = interpreters.create_channel()
+ with self.assertRaises(interpreters.ChannelEmptyError):
+ r.recv()
+
+ def test_send_recv_nowait_main(self):
+ r, s = interpreters.create_channel()
+ orig = b'spam'
+ s.send(orig)
+ obj = r.recv_nowait()
+
+ self.assertEqual(obj, orig)
+ self.assertIsNot(obj, orig)
+
+ def test_send_recv_nowait_same_interpreter(self):
+ interp = interpreters.create()
+ out = _run_output(interp, dedent("""
+ from test.support import _interpreters as interpreters
+ r, s = interpreters.create_channel()
+ orig = b'spam'
+ s.send(orig)
+ obj = r.recv_nowait()
+ assert obj is not orig
+ assert obj == orig
+ """))
+
+ def test_send_recv_nowait_different_threads(self):
+ r, s = interpreters.create_channel()
+
+ def f():
+ while True:
+ try:
+ obj = r.recv_nowait()
+ break
+ except interpreters.ChannelEmptyError:
+ time.sleep(0.1)
+ s.send(obj)
+ t = threading.Thread(target=f)
+ t.start()
+
+ s.send(b'spam')
+ t.join()
+ obj = r.recv_nowait()
+
+ self.assertEqual(obj, b'spam')
+
+ def test_recv_nowait_empty(self):
+ r, s = interpreters.create_channel()
+ with self.assertRaises(interpreters.ChannelEmptyError):
+ r.recv_nowait()
+
\ No newline at end of file
diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c
index 9c5df16e156a1d..74a665eba8b741 100644
--- a/Modules/_xxsubinterpretersmodule.c
+++ b/Modules/_xxsubinterpretersmodule.c
@@ -1167,6 +1167,8 @@ static PyObject *ChannelNotFoundError;
static PyObject *ChannelClosedError;
static PyObject *ChannelEmptyError;
static PyObject *ChannelNotEmptyError;
+static PyObject *ChannelReleasedError;
+static PyObject *NotReceivedError;
static int
channel_exceptions_init(PyObject *ns)
@@ -1203,6 +1205,27 @@ channel_exceptions_init(PyObject *ns)
return -1;
}
+ // An operation tried to use a released channel.
+ ChannelReleasedError = PyErr_NewException(
+ "_interpreters.ChannelReleasedError", ChannelClosedError, NULL);
+ if (ChannelReleasedError == NULL) {
+ return -1;
+ }
+ if (PyDict_SetItemString(ns, "ChannelReleasedError", ChannelReleasedError) != 0) {
+ return -1;
+ }
+
+ // An operation trying to send an object when Nothing was waiting
+ // to receive it
+ NotReceivedError = PyErr_NewException(
+ "_interpreters.NotReceivedError", ChannelError, NULL);
+ if (NotReceivedError == NULL) {
+ return -1;
+ }
+ if (PyDict_SetItemString(ns, "NotReceivedError", NotReceivedError) != 0) {
+ return -1;
+ }
+
// An operation tried to pop from an empty channel.
ChannelEmptyError = PyErr_NewException(
"_xxsubinterpreters.ChannelEmptyError", ChannelError, NULL);
From b2fda3c22b0970733b3064eaadf43e0f78c81508 Mon Sep 17 00:00:00 2001
From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com>
Date: Thu, 7 May 2020 22:00:17 +0000
Subject: [PATCH 02/15] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?=
=?UTF-8?q?lurb=5Fit.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../next/Library/2020-05-07-22-00-12.bpo-39881.E1xsNv.rst | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 Misc/NEWS.d/next/Library/2020-05-07-22-00-12.bpo-39881.E1xsNv.rst
diff --git a/Misc/NEWS.d/next/Library/2020-05-07-22-00-12.bpo-39881.E1xsNv.rst b/Misc/NEWS.d/next/Library/2020-05-07-22-00-12.bpo-39881.E1xsNv.rst
new file mode 100644
index 00000000000000..1129cd7649b96a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-05-07-22-00-12.bpo-39881.E1xsNv.rst
@@ -0,0 +1,2 @@
+PEP 554 for use in the test suite.
+(Patch By Joannah Nanjekye)
\ No newline at end of file
From 36676262a370a9b836fd814d7bf89b7cddd2bb09 Mon Sep 17 00:00:00 2001
From: nanjekyejoannah
Date: Thu, 7 May 2020 19:09:20 -0300
Subject: [PATCH 03/15] Fix space
---
Lib/test/support/_interpreters.py | 2 +-
Lib/test/test_interpreters.py | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/Lib/test/support/_interpreters.py b/Lib/test/support/_interpreters.py
index 865996fb7405d3..8fbf7e484baaf1 100644
--- a/Lib/test/support/_interpreters.py
+++ b/Lib/test/support/_interpreters.py
@@ -232,4 +232,4 @@ class ChannelReleasedError(ChannelClosedError):
class RunFailedError(RuntimeError):
- pass
\ No newline at end of file
+ pass
diff --git a/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py
index db658202659bbf..663b4bbaab1a88 100644
--- a/Lib/test/test_interpreters.py
+++ b/Lib/test/test_interpreters.py
@@ -566,4 +566,3 @@ def test_recv_nowait_empty(self):
r, s = interpreters.create_channel()
with self.assertRaises(interpreters.ChannelEmptyError):
r.recv_nowait()
-
\ No newline at end of file
From cb594ddfdfed939392b41a0c1de3917234fdb437 Mon Sep 17 00:00:00 2001
From: nanjekyejoannah
Date: Thu, 7 May 2020 19:31:36 -0300
Subject: [PATCH 04/15] Add doc to doc tree
---
Doc/library/python.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/Doc/library/python.rst b/Doc/library/python.rst
index f39613f572884f..6aea467dd3d2ec 100644
--- a/Doc/library/python.rst
+++ b/Doc/library/python.rst
@@ -25,3 +25,4 @@ overview:
gc.rst
inspect.rst
site.rst
+ _interpreters.rst
From 0448a8a71f9c6a7eda1ed06e8ed89bc18923bd9a Mon Sep 17 00:00:00 2001
From: nanjekyejoannah
Date: Thu, 7 May 2020 19:40:20 -0300
Subject: [PATCH 05/15] Move to modules doc tree
---
Doc/library/index.rst | 1 +
Doc/library/python.rst | 1 -
2 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/Doc/library/index.rst b/Doc/library/index.rst
index bebf7429b0e63e..d724238be7893c 100644
--- a/Doc/library/index.rst
+++ b/Doc/library/index.rst
@@ -77,3 +77,4 @@ the `Python Package Index `_.
unix.rst
superseded.rst
undoc.rst
+ _interpreters.rst
diff --git a/Doc/library/python.rst b/Doc/library/python.rst
index 6aea467dd3d2ec..f39613f572884f 100644
--- a/Doc/library/python.rst
+++ b/Doc/library/python.rst
@@ -25,4 +25,3 @@ overview:
gc.rst
inspect.rst
site.rst
- _interpreters.rst
From 85fde9f160acedba39a54ac059f41d05a7ca3a92 Mon Sep 17 00:00:00 2001
From: nanjekyejoannah
Date: Thu, 7 May 2020 20:04:35 -0300
Subject: [PATCH 06/15] Fix suspicious doc errors
---
Doc/library/_interpreters.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/Doc/library/_interpreters.rst b/Doc/library/_interpreters.rst
index f1840450799830..cc17fbe9292122 100644
--- a/Doc/library/_interpreters.rst
+++ b/Doc/library/_interpreters.rst
@@ -18,6 +18,7 @@ Interpreter Objects
-------------------
The Interpreter object represents a single interpreter.
+
.. class:: Interpreter(id)
The class implementing a subinterpreter object.
From f6af61c7ac8af86c934689c4c805acde89190974 Mon Sep 17 00:00:00 2001
From: nanjekyejoannah
Date: Thu, 7 May 2020 20:23:15 -0300
Subject: [PATCH 07/15] Fix test__all
---
Lib/test/support/_interpreters.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Lib/test/support/_interpreters.py b/Lib/test/support/_interpreters.py
index 8fbf7e484baaf1..93780ec08da94a 100644
--- a/Lib/test/support/_interpreters.py
+++ b/Lib/test/support/_interpreters.py
@@ -6,7 +6,7 @@
__all__ = ['Interpreter', 'SendChannel', 'RecvChannel',
'is_shareable', 'create_channel',
'list_all_channels', 'get_current',
- 'get_current', 'create']
+ 'get_main', 'create']
def create(*, isolated=True):
From 0decbeefeabef7f9167052133c89d6e6dc4a2ef4 Mon Sep 17 00:00:00 2001
From: nanjekyejoannah
Date: Thu, 7 May 2020 20:48:37 -0300
Subject: [PATCH 08/15] Docs docs docs
---
Doc/library/_interpreters.rst | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Doc/library/_interpreters.rst b/Doc/library/_interpreters.rst
index cc17fbe9292122..771030f0301300 100644
--- a/Doc/library/_interpreters.rst
+++ b/Doc/library/_interpreters.rst
@@ -10,7 +10,7 @@
This module provides high-level tools for working with sub-interpreters,
such as creating them, running code in them, or sending data between them.
-It is a wrapper around the low-level `_xxsubinterpreters` module.
+It is a wrapper around the low-level ``__xxsubinterpreters`` module.
.. versionchanged:: added in 3.9
@@ -25,7 +25,7 @@ The Interpreter object represents a single interpreter.
.. method:: is_running()
- Return `True` if the identified interpreter is running.
+ Return ``True`` if the identified interpreter is running.
.. method:: close()
@@ -35,7 +35,7 @@ The Interpreter object represents a single interpreter.
.. method:: run(self, src_str, /, *, channels=None):
Run the given source code in the interpreter. This blocks
- the current thread until done. `channels` should be in
+ the current thread until done. ``channels`` should be in
the form : `(RecvChannel, SendChannel)`.
RecvChannel Objects
From 3b35e1b08adfcc9a656009ed2e771241fb476ded Mon Sep 17 00:00:00 2001
From: Joannah Nanjekye
Date: Fri, 15 May 2020 18:04:17 +0000
Subject: [PATCH 09/15] Support isolated and fix wait
---
Doc/library/index.rst | 1 -
Lib/test/support/_interpreters.py | 235 ------------------
Lib/test/support/interpreters.py | 176 +++++++++++++
.../test/support/interpreters.rst | 11 +-
Lib/test/test_interpreters.py | 45 +---
5 files changed, 191 insertions(+), 277 deletions(-)
delete mode 100644 Lib/test/support/_interpreters.py
create mode 100644 Lib/test/support/interpreters.py
rename Doc/library/_interpreters.rst => Lib/test/support/interpreters.rst (92%)
diff --git a/Doc/library/index.rst b/Doc/library/index.rst
index d724238be7893c..bebf7429b0e63e 100644
--- a/Doc/library/index.rst
+++ b/Doc/library/index.rst
@@ -77,4 +77,3 @@ the `Python Package Index `_.
unix.rst
superseded.rst
undoc.rst
- _interpreters.rst
diff --git a/Lib/test/support/_interpreters.py b/Lib/test/support/_interpreters.py
deleted file mode 100644
index 93780ec08da94a..00000000000000
--- a/Lib/test/support/_interpreters.py
+++ /dev/null
@@ -1,235 +0,0 @@
-"""Subinterpreters High Level Module."""
-
-import logging
-import _xxsubinterpreters as _interpreters
-
-__all__ = ['Interpreter', 'SendChannel', 'RecvChannel',
- 'is_shareable', 'create_channel',
- 'list_all_channels', 'get_current',
- 'get_main', 'create']
-
-
-def create(*, isolated=True):
- """ create() -> Interpreter
-
- Initialize a new (idle) Python interpreter.
- """
- id = _interpreters.create()
- return Interpreter(id)
-
-def list_all():
- """ list_all() -> [Interpreter]
-
- Get all existing interpreters.
- """
- return [Interpreter(id) for id in
- _interpreters.list_all()]
-
-def get_current():
- """ get_current() -> Interpreter
-
- Get the currently running interpreter.
- """
- id = _interpreters.get_current()
- return Interpreter(id)
-
-def get_main():
- """ get_main() -> Interpreter
-
- Get the main interpreter.
- """
- id = _interpreters.get_main()
- return Interpreter(id)
-
-
-class Interpreter:
-
- def __init__(self, id):
- self._id = id
-
- @property
- def id(self):
- return self._id
-
- def is_running(self):
- """is_running() -> bool
-
- Return whether or not the identified
- interpreter is running.
- """
- return _interpreters.is_running(self._id)
-
- def close(self):
- """close()
-
- Finalize and destroy the interpreter.
-
- Attempting to destroy the current
- interpreter results in a RuntimeError.
- """
- return _interpreters.destroy(self._id)
-
- def run(self, src_str, /, *, channels=None):
- """run(src_str, /, *, channels=None)
-
- Run the given source code in the interpreter.
- This blocks the current thread until done.
- """
- try:
- _interpreters.run_string(self._id, src_str)
- except RunFailedError as err:
- logger.error(err)
- raise
-
-
-def is_shareable(obj):
- """ is_shareable(obj) -> Bool
-
- Return `True` if the object's data can be
- shared between interpreters and `False` otherwise.
- """
- return _interpreters.is_shareable(obj)
-
-def create_channel():
- """ create_channel() -> (RecvChannel, SendChannel)
-
- Create a new channel for passing data between
- interpreters.
- """
-
- cid = _interpreters.channel_create()
- return (RecvChannel(cid), SendChannel(cid))
-
-def list_all_channels():
- """ list_all_channels() -> [(RecvChannel, SendChannel)]
-
- Get all open channels.
- """
- return [(RecvChannel(cid), SendChannel(cid))
- for cid in _interpreters.channel_list_all()]
-
-def wait(timeout):
- #The implementation for wait
- # will be non trivial to be useful
- import time
- time.sleep(timeout)
-
-class RecvChannel:
-
- def __init__(self, id):
- self.id = id
-
- def recv(self):
- """ channel_recv() -> obj
-
- Get the next object from the channel,
- and wait if none have been sent.
- Associate the interpreter with the channel.
- """
- try:
- obj = _interpreters.channel_recv(self.id)
- if obj == None:
- wait(2)
- obj = _interpreters.channel_recv(self.id)
- except _interpreters.ChannelEmptyError:
- raise ChannelEmptyError
- except _interpreters.ChannelNotFoundError:
- raise ChannelNotFoundError
- except _interpreters.ChannelClosedError:
- raise ChannelClosedError
- except _interpreters.RunFailedError:
- raise RunFailedError
- return obj
-
- def recv_nowait(self, default=None):
- """recv_nowait(default=None) -> object
-
- Like recv(), but return the default
- instead of waiting.
- """
- try:
- obj = _interpreters.channel_recv(self.id)
- except _interpreters.ChannelEmptyError:
- raise ChannelEmptyError
- except _interpreters.ChannelNotFoundError:
- raise ChannelNotFoundError
- except _interpreters.ChannelClosedError:
- raise ChannelClosedError
- except _interpreters.RunFailedError:
- raise RunFailedError
- return obj
-
-
-class SendChannel:
-
- def __init__(self, id):
- self.id = id
-
- def send(self, obj):
- """ send(obj)
-
- Send the object (i.e. its data) to the receiving
- end of the channel and wait. Associate the interpreter
- with the channel.
- """
- try:
- _interpreters.channel_send(self.id, obj)
- wait(2)
- except _interpreters.ChannelNotFoundError:
- raise ChannelNotFoundError
- except _interpreters.ChannelClosedError:
- raise ChannelClosedError
- except _interpreters.RunFailedError:
- raise RunFailedError
-
- def send_nowait(self, obj):
- """ send_nowait(obj)
-
- Like send(), but return False if not received.
- """
- try:
- _interpreters.channel_send(self.id, obj)
- except _interpreters.ChannelNotFoundError:
- raise ChannelNotFoundError
- except _interpreters.ChannelClosedError:
- raise ChannelClosedError
- except _interpreters.RunFailedError:
- raise RunFailedError
-
- recv_obj = _interpreters.channel_recv(self.id)
- if recv_obj:
- return obj
- else:
- return False
-
-
-class ChannelError(Exception):
- pass
-
-
-class ChannelNotFoundError(ChannelError):
- pass
-
-
-class ChannelEmptyError(ChannelError):
- pass
-
-
-class ChannelNotEmptyError(ChannelError):
- pass
-
-
-class NotReceivedError(ChannelError):
- pass
-
-
-class ChannelClosedError(ChannelError):
- pass
-
-
-class ChannelReleasedError(ChannelClosedError):
- pass
-
-
-class RunFailedError(RuntimeError):
- pass
diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py
new file mode 100644
index 00000000000000..52bbf2d2a53d9f
--- /dev/null
+++ b/Lib/test/support/interpreters.py
@@ -0,0 +1,176 @@
+"""Subinterpreters High Level Module."""
+
+import _xxsubinterpreters as _interpreters
+
+__all__ = [
+ 'Interpreter', 'get_current', 'get_main', 'create', 'list_all',
+ 'SendChannel', 'RecvChannel',
+ 'create_channel', 'list_all_channels', 'is_shareable',
+ 'RunFailedError',
+ ]
+
+
+def create(*, isolated=True):
+ """
+ Initialize 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()]
+
+def get_current():
+ """
+ Get the currently running interpreter.
+ """
+ id = _interpreters.get_current()
+ return Interpreter(id)
+
+def get_main():
+ """
+ Get the main interpreter.
+ """
+ id = _interpreters.get_main()
+ return Interpreter(id)
+
+
+class Interpreter:
+ """
+ The Interpreter object represents
+ a single interpreter.
+ """
+
+ def __init__(self, id, *, isolated=None):
+ self._id = id
+ self._isolated = isolated
+
+ @property
+ def id(self):
+ return self._id
+
+ @property
+ def isolated(self):
+ if self._isolated is None:
+ self._isolated = _interpreters.is_isolated(self._id)
+ return self._isolated
+
+ def is_running(self):
+ """
+ Return whether or not the identified
+ interpreter is running.
+ """
+ return _interpreters.is_running(self._id)
+
+ def close(self):
+ """
+ Finalize and destroy the interpreter.
+
+ 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.
+ This blocks the current Python thread until done.
+ """
+ try:
+ _interpreters.run_string(self._id, src_str)
+ except _interpreters.RunFailedError as err:
+ raise
+
+
+def is_shareable(obj):
+ """
+ Return `True` if the object's data can be
+ shared between interpreters and `False` otherwise.
+ """
+ return _interpreters.is_shareable(obj)
+
+def create_channel():
+ """
+ Create a new channel for passing data between
+ interpreters.
+ """
+
+ cid = _interpreters.channel_create()
+ return (RecvChannel(cid), SendChannel(cid))
+
+def list_all_channels():
+ """
+ Get all open channels.
+ """
+ return [(RecvChannel(cid), SendChannel(cid))
+ for cid in _interpreters.channel_list_all()]
+
+
+class RecvChannel:
+ """
+ The RecvChannel object represents
+ a recieving channel.
+ """
+
+ def __init__(self, id):
+ self._id = id
+
+ def recv(self, *, _delay=10 / 1000): # seconds
+ """ channel_recv() -> obj
+ Get the next object from the channel,
+ and wait if none have been sent.
+ Associate the interpreter with the channel.
+ """
+ import time
+ sentinel = object()
+ obj = _interpreters.channel_recv(self._id, sentinel)
+ while obj is sentinel:
+ time.sleep(_delay)
+ obj = _interpreters.channel_recv(self._id, sentinel)
+ return obj
+
+ _NOT_SET = object()
+
+ def recv_nowait(self, default=None):
+ """
+ Like recv(), but return the default
+ instead of waiting.
+ """
+
+ if default is None:
+ 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
+
+ def send(self, obj):
+ """
+ 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)
+ time.sleep(2)
+
+ def send_nowait(self, obj):
+ """
+ Like send(), but return False if not received.
+ """
+
+ _interpreters.channel_send(self._id, obj)
+ return False
diff --git a/Doc/library/_interpreters.rst b/Lib/test/support/interpreters.rst
similarity index 92%
rename from Doc/library/_interpreters.rst
rename to Lib/test/support/interpreters.rst
index 771030f0301300..9a05eb67520c8c 100644
--- a/Doc/library/_interpreters.rst
+++ b/Lib/test/support/interpreters.rst
@@ -1,8 +1,5 @@
-:mod:`_interpreters` --- High-level Subinterpreters Module
-==========================================================
-
-.. module:: _interpreters
- :synopsis: High-level Subinterpreters Module.
+High-level implementation of Subinterpreters
+============================================
**Source code:** :source:`Lib/test/support/_interpreters.py`
@@ -17,7 +14,7 @@ It is a wrapper around the low-level ``__xxsubinterpreters`` module.
Interpreter Objects
-------------------
-The Interpreter object represents a single interpreter.
+The ``Interpreter`` object represents a single interpreter.
.. class:: Interpreter(id)
@@ -41,7 +38,7 @@ The Interpreter object represents a single interpreter.
RecvChannel Objects
-------------------
-The RecvChannel object represents a recieving channel.
+The ``RecvChannel`` object represents a recieving channel.
.. class:: RecvChannel(id)
diff --git a/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py
index 663b4bbaab1a88..d52d8270ec2e66 100644
--- a/Lib/test/test_interpreters.py
+++ b/Lib/test/test_interpreters.py
@@ -6,7 +6,7 @@
import time
import _xxsubinterpreters as _interpreters
-from test.support import _interpreters as interpreters
+from test.support import interpreters
def _captured_script(script):
r, w = os.pipe()
@@ -88,7 +88,7 @@ def test_in_subinterpreter(self):
main, = interpreters.list_all()
interp = interpreters.create()
out = _run_output(interp, dedent("""
- from test.support import _interpreters as interpreters
+ from test.support import interpreters
interp = interpreters.create()
print(interp)
"""))
@@ -96,25 +96,6 @@ def test_in_subinterpreter(self):
self.assertEqual(len(set(interpreters.list_all())), len({main, interp, interp2}))
- def test_in_threaded_subinterpreter(self):
- main, = interpreters.list_all()
- interp = interpreters.create()
- interp2 = None
- def f():
- nonlocal interp2
- out = _run_output(interp, dedent("""
- from test.support import _interpreters as interpreters
- interp = interpreters.create()
- print(interp)
- """))
- interp2 = int(out.strip())
-
- t = threading.Thread(target=f)
- t.start()
- t.join()
-
- self.assertEqual(len(set(interpreters.list_all())), len({main, interp, interp2}))
-
def test_after_destroy_all(self):
before = set(interpreters.list_all())
# Create 3 subinterpreters.
@@ -154,7 +135,7 @@ def test_subinterpreter(self):
main = _interpreters.get_main()
interp = interpreters.create()
out = _run_output(interp, dedent("""
- from test.support import _interpreters as interpreters
+ from test.support import interpreters
cur = interpreters.get_current()
print(cur)
"""))
@@ -284,7 +265,7 @@ def test_from_current(self):
main, = interpreters.list_all()
interp = interpreters.create()
script = dedent(f"""
- from test.support import _interpreters as interpreters
+ from test.support import interpreters
try:
main = interpreters.get_current()
main.close()
@@ -299,7 +280,7 @@ def test_from_sibling(self):
main, = interpreters.list_all()
interp1 = interpreters.create()
script = dedent(f"""
- from test.support import _interpreters as interpreters
+ from test.support import interpreters
interp2 = interpreters.create()
interp2.close()
""")
@@ -473,6 +454,7 @@ def test_sequential_ids(self):
self.assertEqual(len(set(after) - set(before)),
len({channels1, channels2, channels3}))
+
class TestSendRecv(TestBase):
def test_send_recv_main(self):
@@ -487,7 +469,7 @@ def test_send_recv_main(self):
def test_send_recv_same_interpreter(self):
interp = interpreters.create()
out = _run_output(interp, dedent("""
- from test.support import _interpreters as interpreters
+ from test.support import interpreters
r, s = interpreters.create_channel()
orig = b'spam'
s.send(orig)
@@ -516,11 +498,6 @@ def f():
self.assertEqual(obj, b'spam')
- def test_recv_empty(self):
- r, s = interpreters.create_channel()
- with self.assertRaises(interpreters.ChannelEmptyError):
- r.recv()
-
def test_send_recv_nowait_main(self):
r, s = interpreters.create_channel()
orig = b'spam'
@@ -533,7 +510,7 @@ def test_send_recv_nowait_main(self):
def test_send_recv_nowait_same_interpreter(self):
interp = interpreters.create()
out = _run_output(interp, dedent("""
- from test.support import _interpreters as interpreters
+ from test.support import interpreters
r, s = interpreters.create_channel()
orig = b'spam'
s.send(orig)
@@ -542,7 +519,7 @@ def test_send_recv_nowait_same_interpreter(self):
assert obj == orig
"""))
- def test_send_recv_nowait_different_threads(self):
+
r, s = interpreters.create_channel()
def f():
@@ -550,7 +527,7 @@ def f():
try:
obj = r.recv_nowait()
break
- except interpreters.ChannelEmptyError:
+ except _interpreters.ChannelEmptyError:
time.sleep(0.1)
s.send(obj)
t = threading.Thread(target=f)
@@ -564,5 +541,5 @@ def f():
def test_recv_nowait_empty(self):
r, s = interpreters.create_channel()
- with self.assertRaises(interpreters.ChannelEmptyError):
+ with self.assertRaises(_interpreters.ChannelEmptyError):
r.recv_nowait()
From af90b25c99d76be03d2c004c2ad32d32f8fa6764 Mon Sep 17 00:00:00 2001
From: Joannah Nanjekye
Date: Fri, 15 May 2020 18:15:24 +0000
Subject: [PATCH 10/15] Fix white space
---
Lib/test/support/interpreters.py | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py
index 52bbf2d2a53d9f..df37248ceb808b 100644
--- a/Lib/test/support/interpreters.py
+++ b/Lib/test/support/interpreters.py
@@ -11,7 +11,7 @@
def create(*, isolated=True):
- """
+ """
Initialize a new (idle) Python interpreter.
"""
@@ -19,14 +19,14 @@ def create(*, isolated=True):
return Interpreter(id, isolated=isolated)
def list_all():
- """
+ """
Get all existing interpreters.
"""
return [Interpreter(id) for id in
_interpreters.list_all()]
def get_current():
- """
+ """
Get the currently running interpreter.
"""
id = _interpreters.get_current()
@@ -88,14 +88,14 @@ def run(self, src_str, /, *, channels=None):
def is_shareable(obj):
- """
+ """
Return `True` if the object's data can be
shared between interpreters and `False` otherwise.
"""
return _interpreters.is_shareable(obj)
def create_channel():
- """
+ """
Create a new channel for passing data between
interpreters.
"""
@@ -104,7 +104,7 @@ def create_channel():
return (RecvChannel(cid), SendChannel(cid))
def list_all_channels():
- """
+ """
Get all open channels.
"""
return [(RecvChannel(cid), SendChannel(cid))
@@ -133,7 +133,7 @@ def recv(self, *, _delay=10 / 1000): # seconds
time.sleep(_delay)
obj = _interpreters.channel_recv(self._id, sentinel)
return obj
-
+
_NOT_SET = object()
def recv_nowait(self, default=None):
@@ -158,7 +158,7 @@ def __init__(self, id):
self._id = id
def send(self, obj):
- """
+ """
Send the object (i.e. its data) to the receiving
end of the channel and wait. Associate the interpreter
with the channel.
@@ -168,7 +168,7 @@ def send(self, obj):
time.sleep(2)
def send_nowait(self, obj):
- """
+ """
Like send(), but return False if not received.
"""
From 90ec54b0b4569ff5ad8e7e4fc598172f21ac5c48 Mon Sep 17 00:00:00 2001
From: Joannah Nanjekye
Date: Fri, 15 May 2020 18:42:23 +0000
Subject: [PATCH 11/15] Remove undefined from __all__
---
Lib/test/support/interpreters.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py
index df37248ceb808b..246905544337fb 100644
--- a/Lib/test/support/interpreters.py
+++ b/Lib/test/support/interpreters.py
@@ -6,7 +6,6 @@
'Interpreter', 'get_current', 'get_main', 'create', 'list_all',
'SendChannel', 'RecvChannel',
'create_channel', 'list_all_channels', 'is_shareable',
- 'RunFailedError',
]
From 049aa3a51dcccee4bfe319745edee6c6beb3c0f0 Mon Sep 17 00:00:00 2001
From: Joannah Nanjekye
Date: Sat, 16 May 2020 17:28:02 +0000
Subject: [PATCH 12/15] Fix recv and add exceptions
---
Lib/test/support/interpreters.py | 38 ++++++++++++++++++--------------
Lib/test/test_interpreters.py | 13 -----------
2 files changed, 21 insertions(+), 30 deletions(-)
diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py
index 246905544337fb..9d3048848e4382 100644
--- a/Lib/test/support/interpreters.py
+++ b/Lib/test/support/interpreters.py
@@ -1,18 +1,26 @@
"""Subinterpreters High Level Module."""
import _xxsubinterpreters as _interpreters
+# aliases:
+from _xxsubinterpreters import (
+ ChannelError, ChannelNotFoundError,
+ ChannelEmptyError, ChannelNotEmptyError, NotReceivedError,
+ is_shareable,
+)
__all__ = [
'Interpreter', 'get_current', 'get_main', 'create', 'list_all',
'SendChannel', 'RecvChannel',
'create_channel', 'list_all_channels', 'is_shareable',
+ 'ChannelError', 'ChannelNotFoundError',
+ 'ChannelEmptyError', 'ChannelNotEmptyError',
+ 'NotReceivedError',
]
def create(*, isolated=True):
"""
Initialize a new (idle) Python interpreter.
-
"""
id = _interpreters.create(isolated=isolated)
return Interpreter(id, isolated=isolated)
@@ -80,19 +88,9 @@ def run(self, src_str, /, *, channels=None):
Run the given source code in the interpreter.
This blocks the current Python thread until done.
"""
- try:
- _interpreters.run_string(self._id, src_str)
- except _interpreters.RunFailedError as err:
- raise
+ _interpreters.run_string(self._id, src_str)
-def is_shareable(obj):
- """
- Return `True` if the object's data can be
- shared between interpreters and `False` otherwise.
- """
- return _interpreters.is_shareable(obj)
-
def create_channel():
"""
Create a new channel for passing data between
@@ -119,8 +117,8 @@ class RecvChannel:
def __init__(self, id):
self._id = id
- def recv(self, *, _delay=10 / 1000): # seconds
- """ channel_recv() -> obj
+ 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.
@@ -133,19 +131,22 @@ def recv(self, *, _delay=10 / 1000): # seconds
obj = _interpreters.channel_recv(self._id, sentinel)
return obj
- _NOT_SET = object()
-
def recv_nowait(self, default=None):
"""
Like recv(), but return the default
instead of waiting.
- """
+ This function is blocked by a missing low-level
+ implementation of channel_recv_wait().
+ """
if default is None:
+ default = _NOT_SET
+ if default is _NOT_SET:
return _interpreters.channel_recv(self._id)
else:
return _interpreters.channel_recv(self._id, default)
+_NOT_SET = object()
class SendChannel:
"""
@@ -169,6 +170,9 @@ def send(self, obj):
def send_nowait(self, obj):
"""
Like send(), but return False if not received.
+
+ This function is blocked by a missing low-level
+ implementation of channel_send_wait().
"""
_interpreters.channel_send(self._id, obj)
diff --git a/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py
index d52d8270ec2e66..4f5bed0e420b62 100644
--- a/Lib/test/test_interpreters.py
+++ b/Lib/test/test_interpreters.py
@@ -530,16 +530,3 @@ def f():
except _interpreters.ChannelEmptyError:
time.sleep(0.1)
s.send(obj)
- t = threading.Thread(target=f)
- t.start()
-
- s.send(b'spam')
- t.join()
- obj = r.recv_nowait()
-
- self.assertEqual(obj, b'spam')
-
- def test_recv_nowait_empty(self):
- r, s = interpreters.create_channel()
- with self.assertRaises(_interpreters.ChannelEmptyError):
- r.recv_nowait()
From 9577395e4c0867514974ea546aaa757bc5608735 Mon Sep 17 00:00:00 2001
From: Joannah Nanjekye
Date: Mon, 18 May 2020 16:26:00 +0000
Subject: [PATCH 13/15] Remove unused exceptions, fix pep 8 formatting errors
and fix _NOT_SET in recv_nowait()
---
Lib/test/support/interpreters.py | 20 ++++++++++++--------
Lib/test/test_interpreters.py | 5 ++++-
Modules/_xxsubinterpretersmodule.c | 23 -----------------------
3 files changed, 16 insertions(+), 32 deletions(-)
diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py
index 9d3048848e4382..ef9dcafb2a3860 100644
--- a/Lib/test/support/interpreters.py
+++ b/Lib/test/support/interpreters.py
@@ -1,20 +1,20 @@
"""Subinterpreters High Level Module."""
import _xxsubinterpreters as _interpreters
+
# aliases:
from _xxsubinterpreters import (
- ChannelError, ChannelNotFoundError,
- ChannelEmptyError, ChannelNotEmptyError, NotReceivedError,
+ ChannelError, ChannelNotFoundError, ChannelEmptyError,
is_shareable,
)
+
__all__ = [
'Interpreter', 'get_current', 'get_main', 'create', 'list_all',
'SendChannel', 'RecvChannel',
'create_channel', 'list_all_channels', 'is_shareable',
'ChannelError', 'ChannelNotFoundError',
- 'ChannelEmptyError', 'ChannelNotEmptyError',
- 'NotReceivedError',
+ 'ChannelEmptyError',
]
@@ -25,6 +25,7 @@ def create(*, isolated=True):
id = _interpreters.create(isolated=isolated)
return Interpreter(id, isolated=isolated)
+
def list_all():
"""
Get all existing interpreters.
@@ -32,6 +33,7 @@ def list_all():
return [Interpreter(id) for id in
_interpreters.list_all()]
+
def get_current():
"""
Get the currently running interpreter.
@@ -39,6 +41,7 @@ def get_current():
id = _interpreters.get_current()
return Interpreter(id)
+
def get_main():
"""
Get the main interpreter.
@@ -100,6 +103,7 @@ def create_channel():
cid = _interpreters.channel_create()
return (RecvChannel(cid), SendChannel(cid))
+
def list_all_channels():
"""
Get all open channels.
@@ -108,6 +112,9 @@ def list_all_channels():
for cid in _interpreters.channel_list_all()]
+_NOT_SET = object()
+
+
class RecvChannel:
"""
The RecvChannel object represents
@@ -131,7 +138,7 @@ def recv(self, *, _delay=10 / 1000): # 10 milliseconds
obj = _interpreters.channel_recv(self._id, sentinel)
return obj
- def recv_nowait(self, default=None):
+ def recv_nowait(self, default=_NOT_SET):
"""
Like recv(), but return the default
instead of waiting.
@@ -139,14 +146,11 @@ def recv_nowait(self, default=None):
This function is blocked by a missing low-level
implementation of channel_recv_wait().
"""
- if default is None:
- default = _NOT_SET
if default is _NOT_SET:
return _interpreters.channel_recv(self._id)
else:
return _interpreters.channel_recv(self._id, default)
-_NOT_SET = object()
class SendChannel:
"""
diff --git a/Lib/test/test_interpreters.py b/Lib/test/test_interpreters.py
index 4f5bed0e420b62..3451a4c8759d8b 100644
--- a/Lib/test/test_interpreters.py
+++ b/Lib/test/test_interpreters.py
@@ -8,6 +8,7 @@
import _xxsubinterpreters as _interpreters
from test.support import interpreters
+
def _captured_script(script):
r, w = os.pipe()
indented = script.replace('\n', '\n ')
@@ -19,6 +20,7 @@ def _captured_script(script):
""")
return wrapped, open(r)
+
def clean_up_interpreters():
for interp in interpreters.list_all():
if interp.id == 0: # main
@@ -28,12 +30,14 @@ def clean_up_interpreters():
except RuntimeError:
pass # already destroyed
+
def _run_output(interp, request, shared=None):
script, rpipe = _captured_script(request)
with rpipe:
interp.run(script)
return rpipe.read()
+
@contextlib.contextmanager
def _running(interp):
r, w = os.pipe()
@@ -519,7 +523,6 @@ def test_send_recv_nowait_same_interpreter(self):
assert obj == orig
"""))
-
r, s = interpreters.create_channel()
def f():
diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c
index 74a665eba8b741..9c5df16e156a1d 100644
--- a/Modules/_xxsubinterpretersmodule.c
+++ b/Modules/_xxsubinterpretersmodule.c
@@ -1167,8 +1167,6 @@ static PyObject *ChannelNotFoundError;
static PyObject *ChannelClosedError;
static PyObject *ChannelEmptyError;
static PyObject *ChannelNotEmptyError;
-static PyObject *ChannelReleasedError;
-static PyObject *NotReceivedError;
static int
channel_exceptions_init(PyObject *ns)
@@ -1205,27 +1203,6 @@ channel_exceptions_init(PyObject *ns)
return -1;
}
- // An operation tried to use a released channel.
- ChannelReleasedError = PyErr_NewException(
- "_interpreters.ChannelReleasedError", ChannelClosedError, NULL);
- if (ChannelReleasedError == NULL) {
- return -1;
- }
- if (PyDict_SetItemString(ns, "ChannelReleasedError", ChannelReleasedError) != 0) {
- return -1;
- }
-
- // An operation trying to send an object when Nothing was waiting
- // to receive it
- NotReceivedError = PyErr_NewException(
- "_interpreters.NotReceivedError", ChannelError, NULL);
- if (NotReceivedError == NULL) {
- return -1;
- }
- if (PyDict_SetItemString(ns, "NotReceivedError", NotReceivedError) != 0) {
- return -1;
- }
-
// An operation tried to pop from an empty channel.
ChannelEmptyError = PyErr_NewException(
"_xxsubinterpreters.ChannelEmptyError", ChannelError, NULL);
From 7370a9c3291495f2586c90ecd286a0b0d117a30c Mon Sep 17 00:00:00 2001
From: Joannah Nanjekye <33177550+nanjekyejoannah@users.noreply.github.com>
Date: Wed, 3 Jun 2020 18:30:57 -0300
Subject: [PATCH 14/15] Update Lib/test/support/interpreters.py
Co-authored-by: Pablo Galindo
---
Lib/test/support/interpreters.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Lib/test/support/interpreters.py b/Lib/test/support/interpreters.py
index ef9dcafb2a3860..09508e1bbeca03 100644
--- a/Lib/test/support/interpreters.py
+++ b/Lib/test/support/interpreters.py
@@ -118,7 +118,7 @@ def list_all_channels():
class RecvChannel:
"""
The RecvChannel object represents
- a recieving channel.
+ a receiving channel.
"""
def __init__(self, id):
From d5f2579c3e76f089f97d5c262c6620e3aee81338 Mon Sep 17 00:00:00 2001
From: Joannah Nanjekye <33177550+nanjekyejoannah@users.noreply.github.com>
Date: Fri, 5 Jun 2020 13:36:39 -0300
Subject: [PATCH 15/15] Remove documentation (module is for internal use)
---
Lib/test/support/interpreters.rst | 145 ------------------------------
1 file changed, 145 deletions(-)
delete mode 100644 Lib/test/support/interpreters.rst
diff --git a/Lib/test/support/interpreters.rst b/Lib/test/support/interpreters.rst
deleted file mode 100644
index 9a05eb67520c8c..00000000000000
--- a/Lib/test/support/interpreters.rst
+++ /dev/null
@@ -1,145 +0,0 @@
-High-level implementation of Subinterpreters
-============================================
-
-**Source code:** :source:`Lib/test/support/_interpreters.py`
-
---------------
-
-This module provides high-level tools for working with sub-interpreters,
-such as creating them, running code in them, or sending data between them.
-It is a wrapper around the low-level ``__xxsubinterpreters`` module.
-
-.. versionchanged:: added in 3.9
-
-Interpreter Objects
--------------------
-
-The ``Interpreter`` object represents a single interpreter.
-
-.. class:: Interpreter(id)
-
- The class implementing a subinterpreter object.
-
- .. method:: is_running()
-
- Return ``True`` if the identified interpreter is running.
-
- .. method:: close()
-
- Destroy the interpreter. Attempting to destroy the current
- interpreter results in a `RuntimeError`.
-
- .. method:: run(self, src_str, /, *, channels=None):
-
- Run the given source code in the interpreter. This blocks
- the current thread until done. ``channels`` should be in
- the form : `(RecvChannel, SendChannel)`.
-
-RecvChannel Objects
--------------------
-
-The ``RecvChannel`` object represents a recieving channel.
-
-.. class:: RecvChannel(id)
-
- This class represents the receiving end of a channel.
-
- .. method:: recv()
-
- Get the next object from the channel, and wait if
- none have been sent. Associate the interpreter
- with the channel.
-
- .. method:: recv_nowait(default=None)
-
- Like ``recv()``, but return the default result
- instead of waiting.
-
-
-SendChannel Objects
---------------------
-
-The ``SendChannel`` object represents a sending channel.
-
-.. class:: SendChannel(id)
-
- This class represents the sending end of a channel.
-
- .. method:: send(obj)
-
- Send the object ``obj`` to the receiving end of the channel
- and wait. Associate the interpreter with the channel.
-
- .. method:: send_nowait(obj)
-
- Similar to ``send()``, but returns ``False`` if
- *obj* is not immediately received instead of blocking.
-
-
-This module defines the following global functions:
-
-
-.. function:: is_shareable(obj)
-
- Return ``True`` if the object's data can be shared between
- interpreters.
-
-.. function:: create_channel()
-
- Create a new channel for passing data between interpreters.
-
-.. function:: list_all_channels()
-
- Return all open channels.
-
-.. function:: create(*, isolated=True)
-
- Initialize a new (idle) Python interpreter. Get the currently
- running interpreter. This method returns an ``Interpreter`` object.
-
-.. function:: get_current()
-
- Get the currently running interpreter. This method returns
- an ``Interpreter`` object.
-
-.. function:: get_main()
-
- Get the main interpreter. This method returns
- an ``Interpreter`` object.
-
-.. function:: list_all()
-
- Get all existing interpreters. Returns a list
- of ``Interpreter`` objects.
-
-This module also defines the following exceptions.
-
-.. exception:: RunFailedError
-
- This exception, a subclass of :exc:`RuntimeError`, is raised when the
- ``Interpreter.run()`` results in an uncaught exception.
-
-.. exception:: ChannelError
-
- This exception is a subclass of :exc:`Exception`, and is the base
- class for all channel-related exceptions.
-
-.. exception:: ChannelNotFoundError
-
- This exception is a subclass of :exc:`ChannelError`, and is raised
- when the the identified channel is not found.
-
-.. exception:: ChannelEmptyError
-
- This exception is a subclass of :exc:`ChannelError`, and is raised when
- the channel is unexpectedly empty.
-
-.. exception:: ChannelNotEmptyError
-
- This exception is a subclass of :exc:`ChannelError`, and is raised when
- the channel is unexpectedly not empty.
-
-.. exception:: NotReceivedError
-
- This exception is a subclass of :exc:`ChannelError`, and is raised when
- nothing was waiting to receive a sent object.
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/python/cpython/pull/20611.patch
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy