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. pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy