mirror of https://github.com/python/cpython.git
gh-76785: Add More Tests to test_interpreters.test_api (gh-117662)
In addition to the increase test coverage, this is a precursor to sorting out how we handle interpreters created directly via the C-API.
This commit is contained in:
parent
0cc71bde00
commit
993c3cca16
|
@ -217,6 +217,11 @@ typedef struct _excinfo {
|
||||||
const char *errdisplay;
|
const char *errdisplay;
|
||||||
} _PyXI_excinfo;
|
} _PyXI_excinfo;
|
||||||
|
|
||||||
|
PyAPI_FUNC(int) _PyXI_InitExcInfo(_PyXI_excinfo *info, PyObject *exc);
|
||||||
|
PyAPI_FUNC(PyObject *) _PyXI_FormatExcInfo(_PyXI_excinfo *info);
|
||||||
|
PyAPI_FUNC(PyObject *) _PyXI_ExcInfoAsObject(_PyXI_excinfo *info);
|
||||||
|
PyAPI_FUNC(void) _PyXI_ClearExcInfo(_PyXI_excinfo *info);
|
||||||
|
|
||||||
|
|
||||||
typedef enum error_code {
|
typedef enum error_code {
|
||||||
_PyXI_ERR_NO_ERROR = 0,
|
_PyXI_ERR_NO_ERROR = 0,
|
||||||
|
@ -313,6 +318,21 @@ PyAPI_FUNC(PyObject *) _PyXI_ApplyCapturedException(_PyXI_session *session);
|
||||||
PyAPI_FUNC(int) _PyXI_HasCapturedException(_PyXI_session *session);
|
PyAPI_FUNC(int) _PyXI_HasCapturedException(_PyXI_session *session);
|
||||||
|
|
||||||
|
|
||||||
|
/*************/
|
||||||
|
/* other API */
|
||||||
|
/*************/
|
||||||
|
|
||||||
|
// Export for _testinternalcapi shared extension
|
||||||
|
PyAPI_FUNC(PyInterpreterState *) _PyXI_NewInterpreter(
|
||||||
|
PyInterpreterConfig *config,
|
||||||
|
PyThreadState **p_tstate,
|
||||||
|
PyThreadState **p_save_tstate);
|
||||||
|
PyAPI_FUNC(void) _PyXI_EndInterpreter(
|
||||||
|
PyInterpreterState *interp,
|
||||||
|
PyThreadState *tstate,
|
||||||
|
PyThreadState **p_save_tstate);
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -103,11 +103,22 @@ struct _is {
|
||||||
int requires_idref;
|
int requires_idref;
|
||||||
PyThread_type_lock id_mutex;
|
PyThread_type_lock id_mutex;
|
||||||
|
|
||||||
|
#define _PyInterpreterState_WHENCE_NOTSET -1
|
||||||
|
#define _PyInterpreterState_WHENCE_UNKNOWN 0
|
||||||
|
#define _PyInterpreterState_WHENCE_RUNTIME 1
|
||||||
|
#define _PyInterpreterState_WHENCE_LEGACY_CAPI 2
|
||||||
|
#define _PyInterpreterState_WHENCE_CAPI 3
|
||||||
|
#define _PyInterpreterState_WHENCE_XI 4
|
||||||
|
#define _PyInterpreterState_WHENCE_MAX 4
|
||||||
|
long _whence;
|
||||||
|
|
||||||
/* Has been initialized to a safe state.
|
/* Has been initialized to a safe state.
|
||||||
|
|
||||||
In order to be effective, this must be set to 0 during or right
|
In order to be effective, this must be set to 0 during or right
|
||||||
after allocation. */
|
after allocation. */
|
||||||
int _initialized;
|
int _initialized;
|
||||||
|
/* Has been fully initialized via pylifecycle.c. */
|
||||||
|
int _ready;
|
||||||
int finalizing;
|
int finalizing;
|
||||||
|
|
||||||
uintptr_t last_restart_version;
|
uintptr_t last_restart_version;
|
||||||
|
@ -305,6 +316,11 @@ PyAPI_FUNC(int) _PyInterpreterState_IDInitref(PyInterpreterState *);
|
||||||
PyAPI_FUNC(int) _PyInterpreterState_IDIncref(PyInterpreterState *);
|
PyAPI_FUNC(int) _PyInterpreterState_IDIncref(PyInterpreterState *);
|
||||||
PyAPI_FUNC(void) _PyInterpreterState_IDDecref(PyInterpreterState *);
|
PyAPI_FUNC(void) _PyInterpreterState_IDDecref(PyInterpreterState *);
|
||||||
|
|
||||||
|
PyAPI_FUNC(long) _PyInterpreterState_GetWhence(PyInterpreterState *interp);
|
||||||
|
extern void _PyInterpreterState_SetWhence(
|
||||||
|
PyInterpreterState *interp,
|
||||||
|
long whence);
|
||||||
|
|
||||||
extern const PyConfig* _PyInterpreterState_GetConfig(PyInterpreterState *interp);
|
extern const PyConfig* _PyInterpreterState_GetConfig(PyInterpreterState *interp);
|
||||||
|
|
||||||
// Get a copy of the current interpreter configuration.
|
// Get a copy of the current interpreter configuration.
|
||||||
|
|
|
@ -77,6 +77,9 @@ _Py_IsMainInterpreterFinalizing(PyInterpreterState *interp)
|
||||||
interp == &_PyRuntime._main_interpreter);
|
interp == &_PyRuntime._main_interpreter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Export for _xxsubinterpreters module.
|
||||||
|
PyAPI_FUNC(PyObject *) _PyInterpreterState_GetIDObject(PyInterpreterState *);
|
||||||
|
|
||||||
// Export for _xxsubinterpreters module.
|
// Export for _xxsubinterpreters module.
|
||||||
PyAPI_FUNC(int) _PyInterpreterState_SetRunningMain(PyInterpreterState *);
|
PyAPI_FUNC(int) _PyInterpreterState_SetRunningMain(PyInterpreterState *);
|
||||||
PyAPI_FUNC(void) _PyInterpreterState_SetNotRunningMain(PyInterpreterState *);
|
PyAPI_FUNC(void) _PyInterpreterState_SetNotRunningMain(PyInterpreterState *);
|
||||||
|
|
|
@ -162,6 +162,7 @@ extern PyTypeObject _PyExc_MemoryError;
|
||||||
#define _PyInterpreterState_INIT(INTERP) \
|
#define _PyInterpreterState_INIT(INTERP) \
|
||||||
{ \
|
{ \
|
||||||
.id_refcount = -1, \
|
.id_refcount = -1, \
|
||||||
|
._whence = _PyInterpreterState_WHENCE_NOTSET, \
|
||||||
.imports = IMPORTS_INIT, \
|
.imports = IMPORTS_INIT, \
|
||||||
.ceval = { \
|
.ceval = { \
|
||||||
.recursion_limit = Py_DEFAULT_RECURSION_LIMIT, \
|
.recursion_limit = Py_DEFAULT_RECURSION_LIMIT, \
|
||||||
|
|
|
@ -79,18 +79,19 @@ def create():
|
||||||
|
|
||||||
def list_all():
|
def list_all():
|
||||||
"""Return all existing interpreters."""
|
"""Return all existing interpreters."""
|
||||||
return [Interpreter(id) for id in _interpreters.list_all()]
|
return [Interpreter(id)
|
||||||
|
for id, in _interpreters.list_all()]
|
||||||
|
|
||||||
|
|
||||||
def get_current():
|
def get_current():
|
||||||
"""Return the currently running interpreter."""
|
"""Return the currently running interpreter."""
|
||||||
id = _interpreters.get_current()
|
id, = _interpreters.get_current()
|
||||||
return Interpreter(id)
|
return Interpreter(id)
|
||||||
|
|
||||||
|
|
||||||
def get_main():
|
def get_main():
|
||||||
"""Return the main interpreter."""
|
"""Return the main interpreter."""
|
||||||
id = _interpreters.get_main()
|
id, = _interpreters.get_main()
|
||||||
return Interpreter(id)
|
return Interpreter(id)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
from test.support import import_helper
|
from test.support import import_helper
|
||||||
|
|
||||||
from test.test__xxsubinterpreters import (
|
from test.test__xxsubinterpreters import (
|
||||||
interpreters,
|
_interpreters,
|
||||||
_run_output,
|
_run_output,
|
||||||
clean_up_interpreters,
|
clean_up_interpreters,
|
||||||
)
|
)
|
||||||
|
@ -49,14 +49,15 @@ def run_interp(id, source, **shared):
|
||||||
|
|
||||||
def _run_interp(id, source, shared, _mainns={}):
|
def _run_interp(id, source, shared, _mainns={}):
|
||||||
source = dedent(source)
|
source = dedent(source)
|
||||||
main = interpreters.get_main()
|
main, *_ = _interpreters.get_main()
|
||||||
if main == id:
|
if main == id:
|
||||||
if interpreters.get_current() != main:
|
cur, *_ = _interpreters.get_current()
|
||||||
|
if cur != main:
|
||||||
raise RuntimeError
|
raise RuntimeError
|
||||||
# XXX Run a func?
|
# XXX Run a func?
|
||||||
exec(source, _mainns)
|
exec(source, _mainns)
|
||||||
else:
|
else:
|
||||||
interpreters.run_string(id, source, shared)
|
_interpreters.run_string(id, source, shared)
|
||||||
|
|
||||||
|
|
||||||
class Interpreter(namedtuple('Interpreter', 'name id')):
|
class Interpreter(namedtuple('Interpreter', 'name id')):
|
||||||
|
@ -71,7 +72,7 @@ def from_raw(cls, raw):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def __new__(cls, name=None, id=None):
|
def __new__(cls, name=None, id=None):
|
||||||
main = interpreters.get_main()
|
main, *_ = _interpreters.get_main()
|
||||||
if id == main:
|
if id == main:
|
||||||
if not name:
|
if not name:
|
||||||
name = 'main'
|
name = 'main'
|
||||||
|
@ -89,7 +90,7 @@ def __new__(cls, name=None, id=None):
|
||||||
name = 'main'
|
name = 'main'
|
||||||
id = main
|
id = main
|
||||||
else:
|
else:
|
||||||
id = interpreters.create()
|
id = _interpreters.create()
|
||||||
self = super().__new__(cls, name, id)
|
self = super().__new__(cls, name, id)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@ -370,7 +371,7 @@ def test_sequential_ids(self):
|
||||||
self.assertEqual(set(after) - set(before), {id1, id2, id3})
|
self.assertEqual(set(after) - set(before), {id1, id2, id3})
|
||||||
|
|
||||||
def test_ids_global(self):
|
def test_ids_global(self):
|
||||||
id1 = interpreters.create()
|
id1 = _interpreters.create()
|
||||||
out = _run_output(id1, dedent("""
|
out = _run_output(id1, dedent("""
|
||||||
import _xxinterpchannels as _channels
|
import _xxinterpchannels as _channels
|
||||||
cid = _channels.create()
|
cid = _channels.create()
|
||||||
|
@ -378,7 +379,7 @@ def test_ids_global(self):
|
||||||
"""))
|
"""))
|
||||||
cid1 = int(out.strip())
|
cid1 = int(out.strip())
|
||||||
|
|
||||||
id2 = interpreters.create()
|
id2 = _interpreters.create()
|
||||||
out = _run_output(id2, dedent("""
|
out = _run_output(id2, dedent("""
|
||||||
import _xxinterpchannels as _channels
|
import _xxinterpchannels as _channels
|
||||||
cid = _channels.create()
|
cid = _channels.create()
|
||||||
|
@ -390,7 +391,7 @@ def test_ids_global(self):
|
||||||
|
|
||||||
def test_channel_list_interpreters_none(self):
|
def test_channel_list_interpreters_none(self):
|
||||||
"""Test listing interpreters for a channel with no associations."""
|
"""Test listing interpreters for a channel with no associations."""
|
||||||
# Test for channel with no associated interpreters.
|
# Test for channel with no associated _interpreters.
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
send_interps = channels.list_interpreters(cid, send=True)
|
send_interps = channels.list_interpreters(cid, send=True)
|
||||||
recv_interps = channels.list_interpreters(cid, send=False)
|
recv_interps = channels.list_interpreters(cid, send=False)
|
||||||
|
@ -398,8 +399,8 @@ def test_channel_list_interpreters_none(self):
|
||||||
self.assertEqual(recv_interps, [])
|
self.assertEqual(recv_interps, [])
|
||||||
|
|
||||||
def test_channel_list_interpreters_basic(self):
|
def test_channel_list_interpreters_basic(self):
|
||||||
"""Test basic listing channel interpreters."""
|
"""Test basic listing channel _interpreters."""
|
||||||
interp0 = interpreters.get_main()
|
interp0, *_ = _interpreters.get_main()
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
channels.send(cid, "send", blocking=False)
|
channels.send(cid, "send", blocking=False)
|
||||||
# Test for a channel that has one end associated to an interpreter.
|
# Test for a channel that has one end associated to an interpreter.
|
||||||
|
@ -408,7 +409,7 @@ def test_channel_list_interpreters_basic(self):
|
||||||
self.assertEqual(send_interps, [interp0])
|
self.assertEqual(send_interps, [interp0])
|
||||||
self.assertEqual(recv_interps, [])
|
self.assertEqual(recv_interps, [])
|
||||||
|
|
||||||
interp1 = interpreters.create()
|
interp1 = _interpreters.create()
|
||||||
_run_output(interp1, dedent(f"""
|
_run_output(interp1, dedent(f"""
|
||||||
import _xxinterpchannels as _channels
|
import _xxinterpchannels as _channels
|
||||||
obj = _channels.recv({cid})
|
obj = _channels.recv({cid})
|
||||||
|
@ -421,10 +422,10 @@ def test_channel_list_interpreters_basic(self):
|
||||||
|
|
||||||
def test_channel_list_interpreters_multiple(self):
|
def test_channel_list_interpreters_multiple(self):
|
||||||
"""Test listing interpreters for a channel with many associations."""
|
"""Test listing interpreters for a channel with many associations."""
|
||||||
interp0 = interpreters.get_main()
|
interp0, *_ = _interpreters.get_main()
|
||||||
interp1 = interpreters.create()
|
interp1 = _interpreters.create()
|
||||||
interp2 = interpreters.create()
|
interp2 = _interpreters.create()
|
||||||
interp3 = interpreters.create()
|
interp3 = _interpreters.create()
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
|
|
||||||
channels.send(cid, "send", blocking=False)
|
channels.send(cid, "send", blocking=False)
|
||||||
|
@ -447,8 +448,8 @@ def test_channel_list_interpreters_multiple(self):
|
||||||
|
|
||||||
def test_channel_list_interpreters_destroyed(self):
|
def test_channel_list_interpreters_destroyed(self):
|
||||||
"""Test listing channel interpreters with a destroyed interpreter."""
|
"""Test listing channel interpreters with a destroyed interpreter."""
|
||||||
interp0 = interpreters.get_main()
|
interp0, *_ = _interpreters.get_main()
|
||||||
interp1 = interpreters.create()
|
interp1 = _interpreters.create()
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
channels.send(cid, "send", blocking=False)
|
channels.send(cid, "send", blocking=False)
|
||||||
_run_output(interp1, dedent(f"""
|
_run_output(interp1, dedent(f"""
|
||||||
|
@ -461,7 +462,7 @@ def test_channel_list_interpreters_destroyed(self):
|
||||||
self.assertEqual(send_interps, [interp0])
|
self.assertEqual(send_interps, [interp0])
|
||||||
self.assertEqual(recv_interps, [interp1])
|
self.assertEqual(recv_interps, [interp1])
|
||||||
|
|
||||||
interpreters.destroy(interp1)
|
_interpreters.destroy(interp1)
|
||||||
# Destroyed interpreter should not be listed.
|
# Destroyed interpreter should not be listed.
|
||||||
send_interps = channels.list_interpreters(cid, send=True)
|
send_interps = channels.list_interpreters(cid, send=True)
|
||||||
recv_interps = channels.list_interpreters(cid, send=False)
|
recv_interps = channels.list_interpreters(cid, send=False)
|
||||||
|
@ -472,9 +473,9 @@ def test_channel_list_interpreters_released(self):
|
||||||
"""Test listing channel interpreters with a released channel."""
|
"""Test listing channel interpreters with a released channel."""
|
||||||
# Set up one channel with main interpreter on the send end and two
|
# Set up one channel with main interpreter on the send end and two
|
||||||
# subinterpreters on the receive end.
|
# subinterpreters on the receive end.
|
||||||
interp0 = interpreters.get_main()
|
interp0, *_ = _interpreters.get_main()
|
||||||
interp1 = interpreters.create()
|
interp1 = _interpreters.create()
|
||||||
interp2 = interpreters.create()
|
interp2 = _interpreters.create()
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
channels.send(cid, "data", blocking=False)
|
channels.send(cid, "data", blocking=False)
|
||||||
_run_output(interp1, dedent(f"""
|
_run_output(interp1, dedent(f"""
|
||||||
|
@ -494,7 +495,7 @@ def test_channel_list_interpreters_released(self):
|
||||||
|
|
||||||
# Release the main interpreter from the send end.
|
# Release the main interpreter from the send end.
|
||||||
channels.release(cid, send=True)
|
channels.release(cid, send=True)
|
||||||
# Send end should have no associated interpreters.
|
# Send end should have no associated _interpreters.
|
||||||
send_interps = channels.list_interpreters(cid, send=True)
|
send_interps = channels.list_interpreters(cid, send=True)
|
||||||
recv_interps = channels.list_interpreters(cid, send=False)
|
recv_interps = channels.list_interpreters(cid, send=False)
|
||||||
self.assertEqual(len(send_interps), 0)
|
self.assertEqual(len(send_interps), 0)
|
||||||
|
@ -513,8 +514,8 @@ def test_channel_list_interpreters_released(self):
|
||||||
|
|
||||||
def test_channel_list_interpreters_closed(self):
|
def test_channel_list_interpreters_closed(self):
|
||||||
"""Test listing channel interpreters with a closed channel."""
|
"""Test listing channel interpreters with a closed channel."""
|
||||||
interp0 = interpreters.get_main()
|
interp0, *_ = _interpreters.get_main()
|
||||||
interp1 = interpreters.create()
|
interp1 = _interpreters.create()
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
# Put something in the channel so that it's not empty.
|
# Put something in the channel so that it's not empty.
|
||||||
channels.send(cid, "send", blocking=False)
|
channels.send(cid, "send", blocking=False)
|
||||||
|
@ -535,8 +536,8 @@ def test_channel_list_interpreters_closed(self):
|
||||||
|
|
||||||
def test_channel_list_interpreters_closed_send_end(self):
|
def test_channel_list_interpreters_closed_send_end(self):
|
||||||
"""Test listing channel interpreters with a channel's send end closed."""
|
"""Test listing channel interpreters with a channel's send end closed."""
|
||||||
interp0 = interpreters.get_main()
|
interp0, *_ = _interpreters.get_main()
|
||||||
interp1 = interpreters.create()
|
interp1 = _interpreters.create()
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
# Put something in the channel so that it's not empty.
|
# Put something in the channel so that it's not empty.
|
||||||
channels.send(cid, "send", blocking=False)
|
channels.send(cid, "send", blocking=False)
|
||||||
|
@ -589,9 +590,9 @@ def test_allowed_types(self):
|
||||||
|
|
||||||
def test_run_string_arg_unresolved(self):
|
def test_run_string_arg_unresolved(self):
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
interp = interpreters.create()
|
interp = _interpreters.create()
|
||||||
|
|
||||||
interpreters.set___main___attrs(interp, dict(cid=cid.send))
|
_interpreters.set___main___attrs(interp, dict(cid=cid.send))
|
||||||
out = _run_output(interp, dedent("""
|
out = _run_output(interp, dedent("""
|
||||||
import _xxinterpchannels as _channels
|
import _xxinterpchannels as _channels
|
||||||
print(cid.end)
|
print(cid.end)
|
||||||
|
@ -609,7 +610,7 @@ def test_run_string_arg_unresolved(self):
|
||||||
def test_run_string_arg_resolved(self):
|
def test_run_string_arg_resolved(self):
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
cid = channels._channel_id(cid, _resolve=True)
|
cid = channels._channel_id(cid, _resolve=True)
|
||||||
interp = interpreters.create()
|
interp = _interpreters.create()
|
||||||
|
|
||||||
out = _run_output(interp, dedent("""
|
out = _run_output(interp, dedent("""
|
||||||
import _xxinterpchannels as _channels
|
import _xxinterpchannels as _channels
|
||||||
|
@ -635,7 +636,7 @@ def test_send_recv_main(self):
|
||||||
self.assertIsNot(obj, orig)
|
self.assertIsNot(obj, orig)
|
||||||
|
|
||||||
def test_send_recv_same_interpreter(self):
|
def test_send_recv_same_interpreter(self):
|
||||||
id1 = interpreters.create()
|
id1 = _interpreters.create()
|
||||||
out = _run_output(id1, dedent("""
|
out = _run_output(id1, dedent("""
|
||||||
import _xxinterpchannels as _channels
|
import _xxinterpchannels as _channels
|
||||||
cid = _channels.create()
|
cid = _channels.create()
|
||||||
|
@ -648,7 +649,7 @@ def test_send_recv_same_interpreter(self):
|
||||||
|
|
||||||
def test_send_recv_different_interpreters(self):
|
def test_send_recv_different_interpreters(self):
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
id1 = interpreters.create()
|
id1 = _interpreters.create()
|
||||||
out = _run_output(id1, dedent(f"""
|
out = _run_output(id1, dedent(f"""
|
||||||
import _xxinterpchannels as _channels
|
import _xxinterpchannels as _channels
|
||||||
_channels.send({cid}, b'spam', blocking=False)
|
_channels.send({cid}, b'spam', blocking=False)
|
||||||
|
@ -674,7 +675,7 @@ def f():
|
||||||
|
|
||||||
def test_send_recv_different_interpreters_and_threads(self):
|
def test_send_recv_different_interpreters_and_threads(self):
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
id1 = interpreters.create()
|
id1 = _interpreters.create()
|
||||||
out = None
|
out = None
|
||||||
|
|
||||||
def f():
|
def f():
|
||||||
|
@ -737,12 +738,12 @@ def test_recv_default(self):
|
||||||
def test_recv_sending_interp_destroyed(self):
|
def test_recv_sending_interp_destroyed(self):
|
||||||
with self.subTest('closed'):
|
with self.subTest('closed'):
|
||||||
cid1 = channels.create()
|
cid1 = channels.create()
|
||||||
interp = interpreters.create()
|
interp = _interpreters.create()
|
||||||
interpreters.run_string(interp, dedent(f"""
|
_interpreters.run_string(interp, dedent(f"""
|
||||||
import _xxinterpchannels as _channels
|
import _xxinterpchannels as _channels
|
||||||
_channels.send({cid1}, b'spam', blocking=False)
|
_channels.send({cid1}, b'spam', blocking=False)
|
||||||
"""))
|
"""))
|
||||||
interpreters.destroy(interp)
|
_interpreters.destroy(interp)
|
||||||
|
|
||||||
with self.assertRaisesRegex(RuntimeError,
|
with self.assertRaisesRegex(RuntimeError,
|
||||||
f'channel {cid1} is closed'):
|
f'channel {cid1} is closed'):
|
||||||
|
@ -750,13 +751,13 @@ def test_recv_sending_interp_destroyed(self):
|
||||||
del cid1
|
del cid1
|
||||||
with self.subTest('still open'):
|
with self.subTest('still open'):
|
||||||
cid2 = channels.create()
|
cid2 = channels.create()
|
||||||
interp = interpreters.create()
|
interp = _interpreters.create()
|
||||||
interpreters.run_string(interp, dedent(f"""
|
_interpreters.run_string(interp, dedent(f"""
|
||||||
import _xxinterpchannels as _channels
|
import _xxinterpchannels as _channels
|
||||||
_channels.send({cid2}, b'spam', blocking=False)
|
_channels.send({cid2}, b'spam', blocking=False)
|
||||||
"""))
|
"""))
|
||||||
channels.send(cid2, b'eggs', blocking=False)
|
channels.send(cid2, b'eggs', blocking=False)
|
||||||
interpreters.destroy(interp)
|
_interpreters.destroy(interp)
|
||||||
|
|
||||||
channels.recv(cid2)
|
channels.recv(cid2)
|
||||||
with self.assertRaisesRegex(RuntimeError,
|
with self.assertRaisesRegex(RuntimeError,
|
||||||
|
@ -1010,24 +1011,24 @@ def test_close_single_user(self):
|
||||||
|
|
||||||
def test_close_multiple_users(self):
|
def test_close_multiple_users(self):
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
id1 = interpreters.create()
|
id1 = _interpreters.create()
|
||||||
id2 = interpreters.create()
|
id2 = _interpreters.create()
|
||||||
interpreters.run_string(id1, dedent(f"""
|
_interpreters.run_string(id1, dedent(f"""
|
||||||
import _xxinterpchannels as _channels
|
import _xxinterpchannels as _channels
|
||||||
_channels.send({cid}, b'spam', blocking=False)
|
_channels.send({cid}, b'spam', blocking=False)
|
||||||
"""))
|
"""))
|
||||||
interpreters.run_string(id2, dedent(f"""
|
_interpreters.run_string(id2, dedent(f"""
|
||||||
import _xxinterpchannels as _channels
|
import _xxinterpchannels as _channels
|
||||||
_channels.recv({cid})
|
_channels.recv({cid})
|
||||||
"""))
|
"""))
|
||||||
channels.close(cid)
|
channels.close(cid)
|
||||||
|
|
||||||
excsnap = interpreters.run_string(id1, dedent(f"""
|
excsnap = _interpreters.run_string(id1, dedent(f"""
|
||||||
_channels.send({cid}, b'spam')
|
_channels.send({cid}, b'spam')
|
||||||
"""))
|
"""))
|
||||||
self.assertEqual(excsnap.type.__name__, 'ChannelClosedError')
|
self.assertEqual(excsnap.type.__name__, 'ChannelClosedError')
|
||||||
|
|
||||||
excsnap = interpreters.run_string(id2, dedent(f"""
|
excsnap = _interpreters.run_string(id2, dedent(f"""
|
||||||
_channels.send({cid}, b'spam')
|
_channels.send({cid}, b'spam')
|
||||||
"""))
|
"""))
|
||||||
self.assertEqual(excsnap.type.__name__, 'ChannelClosedError')
|
self.assertEqual(excsnap.type.__name__, 'ChannelClosedError')
|
||||||
|
@ -1154,8 +1155,8 @@ def test_close_never_used(self):
|
||||||
def test_close_by_unassociated_interp(self):
|
def test_close_by_unassociated_interp(self):
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
channels.send(cid, b'spam', blocking=False)
|
channels.send(cid, b'spam', blocking=False)
|
||||||
interp = interpreters.create()
|
interp = _interpreters.create()
|
||||||
interpreters.run_string(interp, dedent(f"""
|
_interpreters.run_string(interp, dedent(f"""
|
||||||
import _xxinterpchannels as _channels
|
import _xxinterpchannels as _channels
|
||||||
_channels.close({cid}, force=True)
|
_channels.close({cid}, force=True)
|
||||||
"""))
|
"""))
|
||||||
|
@ -1251,9 +1252,9 @@ def test_single_user(self):
|
||||||
|
|
||||||
def test_multiple_users(self):
|
def test_multiple_users(self):
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
id1 = interpreters.create()
|
id1 = _interpreters.create()
|
||||||
id2 = interpreters.create()
|
id2 = _interpreters.create()
|
||||||
interpreters.run_string(id1, dedent(f"""
|
_interpreters.run_string(id1, dedent(f"""
|
||||||
import _xxinterpchannels as _channels
|
import _xxinterpchannels as _channels
|
||||||
_channels.send({cid}, b'spam', blocking=False)
|
_channels.send({cid}, b'spam', blocking=False)
|
||||||
"""))
|
"""))
|
||||||
|
@ -1263,7 +1264,7 @@ def test_multiple_users(self):
|
||||||
_channels.release({cid})
|
_channels.release({cid})
|
||||||
print(repr(obj))
|
print(repr(obj))
|
||||||
"""))
|
"""))
|
||||||
interpreters.run_string(id1, dedent(f"""
|
_interpreters.run_string(id1, dedent(f"""
|
||||||
_channels.release({cid})
|
_channels.release({cid})
|
||||||
"""))
|
"""))
|
||||||
|
|
||||||
|
@ -1310,8 +1311,8 @@ def test_never_used(self):
|
||||||
def test_by_unassociated_interp(self):
|
def test_by_unassociated_interp(self):
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
channels.send(cid, b'spam', blocking=False)
|
channels.send(cid, b'spam', blocking=False)
|
||||||
interp = interpreters.create()
|
interp = _interpreters.create()
|
||||||
interpreters.run_string(interp, dedent(f"""
|
_interpreters.run_string(interp, dedent(f"""
|
||||||
import _xxinterpchannels as _channels
|
import _xxinterpchannels as _channels
|
||||||
_channels.release({cid})
|
_channels.release({cid})
|
||||||
"""))
|
"""))
|
||||||
|
@ -1325,8 +1326,8 @@ def test_by_unassociated_interp(self):
|
||||||
def test_close_if_unassociated(self):
|
def test_close_if_unassociated(self):
|
||||||
# XXX Something's not right with this test...
|
# XXX Something's not right with this test...
|
||||||
cid = channels.create()
|
cid = channels.create()
|
||||||
interp = interpreters.create()
|
interp = _interpreters.create()
|
||||||
interpreters.run_string(interp, dedent(f"""
|
_interpreters.run_string(interp, dedent(f"""
|
||||||
import _xxinterpchannels as _channels
|
import _xxinterpchannels as _channels
|
||||||
obj = _channels.send({cid}, b'spam', blocking=False)
|
obj = _channels.send({cid}, b'spam', blocking=False)
|
||||||
_channels.release({cid})
|
_channels.release({cid})
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
from test.support import script_helper
|
from test.support import script_helper
|
||||||
|
|
||||||
|
|
||||||
interpreters = import_helper.import_module('_xxsubinterpreters')
|
_interpreters = import_helper.import_module('_xxsubinterpreters')
|
||||||
_testinternalcapi = import_helper.import_module('_testinternalcapi')
|
_testinternalcapi = import_helper.import_module('_testinternalcapi')
|
||||||
from _xxsubinterpreters import InterpreterNotFoundError
|
from _xxsubinterpreters import InterpreterNotFoundError
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ def _captured_script(script):
|
||||||
def _run_output(interp, request):
|
def _run_output(interp, request):
|
||||||
script, rpipe = _captured_script(request)
|
script, rpipe = _captured_script(request)
|
||||||
with rpipe:
|
with rpipe:
|
||||||
interpreters.run_string(interp, script)
|
_interpreters.run_string(interp, script)
|
||||||
return rpipe.read()
|
return rpipe.read()
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ def _wait_for_interp_to_run(interp, timeout=None):
|
||||||
if timeout is None:
|
if timeout is None:
|
||||||
timeout = support.SHORT_TIMEOUT
|
timeout = support.SHORT_TIMEOUT
|
||||||
for _ in support.sleeping_retry(timeout, error=False):
|
for _ in support.sleeping_retry(timeout, error=False):
|
||||||
if interpreters.is_running(interp):
|
if _interpreters.is_running(interp):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise RuntimeError('interp is not running')
|
raise RuntimeError('interp is not running')
|
||||||
|
@ -57,7 +57,7 @@ def _wait_for_interp_to_run(interp, timeout=None):
|
||||||
def _running(interp):
|
def _running(interp):
|
||||||
r, w = os.pipe()
|
r, w = os.pipe()
|
||||||
def run():
|
def run():
|
||||||
interpreters.run_string(interp, dedent(f"""
|
_interpreters.run_string(interp, dedent(f"""
|
||||||
# wait for "signal"
|
# wait for "signal"
|
||||||
with open({r}, encoding="utf-8") as rpipe:
|
with open({r}, encoding="utf-8") as rpipe:
|
||||||
rpipe.read()
|
rpipe.read()
|
||||||
|
@ -75,12 +75,12 @@ def run():
|
||||||
|
|
||||||
|
|
||||||
def clean_up_interpreters():
|
def clean_up_interpreters():
|
||||||
for id in interpreters.list_all():
|
for id, *_ in _interpreters.list_all():
|
||||||
if id == 0: # main
|
if id == 0: # main
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
interpreters.destroy(id)
|
_interpreters.destroy(id)
|
||||||
except interpreters.InterpreterError:
|
except _interpreters.InterpreterError:
|
||||||
pass # already destroyed
|
pass # already destroyed
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ def test_default_shareables(self):
|
||||||
for obj in shareables:
|
for obj in shareables:
|
||||||
with self.subTest(obj):
|
with self.subTest(obj):
|
||||||
self.assertTrue(
|
self.assertTrue(
|
||||||
interpreters.is_shareable(obj))
|
_interpreters.is_shareable(obj))
|
||||||
|
|
||||||
def test_not_shareable(self):
|
def test_not_shareable(self):
|
||||||
class Cheese:
|
class Cheese:
|
||||||
|
@ -141,7 +141,7 @@ class SubBytes(bytes):
|
||||||
for obj in not_shareables:
|
for obj in not_shareables:
|
||||||
with self.subTest(repr(obj)):
|
with self.subTest(repr(obj)):
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
interpreters.is_shareable(obj))
|
_interpreters.is_shareable(obj))
|
||||||
|
|
||||||
|
|
||||||
class ShareableTypeTests(unittest.TestCase):
|
class ShareableTypeTests(unittest.TestCase):
|
||||||
|
@ -230,7 +230,7 @@ class ModuleTests(TestBase):
|
||||||
|
|
||||||
def test_import_in_interpreter(self):
|
def test_import_in_interpreter(self):
|
||||||
_run_output(
|
_run_output(
|
||||||
interpreters.create(),
|
_interpreters.create(),
|
||||||
'import _xxsubinterpreters as _interpreters',
|
'import _xxsubinterpreters as _interpreters',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -241,45 +241,45 @@ def test_import_in_interpreter(self):
|
||||||
class ListAllTests(TestBase):
|
class ListAllTests(TestBase):
|
||||||
|
|
||||||
def test_initial(self):
|
def test_initial(self):
|
||||||
main = interpreters.get_main()
|
main, *_ = _interpreters.get_main()
|
||||||
ids = interpreters.list_all()
|
ids = [id for id, *_ in _interpreters.list_all()]
|
||||||
self.assertEqual(ids, [main])
|
self.assertEqual(ids, [main])
|
||||||
|
|
||||||
def test_after_creating(self):
|
def test_after_creating(self):
|
||||||
main = interpreters.get_main()
|
main, *_ = _interpreters.get_main()
|
||||||
first = interpreters.create()
|
first = _interpreters.create()
|
||||||
second = interpreters.create()
|
second = _interpreters.create()
|
||||||
ids = interpreters.list_all()
|
ids = [id for id, *_ in _interpreters.list_all()]
|
||||||
self.assertEqual(ids, [main, first, second])
|
self.assertEqual(ids, [main, first, second])
|
||||||
|
|
||||||
def test_after_destroying(self):
|
def test_after_destroying(self):
|
||||||
main = interpreters.get_main()
|
main, *_ = _interpreters.get_main()
|
||||||
first = interpreters.create()
|
first = _interpreters.create()
|
||||||
second = interpreters.create()
|
second = _interpreters.create()
|
||||||
interpreters.destroy(first)
|
_interpreters.destroy(first)
|
||||||
ids = interpreters.list_all()
|
ids = [id for id, *_ in _interpreters.list_all()]
|
||||||
self.assertEqual(ids, [main, second])
|
self.assertEqual(ids, [main, second])
|
||||||
|
|
||||||
|
|
||||||
class GetCurrentTests(TestBase):
|
class GetCurrentTests(TestBase):
|
||||||
|
|
||||||
def test_main(self):
|
def test_main(self):
|
||||||
main = interpreters.get_main()
|
main, *_ = _interpreters.get_main()
|
||||||
cur = interpreters.get_current()
|
cur, *_ = _interpreters.get_current()
|
||||||
self.assertEqual(cur, main)
|
self.assertEqual(cur, main)
|
||||||
self.assertIsInstance(cur, int)
|
self.assertIsInstance(cur, int)
|
||||||
|
|
||||||
def test_subinterpreter(self):
|
def test_subinterpreter(self):
|
||||||
main = interpreters.get_main()
|
main, *_ = _interpreters.get_main()
|
||||||
interp = interpreters.create()
|
interp = _interpreters.create()
|
||||||
out = _run_output(interp, dedent("""
|
out = _run_output(interp, dedent("""
|
||||||
import _xxsubinterpreters as _interpreters
|
import _xxsubinterpreters as _interpreters
|
||||||
cur = _interpreters.get_current()
|
cur, *_ = _interpreters.get_current()
|
||||||
print(cur)
|
print(cur)
|
||||||
assert isinstance(cur, int)
|
assert isinstance(cur, int)
|
||||||
"""))
|
"""))
|
||||||
cur = int(out.strip())
|
cur = int(out.strip())
|
||||||
_, expected = interpreters.list_all()
|
_, expected = [id for id, *_ in _interpreters.list_all()]
|
||||||
self.assertEqual(cur, expected)
|
self.assertEqual(cur, expected)
|
||||||
self.assertNotEqual(cur, main)
|
self.assertNotEqual(cur, main)
|
||||||
|
|
||||||
|
@ -287,17 +287,17 @@ def test_subinterpreter(self):
|
||||||
class GetMainTests(TestBase):
|
class GetMainTests(TestBase):
|
||||||
|
|
||||||
def test_from_main(self):
|
def test_from_main(self):
|
||||||
[expected] = interpreters.list_all()
|
[expected] = [id for id, *_ in _interpreters.list_all()]
|
||||||
main = interpreters.get_main()
|
main, *_ = _interpreters.get_main()
|
||||||
self.assertEqual(main, expected)
|
self.assertEqual(main, expected)
|
||||||
self.assertIsInstance(main, int)
|
self.assertIsInstance(main, int)
|
||||||
|
|
||||||
def test_from_subinterpreter(self):
|
def test_from_subinterpreter(self):
|
||||||
[expected] = interpreters.list_all()
|
[expected] = [id for id, *_ in _interpreters.list_all()]
|
||||||
interp = interpreters.create()
|
interp = _interpreters.create()
|
||||||
out = _run_output(interp, dedent("""
|
out = _run_output(interp, dedent("""
|
||||||
import _xxsubinterpreters as _interpreters
|
import _xxsubinterpreters as _interpreters
|
||||||
main = _interpreters.get_main()
|
main, *_ = _interpreters.get_main()
|
||||||
print(main)
|
print(main)
|
||||||
assert isinstance(main, int)
|
assert isinstance(main, int)
|
||||||
"""))
|
"""))
|
||||||
|
@ -308,20 +308,20 @@ def test_from_subinterpreter(self):
|
||||||
class IsRunningTests(TestBase):
|
class IsRunningTests(TestBase):
|
||||||
|
|
||||||
def test_main(self):
|
def test_main(self):
|
||||||
main = interpreters.get_main()
|
main, *_ = _interpreters.get_main()
|
||||||
self.assertTrue(interpreters.is_running(main))
|
self.assertTrue(_interpreters.is_running(main))
|
||||||
|
|
||||||
@unittest.skip('Fails on FreeBSD')
|
@unittest.skip('Fails on FreeBSD')
|
||||||
def test_subinterpreter(self):
|
def test_subinterpreter(self):
|
||||||
interp = interpreters.create()
|
interp = _interpreters.create()
|
||||||
self.assertFalse(interpreters.is_running(interp))
|
self.assertFalse(_interpreters.is_running(interp))
|
||||||
|
|
||||||
with _running(interp):
|
with _running(interp):
|
||||||
self.assertTrue(interpreters.is_running(interp))
|
self.assertTrue(_interpreters.is_running(interp))
|
||||||
self.assertFalse(interpreters.is_running(interp))
|
self.assertFalse(_interpreters.is_running(interp))
|
||||||
|
|
||||||
def test_from_subinterpreter(self):
|
def test_from_subinterpreter(self):
|
||||||
interp = interpreters.create()
|
interp = _interpreters.create()
|
||||||
out = _run_output(interp, dedent(f"""
|
out = _run_output(interp, dedent(f"""
|
||||||
import _xxsubinterpreters as _interpreters
|
import _xxsubinterpreters as _interpreters
|
||||||
if _interpreters.is_running({interp}):
|
if _interpreters.is_running({interp}):
|
||||||
|
@ -332,34 +332,35 @@ def test_from_subinterpreter(self):
|
||||||
self.assertEqual(out.strip(), 'True')
|
self.assertEqual(out.strip(), 'True')
|
||||||
|
|
||||||
def test_already_destroyed(self):
|
def test_already_destroyed(self):
|
||||||
interp = interpreters.create()
|
interp = _interpreters.create()
|
||||||
interpreters.destroy(interp)
|
_interpreters.destroy(interp)
|
||||||
with self.assertRaises(InterpreterNotFoundError):
|
with self.assertRaises(InterpreterNotFoundError):
|
||||||
interpreters.is_running(interp)
|
_interpreters.is_running(interp)
|
||||||
|
|
||||||
def test_does_not_exist(self):
|
def test_does_not_exist(self):
|
||||||
with self.assertRaises(InterpreterNotFoundError):
|
with self.assertRaises(InterpreterNotFoundError):
|
||||||
interpreters.is_running(1_000_000)
|
_interpreters.is_running(1_000_000)
|
||||||
|
|
||||||
def test_bad_id(self):
|
def test_bad_id(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
interpreters.is_running(-1)
|
_interpreters.is_running(-1)
|
||||||
|
|
||||||
|
|
||||||
class CreateTests(TestBase):
|
class CreateTests(TestBase):
|
||||||
|
|
||||||
def test_in_main(self):
|
def test_in_main(self):
|
||||||
id = interpreters.create()
|
id = _interpreters.create()
|
||||||
self.assertIsInstance(id, int)
|
self.assertIsInstance(id, int)
|
||||||
|
|
||||||
self.assertIn(id, interpreters.list_all())
|
after = [id for id, *_ in _interpreters.list_all()]
|
||||||
|
self.assertIn(id, after)
|
||||||
|
|
||||||
@unittest.skip('enable this test when working on pystate.c')
|
@unittest.skip('enable this test when working on pystate.c')
|
||||||
def test_unique_id(self):
|
def test_unique_id(self):
|
||||||
seen = set()
|
seen = set()
|
||||||
for _ in range(100):
|
for _ in range(100):
|
||||||
id = interpreters.create()
|
id = _interpreters.create()
|
||||||
interpreters.destroy(id)
|
_interpreters.destroy(id)
|
||||||
seen.add(id)
|
seen.add(id)
|
||||||
|
|
||||||
self.assertEqual(len(seen), 100)
|
self.assertEqual(len(seen), 100)
|
||||||
|
@ -369,7 +370,7 @@ def test_in_thread(self):
|
||||||
id = None
|
id = None
|
||||||
def f():
|
def f():
|
||||||
nonlocal id
|
nonlocal id
|
||||||
id = interpreters.create()
|
id = _interpreters.create()
|
||||||
lock.acquire()
|
lock.acquire()
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|
||||||
|
@ -377,11 +378,12 @@ def f():
|
||||||
with lock:
|
with lock:
|
||||||
t.start()
|
t.start()
|
||||||
t.join()
|
t.join()
|
||||||
self.assertIn(id, interpreters.list_all())
|
after = set(id for id, *_ in _interpreters.list_all())
|
||||||
|
self.assertIn(id, after)
|
||||||
|
|
||||||
def test_in_subinterpreter(self):
|
def test_in_subinterpreter(self):
|
||||||
main, = interpreters.list_all()
|
main, = [id for id, *_ in _interpreters.list_all()]
|
||||||
id1 = interpreters.create()
|
id1 = _interpreters.create()
|
||||||
out = _run_output(id1, dedent("""
|
out = _run_output(id1, dedent("""
|
||||||
import _xxsubinterpreters as _interpreters
|
import _xxsubinterpreters as _interpreters
|
||||||
id = _interpreters.create()
|
id = _interpreters.create()
|
||||||
|
@ -390,11 +392,12 @@ def test_in_subinterpreter(self):
|
||||||
"""))
|
"""))
|
||||||
id2 = int(out.strip())
|
id2 = int(out.strip())
|
||||||
|
|
||||||
self.assertEqual(set(interpreters.list_all()), {main, id1, id2})
|
after = set(id for id, *_ in _interpreters.list_all())
|
||||||
|
self.assertEqual(after, {main, id1, id2})
|
||||||
|
|
||||||
def test_in_threaded_subinterpreter(self):
|
def test_in_threaded_subinterpreter(self):
|
||||||
main, = interpreters.list_all()
|
main, = [id for id, *_ in _interpreters.list_all()]
|
||||||
id1 = interpreters.create()
|
id1 = _interpreters.create()
|
||||||
id2 = None
|
id2 = None
|
||||||
def f():
|
def f():
|
||||||
nonlocal id2
|
nonlocal id2
|
||||||
|
@ -409,144 +412,155 @@ def f():
|
||||||
t.start()
|
t.start()
|
||||||
t.join()
|
t.join()
|
||||||
|
|
||||||
self.assertEqual(set(interpreters.list_all()), {main, id1, id2})
|
after = set(id for id, *_ in _interpreters.list_all())
|
||||||
|
self.assertEqual(after, {main, id1, id2})
|
||||||
|
|
||||||
def test_after_destroy_all(self):
|
def test_after_destroy_all(self):
|
||||||
before = set(interpreters.list_all())
|
before = set(id for id, *_ in _interpreters.list_all())
|
||||||
# Create 3 subinterpreters.
|
# Create 3 subinterpreters.
|
||||||
ids = []
|
ids = []
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
id = interpreters.create()
|
id = _interpreters.create()
|
||||||
ids.append(id)
|
ids.append(id)
|
||||||
# Now destroy them.
|
# Now destroy them.
|
||||||
for id in ids:
|
for id in ids:
|
||||||
interpreters.destroy(id)
|
_interpreters.destroy(id)
|
||||||
# Finally, create another.
|
# Finally, create another.
|
||||||
id = interpreters.create()
|
id = _interpreters.create()
|
||||||
self.assertEqual(set(interpreters.list_all()), before | {id})
|
after = set(id for id, *_ in _interpreters.list_all())
|
||||||
|
self.assertEqual(after, before | {id})
|
||||||
|
|
||||||
def test_after_destroy_some(self):
|
def test_after_destroy_some(self):
|
||||||
before = set(interpreters.list_all())
|
before = set(id for id, *_ in _interpreters.list_all())
|
||||||
# Create 3 subinterpreters.
|
# Create 3 subinterpreters.
|
||||||
id1 = interpreters.create()
|
id1 = _interpreters.create()
|
||||||
id2 = interpreters.create()
|
id2 = _interpreters.create()
|
||||||
id3 = interpreters.create()
|
id3 = _interpreters.create()
|
||||||
# Now destroy 2 of them.
|
# Now destroy 2 of them.
|
||||||
interpreters.destroy(id1)
|
_interpreters.destroy(id1)
|
||||||
interpreters.destroy(id3)
|
_interpreters.destroy(id3)
|
||||||
# Finally, create another.
|
# Finally, create another.
|
||||||
id = interpreters.create()
|
id = _interpreters.create()
|
||||||
self.assertEqual(set(interpreters.list_all()), before | {id, id2})
|
after = set(id for id, *_ in _interpreters.list_all())
|
||||||
|
self.assertEqual(after, before | {id, id2})
|
||||||
|
|
||||||
|
|
||||||
class DestroyTests(TestBase):
|
class DestroyTests(TestBase):
|
||||||
|
|
||||||
def test_one(self):
|
def test_one(self):
|
||||||
id1 = interpreters.create()
|
id1 = _interpreters.create()
|
||||||
id2 = interpreters.create()
|
id2 = _interpreters.create()
|
||||||
id3 = interpreters.create()
|
id3 = _interpreters.create()
|
||||||
self.assertIn(id2, interpreters.list_all())
|
before = set(id for id, *_ in _interpreters.list_all())
|
||||||
interpreters.destroy(id2)
|
self.assertIn(id2, before)
|
||||||
self.assertNotIn(id2, interpreters.list_all())
|
|
||||||
self.assertIn(id1, interpreters.list_all())
|
_interpreters.destroy(id2)
|
||||||
self.assertIn(id3, interpreters.list_all())
|
|
||||||
|
after = set(id for id, *_ in _interpreters.list_all())
|
||||||
|
self.assertNotIn(id2, after)
|
||||||
|
self.assertIn(id1, after)
|
||||||
|
self.assertIn(id3, after)
|
||||||
|
|
||||||
def test_all(self):
|
def test_all(self):
|
||||||
before = set(interpreters.list_all())
|
initial = set(id for id, *_ in _interpreters.list_all())
|
||||||
ids = set()
|
ids = set()
|
||||||
for _ in range(3):
|
for _ in range(3):
|
||||||
id = interpreters.create()
|
id = _interpreters.create()
|
||||||
ids.add(id)
|
ids.add(id)
|
||||||
self.assertEqual(set(interpreters.list_all()), before | ids)
|
before = set(id for id, *_ in _interpreters.list_all())
|
||||||
|
self.assertEqual(before, initial | ids)
|
||||||
for id in ids:
|
for id in ids:
|
||||||
interpreters.destroy(id)
|
_interpreters.destroy(id)
|
||||||
self.assertEqual(set(interpreters.list_all()), before)
|
after = set(id for id, *_ in _interpreters.list_all())
|
||||||
|
self.assertEqual(after, initial)
|
||||||
|
|
||||||
def test_main(self):
|
def test_main(self):
|
||||||
main, = interpreters.list_all()
|
main, = [id for id, *_ in _interpreters.list_all()]
|
||||||
with self.assertRaises(interpreters.InterpreterError):
|
with self.assertRaises(_interpreters.InterpreterError):
|
||||||
interpreters.destroy(main)
|
_interpreters.destroy(main)
|
||||||
|
|
||||||
def f():
|
def f():
|
||||||
with self.assertRaises(interpreters.InterpreterError):
|
with self.assertRaises(_interpreters.InterpreterError):
|
||||||
interpreters.destroy(main)
|
_interpreters.destroy(main)
|
||||||
|
|
||||||
t = threading.Thread(target=f)
|
t = threading.Thread(target=f)
|
||||||
t.start()
|
t.start()
|
||||||
t.join()
|
t.join()
|
||||||
|
|
||||||
def test_already_destroyed(self):
|
def test_already_destroyed(self):
|
||||||
id = interpreters.create()
|
id = _interpreters.create()
|
||||||
interpreters.destroy(id)
|
_interpreters.destroy(id)
|
||||||
with self.assertRaises(InterpreterNotFoundError):
|
with self.assertRaises(InterpreterNotFoundError):
|
||||||
interpreters.destroy(id)
|
_interpreters.destroy(id)
|
||||||
|
|
||||||
def test_does_not_exist(self):
|
def test_does_not_exist(self):
|
||||||
with self.assertRaises(InterpreterNotFoundError):
|
with self.assertRaises(InterpreterNotFoundError):
|
||||||
interpreters.destroy(1_000_000)
|
_interpreters.destroy(1_000_000)
|
||||||
|
|
||||||
def test_bad_id(self):
|
def test_bad_id(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
interpreters.destroy(-1)
|
_interpreters.destroy(-1)
|
||||||
|
|
||||||
def test_from_current(self):
|
def test_from_current(self):
|
||||||
main, = interpreters.list_all()
|
main, = [id for id, *_ in _interpreters.list_all()]
|
||||||
id = interpreters.create()
|
id = _interpreters.create()
|
||||||
script = dedent(f"""
|
script = dedent(f"""
|
||||||
import _xxsubinterpreters as _interpreters
|
import _xxsubinterpreters as _interpreters
|
||||||
try:
|
try:
|
||||||
_interpreters.destroy({id})
|
_interpreters.destroy({id})
|
||||||
except interpreters.InterpreterError:
|
except _interpreters.InterpreterError:
|
||||||
pass
|
pass
|
||||||
""")
|
""")
|
||||||
|
|
||||||
interpreters.run_string(id, script)
|
_interpreters.run_string(id, script)
|
||||||
self.assertEqual(set(interpreters.list_all()), {main, id})
|
after = set(id for id, *_ in _interpreters.list_all())
|
||||||
|
self.assertEqual(after, {main, id})
|
||||||
|
|
||||||
def test_from_sibling(self):
|
def test_from_sibling(self):
|
||||||
main, = interpreters.list_all()
|
main, = [id for id, *_ in _interpreters.list_all()]
|
||||||
id1 = interpreters.create()
|
id1 = _interpreters.create()
|
||||||
id2 = interpreters.create()
|
id2 = _interpreters.create()
|
||||||
script = dedent(f"""
|
script = dedent(f"""
|
||||||
import _xxsubinterpreters as _interpreters
|
import _xxsubinterpreters as _interpreters
|
||||||
_interpreters.destroy({id2})
|
_interpreters.destroy({id2})
|
||||||
""")
|
""")
|
||||||
interpreters.run_string(id1, script)
|
_interpreters.run_string(id1, script)
|
||||||
|
|
||||||
self.assertEqual(set(interpreters.list_all()), {main, id1})
|
after = set(id for id, *_ in _interpreters.list_all())
|
||||||
|
self.assertEqual(after, {main, id1})
|
||||||
|
|
||||||
def test_from_other_thread(self):
|
def test_from_other_thread(self):
|
||||||
id = interpreters.create()
|
id = _interpreters.create()
|
||||||
def f():
|
def f():
|
||||||
interpreters.destroy(id)
|
_interpreters.destroy(id)
|
||||||
|
|
||||||
t = threading.Thread(target=f)
|
t = threading.Thread(target=f)
|
||||||
t.start()
|
t.start()
|
||||||
t.join()
|
t.join()
|
||||||
|
|
||||||
def test_still_running(self):
|
def test_still_running(self):
|
||||||
main, = interpreters.list_all()
|
main, = [id for id, *_ in _interpreters.list_all()]
|
||||||
interp = interpreters.create()
|
interp = _interpreters.create()
|
||||||
with _running(interp):
|
with _running(interp):
|
||||||
self.assertTrue(interpreters.is_running(interp),
|
self.assertTrue(_interpreters.is_running(interp),
|
||||||
msg=f"Interp {interp} should be running before destruction.")
|
msg=f"Interp {interp} should be running before destruction.")
|
||||||
|
|
||||||
with self.assertRaises(interpreters.InterpreterError,
|
with self.assertRaises(_interpreters.InterpreterError,
|
||||||
msg=f"Should not be able to destroy interp {interp} while it's still running."):
|
msg=f"Should not be able to destroy interp {interp} while it's still running."):
|
||||||
interpreters.destroy(interp)
|
_interpreters.destroy(interp)
|
||||||
self.assertTrue(interpreters.is_running(interp))
|
self.assertTrue(_interpreters.is_running(interp))
|
||||||
|
|
||||||
|
|
||||||
class RunStringTests(TestBase):
|
class RunStringTests(TestBase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.id = interpreters.create()
|
self.id = _interpreters.create()
|
||||||
|
|
||||||
def test_success(self):
|
def test_success(self):
|
||||||
script, file = _captured_script('print("it worked!", end="")')
|
script, file = _captured_script('print("it worked!", end="")')
|
||||||
with file:
|
with file:
|
||||||
interpreters.run_string(self.id, script)
|
_interpreters.run_string(self.id, script)
|
||||||
out = file.read()
|
out = file.read()
|
||||||
|
|
||||||
self.assertEqual(out, 'it worked!')
|
self.assertEqual(out, 'it worked!')
|
||||||
|
@ -555,7 +569,7 @@ def test_in_thread(self):
|
||||||
script, file = _captured_script('print("it worked!", end="")')
|
script, file = _captured_script('print("it worked!", end="")')
|
||||||
with file:
|
with file:
|
||||||
def f():
|
def f():
|
||||||
interpreters.run_string(self.id, script)
|
_interpreters.run_string(self.id, script)
|
||||||
|
|
||||||
t = threading.Thread(target=f)
|
t = threading.Thread(target=f)
|
||||||
t.start()
|
t.start()
|
||||||
|
@ -565,7 +579,7 @@ def f():
|
||||||
self.assertEqual(out, 'it worked!')
|
self.assertEqual(out, 'it worked!')
|
||||||
|
|
||||||
def test_create_thread(self):
|
def test_create_thread(self):
|
||||||
subinterp = interpreters.create()
|
subinterp = _interpreters.create()
|
||||||
script, file = _captured_script("""
|
script, file = _captured_script("""
|
||||||
import threading
|
import threading
|
||||||
def f():
|
def f():
|
||||||
|
@ -576,7 +590,7 @@ def f():
|
||||||
t.join()
|
t.join()
|
||||||
""")
|
""")
|
||||||
with file:
|
with file:
|
||||||
interpreters.run_string(subinterp, script)
|
_interpreters.run_string(subinterp, script)
|
||||||
out = file.read()
|
out = file.read()
|
||||||
|
|
||||||
self.assertEqual(out, 'it worked!')
|
self.assertEqual(out, 'it worked!')
|
||||||
|
@ -584,7 +598,7 @@ def f():
|
||||||
def test_create_daemon_thread(self):
|
def test_create_daemon_thread(self):
|
||||||
with self.subTest('isolated'):
|
with self.subTest('isolated'):
|
||||||
expected = 'spam spam spam spam spam'
|
expected = 'spam spam spam spam spam'
|
||||||
subinterp = interpreters.create('isolated')
|
subinterp = _interpreters.create('isolated')
|
||||||
script, file = _captured_script(f"""
|
script, file = _captured_script(f"""
|
||||||
import threading
|
import threading
|
||||||
def f():
|
def f():
|
||||||
|
@ -598,13 +612,13 @@ def f():
|
||||||
print('{expected}', end='')
|
print('{expected}', end='')
|
||||||
""")
|
""")
|
||||||
with file:
|
with file:
|
||||||
interpreters.run_string(subinterp, script)
|
_interpreters.run_string(subinterp, script)
|
||||||
out = file.read()
|
out = file.read()
|
||||||
|
|
||||||
self.assertEqual(out, expected)
|
self.assertEqual(out, expected)
|
||||||
|
|
||||||
with self.subTest('not isolated'):
|
with self.subTest('not isolated'):
|
||||||
subinterp = interpreters.create('legacy')
|
subinterp = _interpreters.create('legacy')
|
||||||
script, file = _captured_script("""
|
script, file = _captured_script("""
|
||||||
import threading
|
import threading
|
||||||
def f():
|
def f():
|
||||||
|
@ -615,13 +629,13 @@ def f():
|
||||||
t.join()
|
t.join()
|
||||||
""")
|
""")
|
||||||
with file:
|
with file:
|
||||||
interpreters.run_string(subinterp, script)
|
_interpreters.run_string(subinterp, script)
|
||||||
out = file.read()
|
out = file.read()
|
||||||
|
|
||||||
self.assertEqual(out, 'it worked!')
|
self.assertEqual(out, 'it worked!')
|
||||||
|
|
||||||
def test_shareable_types(self):
|
def test_shareable_types(self):
|
||||||
interp = interpreters.create()
|
interp = _interpreters.create()
|
||||||
objects = [
|
objects = [
|
||||||
None,
|
None,
|
||||||
'spam',
|
'spam',
|
||||||
|
@ -630,15 +644,15 @@ def test_shareable_types(self):
|
||||||
]
|
]
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
with self.subTest(obj):
|
with self.subTest(obj):
|
||||||
interpreters.set___main___attrs(interp, dict(obj=obj))
|
_interpreters.set___main___attrs(interp, dict(obj=obj))
|
||||||
interpreters.run_string(
|
_interpreters.run_string(
|
||||||
interp,
|
interp,
|
||||||
f'assert(obj == {obj!r})',
|
f'assert(obj == {obj!r})',
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_os_exec(self):
|
def test_os_exec(self):
|
||||||
expected = 'spam spam spam spam spam'
|
expected = 'spam spam spam spam spam'
|
||||||
subinterp = interpreters.create()
|
subinterp = _interpreters.create()
|
||||||
script, file = _captured_script(f"""
|
script, file = _captured_script(f"""
|
||||||
import os, sys
|
import os, sys
|
||||||
try:
|
try:
|
||||||
|
@ -647,7 +661,7 @@ def test_os_exec(self):
|
||||||
print('{expected}', end='')
|
print('{expected}', end='')
|
||||||
""")
|
""")
|
||||||
with file:
|
with file:
|
||||||
interpreters.run_string(subinterp, script)
|
_interpreters.run_string(subinterp, script)
|
||||||
out = file.read()
|
out = file.read()
|
||||||
|
|
||||||
self.assertEqual(out, expected)
|
self.assertEqual(out, expected)
|
||||||
|
@ -668,7 +682,7 @@ def test_fork(self):
|
||||||
with open('{file.name}', 'w', encoding='utf-8') as out:
|
with open('{file.name}', 'w', encoding='utf-8') as out:
|
||||||
out.write('{expected}')
|
out.write('{expected}')
|
||||||
""")
|
""")
|
||||||
interpreters.run_string(self.id, script)
|
_interpreters.run_string(self.id, script)
|
||||||
|
|
||||||
file.seek(0)
|
file.seek(0)
|
||||||
content = file.read()
|
content = file.read()
|
||||||
|
@ -676,31 +690,31 @@ def test_fork(self):
|
||||||
|
|
||||||
def test_already_running(self):
|
def test_already_running(self):
|
||||||
with _running(self.id):
|
with _running(self.id):
|
||||||
with self.assertRaises(interpreters.InterpreterError):
|
with self.assertRaises(_interpreters.InterpreterError):
|
||||||
interpreters.run_string(self.id, 'print("spam")')
|
_interpreters.run_string(self.id, 'print("spam")')
|
||||||
|
|
||||||
def test_does_not_exist(self):
|
def test_does_not_exist(self):
|
||||||
id = 0
|
id = 0
|
||||||
while id in interpreters.list_all():
|
while id in set(id for id, *_ in _interpreters.list_all()):
|
||||||
id += 1
|
id += 1
|
||||||
with self.assertRaises(InterpreterNotFoundError):
|
with self.assertRaises(InterpreterNotFoundError):
|
||||||
interpreters.run_string(id, 'print("spam")')
|
_interpreters.run_string(id, 'print("spam")')
|
||||||
|
|
||||||
def test_error_id(self):
|
def test_error_id(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
interpreters.run_string(-1, 'print("spam")')
|
_interpreters.run_string(-1, 'print("spam")')
|
||||||
|
|
||||||
def test_bad_id(self):
|
def test_bad_id(self):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
interpreters.run_string('spam', 'print("spam")')
|
_interpreters.run_string('spam', 'print("spam")')
|
||||||
|
|
||||||
def test_bad_script(self):
|
def test_bad_script(self):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
interpreters.run_string(self.id, 10)
|
_interpreters.run_string(self.id, 10)
|
||||||
|
|
||||||
def test_bytes_for_script(self):
|
def test_bytes_for_script(self):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
interpreters.run_string(self.id, b'print("spam")')
|
_interpreters.run_string(self.id, b'print("spam")')
|
||||||
|
|
||||||
def test_with_shared(self):
|
def test_with_shared(self):
|
||||||
r, w = os.pipe()
|
r, w = os.pipe()
|
||||||
|
@ -721,8 +735,8 @@ def test_with_shared(self):
|
||||||
with open({w}, 'wb') as chan:
|
with open({w}, 'wb') as chan:
|
||||||
pickle.dump(ns, chan)
|
pickle.dump(ns, chan)
|
||||||
""")
|
""")
|
||||||
interpreters.set___main___attrs(self.id, shared)
|
_interpreters.set___main___attrs(self.id, shared)
|
||||||
interpreters.run_string(self.id, script)
|
_interpreters.run_string(self.id, script)
|
||||||
with open(r, 'rb') as chan:
|
with open(r, 'rb') as chan:
|
||||||
ns = pickle.load(chan)
|
ns = pickle.load(chan)
|
||||||
|
|
||||||
|
@ -732,7 +746,7 @@ def test_with_shared(self):
|
||||||
self.assertIsNone(ns['cheddar'])
|
self.assertIsNone(ns['cheddar'])
|
||||||
|
|
||||||
def test_shared_overwrites(self):
|
def test_shared_overwrites(self):
|
||||||
interpreters.run_string(self.id, dedent("""
|
_interpreters.run_string(self.id, dedent("""
|
||||||
spam = 'eggs'
|
spam = 'eggs'
|
||||||
ns1 = dict(vars())
|
ns1 = dict(vars())
|
||||||
del ns1['__builtins__']
|
del ns1['__builtins__']
|
||||||
|
@ -743,8 +757,8 @@ def test_shared_overwrites(self):
|
||||||
ns2 = dict(vars())
|
ns2 = dict(vars())
|
||||||
del ns2['__builtins__']
|
del ns2['__builtins__']
|
||||||
""")
|
""")
|
||||||
interpreters.set___main___attrs(self.id, shared)
|
_interpreters.set___main___attrs(self.id, shared)
|
||||||
interpreters.run_string(self.id, script)
|
_interpreters.run_string(self.id, script)
|
||||||
|
|
||||||
r, w = os.pipe()
|
r, w = os.pipe()
|
||||||
script = dedent(f"""
|
script = dedent(f"""
|
||||||
|
@ -754,7 +768,7 @@ def test_shared_overwrites(self):
|
||||||
with open({w}, 'wb') as chan:
|
with open({w}, 'wb') as chan:
|
||||||
pickle.dump(ns, chan)
|
pickle.dump(ns, chan)
|
||||||
""")
|
""")
|
||||||
interpreters.run_string(self.id, script)
|
_interpreters.run_string(self.id, script)
|
||||||
with open(r, 'rb') as chan:
|
with open(r, 'rb') as chan:
|
||||||
ns = pickle.load(chan)
|
ns = pickle.load(chan)
|
||||||
|
|
||||||
|
@ -775,8 +789,8 @@ def test_shared_overwrites_default_vars(self):
|
||||||
with open({w}, 'wb') as chan:
|
with open({w}, 'wb') as chan:
|
||||||
pickle.dump(ns, chan)
|
pickle.dump(ns, chan)
|
||||||
""")
|
""")
|
||||||
interpreters.set___main___attrs(self.id, shared)
|
_interpreters.set___main___attrs(self.id, shared)
|
||||||
interpreters.run_string(self.id, script)
|
_interpreters.run_string(self.id, script)
|
||||||
with open(r, 'rb') as chan:
|
with open(r, 'rb') as chan:
|
||||||
ns = pickle.load(chan)
|
ns = pickle.load(chan)
|
||||||
|
|
||||||
|
@ -784,7 +798,7 @@ def test_shared_overwrites_default_vars(self):
|
||||||
|
|
||||||
def test_main_reused(self):
|
def test_main_reused(self):
|
||||||
r, w = os.pipe()
|
r, w = os.pipe()
|
||||||
interpreters.run_string(self.id, dedent(f"""
|
_interpreters.run_string(self.id, dedent(f"""
|
||||||
spam = True
|
spam = True
|
||||||
|
|
||||||
ns = dict(vars())
|
ns = dict(vars())
|
||||||
|
@ -798,7 +812,7 @@ def test_main_reused(self):
|
||||||
ns1 = pickle.load(chan)
|
ns1 = pickle.load(chan)
|
||||||
|
|
||||||
r, w = os.pipe()
|
r, w = os.pipe()
|
||||||
interpreters.run_string(self.id, dedent(f"""
|
_interpreters.run_string(self.id, dedent(f"""
|
||||||
eggs = False
|
eggs = False
|
||||||
|
|
||||||
ns = dict(vars())
|
ns = dict(vars())
|
||||||
|
@ -827,7 +841,7 @@ def test_execution_namespace_is_main(self):
|
||||||
with open({w}, 'wb') as chan:
|
with open({w}, 'wb') as chan:
|
||||||
pickle.dump(ns, chan)
|
pickle.dump(ns, chan)
|
||||||
""")
|
""")
|
||||||
interpreters.run_string(self.id, script)
|
_interpreters.run_string(self.id, script)
|
||||||
with open(r, 'rb') as chan:
|
with open(r, 'rb') as chan:
|
||||||
ns = pickle.load(chan)
|
ns = pickle.load(chan)
|
||||||
|
|
||||||
|
@ -872,13 +886,13 @@ class RunFailedTests(TestBase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.id = interpreters.create()
|
self.id = _interpreters.create()
|
||||||
|
|
||||||
def add_module(self, modname, text):
|
def add_module(self, modname, text):
|
||||||
import tempfile
|
import tempfile
|
||||||
tempdir = tempfile.mkdtemp()
|
tempdir = tempfile.mkdtemp()
|
||||||
self.addCleanup(lambda: os_helper.rmtree(tempdir))
|
self.addCleanup(lambda: os_helper.rmtree(tempdir))
|
||||||
interpreters.run_string(self.id, dedent(f"""
|
_interpreters.run_string(self.id, dedent(f"""
|
||||||
import sys
|
import sys
|
||||||
sys.path.insert(0, {tempdir!r})
|
sys.path.insert(0, {tempdir!r})
|
||||||
"""))
|
"""))
|
||||||
|
@ -900,11 +914,11 @@ class NeverError(Exception): pass
|
||||||
raise NeverError # never raised
|
raise NeverError # never raised
|
||||||
""").format(dedent(text))
|
""").format(dedent(text))
|
||||||
if fails:
|
if fails:
|
||||||
err = interpreters.run_string(self.id, script)
|
err = _interpreters.run_string(self.id, script)
|
||||||
self.assertIsNot(err, None)
|
self.assertIsNot(err, None)
|
||||||
return err
|
return err
|
||||||
else:
|
else:
|
||||||
err = interpreters.run_string(self.id, script)
|
err = _interpreters.run_string(self.id, script)
|
||||||
self.assertIs(err, None)
|
self.assertIs(err, None)
|
||||||
return None
|
return None
|
||||||
except:
|
except:
|
||||||
|
@ -1029,7 +1043,7 @@ class RunFuncTests(TestBase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.id = interpreters.create()
|
self.id = _interpreters.create()
|
||||||
|
|
||||||
def test_success(self):
|
def test_success(self):
|
||||||
r, w = os.pipe()
|
r, w = os.pipe()
|
||||||
|
@ -1039,8 +1053,8 @@ def script():
|
||||||
with open(w, 'w', encoding="utf-8") as spipe:
|
with open(w, 'w', encoding="utf-8") as spipe:
|
||||||
with contextlib.redirect_stdout(spipe):
|
with contextlib.redirect_stdout(spipe):
|
||||||
print('it worked!', end='')
|
print('it worked!', end='')
|
||||||
interpreters.set___main___attrs(self.id, dict(w=w))
|
_interpreters.set___main___attrs(self.id, dict(w=w))
|
||||||
interpreters.run_func(self.id, script)
|
_interpreters.run_func(self.id, script)
|
||||||
|
|
||||||
with open(r, encoding="utf-8") as outfile:
|
with open(r, encoding="utf-8") as outfile:
|
||||||
out = outfile.read()
|
out = outfile.read()
|
||||||
|
@ -1056,8 +1070,8 @@ def script():
|
||||||
with contextlib.redirect_stdout(spipe):
|
with contextlib.redirect_stdout(spipe):
|
||||||
print('it worked!', end='')
|
print('it worked!', end='')
|
||||||
def f():
|
def f():
|
||||||
interpreters.set___main___attrs(self.id, dict(w=w))
|
_interpreters.set___main___attrs(self.id, dict(w=w))
|
||||||
interpreters.run_func(self.id, script)
|
_interpreters.run_func(self.id, script)
|
||||||
t = threading.Thread(target=f)
|
t = threading.Thread(target=f)
|
||||||
t.start()
|
t.start()
|
||||||
t.join()
|
t.join()
|
||||||
|
@ -1077,8 +1091,8 @@ def script():
|
||||||
with contextlib.redirect_stdout(spipe):
|
with contextlib.redirect_stdout(spipe):
|
||||||
print('it worked!', end='')
|
print('it worked!', end='')
|
||||||
code = script.__code__
|
code = script.__code__
|
||||||
interpreters.set___main___attrs(self.id, dict(w=w))
|
_interpreters.set___main___attrs(self.id, dict(w=w))
|
||||||
interpreters.run_func(self.id, code)
|
_interpreters.run_func(self.id, code)
|
||||||
|
|
||||||
with open(r, encoding="utf-8") as outfile:
|
with open(r, encoding="utf-8") as outfile:
|
||||||
out = outfile.read()
|
out = outfile.read()
|
||||||
|
@ -1091,7 +1105,7 @@ def script():
|
||||||
assert spam
|
assert spam
|
||||||
|
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
interpreters.run_func(self.id, script)
|
_interpreters.run_func(self.id, script)
|
||||||
|
|
||||||
# XXX This hasn't been fixed yet.
|
# XXX This hasn't been fixed yet.
|
||||||
@unittest.expectedFailure
|
@unittest.expectedFailure
|
||||||
|
@ -1099,38 +1113,38 @@ def test_return_value(self):
|
||||||
def script():
|
def script():
|
||||||
return 'spam'
|
return 'spam'
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
interpreters.run_func(self.id, script)
|
_interpreters.run_func(self.id, script)
|
||||||
|
|
||||||
def test_args(self):
|
def test_args(self):
|
||||||
with self.subTest('args'):
|
with self.subTest('args'):
|
||||||
def script(a, b=0):
|
def script(a, b=0):
|
||||||
assert a == b
|
assert a == b
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
interpreters.run_func(self.id, script)
|
_interpreters.run_func(self.id, script)
|
||||||
|
|
||||||
with self.subTest('*args'):
|
with self.subTest('*args'):
|
||||||
def script(*args):
|
def script(*args):
|
||||||
assert not args
|
assert not args
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
interpreters.run_func(self.id, script)
|
_interpreters.run_func(self.id, script)
|
||||||
|
|
||||||
with self.subTest('**kwargs'):
|
with self.subTest('**kwargs'):
|
||||||
def script(**kwargs):
|
def script(**kwargs):
|
||||||
assert not kwargs
|
assert not kwargs
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
interpreters.run_func(self.id, script)
|
_interpreters.run_func(self.id, script)
|
||||||
|
|
||||||
with self.subTest('kwonly'):
|
with self.subTest('kwonly'):
|
||||||
def script(*, spam=True):
|
def script(*, spam=True):
|
||||||
assert spam
|
assert spam
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
interpreters.run_func(self.id, script)
|
_interpreters.run_func(self.id, script)
|
||||||
|
|
||||||
with self.subTest('posonly'):
|
with self.subTest('posonly'):
|
||||||
def script(spam, /):
|
def script(spam, /):
|
||||||
assert spam
|
assert spam
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
interpreters.run_func(self.id, script)
|
_interpreters.run_func(self.id, script)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -2065,7 +2065,7 @@ def test_configured_settings(self):
|
||||||
_testinternalcapi.get_interp_settings()
|
_testinternalcapi.get_interp_settings()
|
||||||
raise NotImplementedError('unreachable')
|
raise NotImplementedError('unreachable')
|
||||||
''')
|
''')
|
||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(_interpreters.InterpreterError):
|
||||||
support.run_in_subinterp_with_config(script, **kwargs)
|
support.run_in_subinterp_with_config(script, **kwargs)
|
||||||
|
|
||||||
@unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module")
|
@unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module")
|
||||||
|
@ -2403,7 +2403,7 @@ def check(config):
|
||||||
continue
|
continue
|
||||||
if match(config, invalid):
|
if match(config, invalid):
|
||||||
with self.subTest(f'invalid: {config}'):
|
with self.subTest(f'invalid: {config}'):
|
||||||
with self.assertRaises(RuntimeError):
|
with self.assertRaises(_interpreters.InterpreterError):
|
||||||
check(config)
|
check(config)
|
||||||
elif match(config, questionable):
|
elif match(config, questionable):
|
||||||
with self.subTest(f'questionable: {config}'):
|
with self.subTest(f'questionable: {config}'):
|
||||||
|
@ -2427,7 +2427,7 @@ def new_interp(config):
|
||||||
with self.subTest('main'):
|
with self.subTest('main'):
|
||||||
expected = _interpreters.new_config('legacy')
|
expected = _interpreters.new_config('legacy')
|
||||||
expected.gil = 'own'
|
expected.gil = 'own'
|
||||||
interpid = _interpreters.get_main()
|
interpid, *_ = _interpreters.get_main()
|
||||||
config = _interpreters.get_config(interpid)
|
config = _interpreters.get_config(interpid)
|
||||||
self.assert_ns_equal(config, expected)
|
self.assert_ns_equal(config, expected)
|
||||||
|
|
||||||
|
@ -2579,7 +2579,7 @@ def test_linked_lifecycle_does_not_exist(self):
|
||||||
|
|
||||||
def test_linked_lifecycle_initial(self):
|
def test_linked_lifecycle_initial(self):
|
||||||
is_linked = _testinternalcapi.interpreter_refcount_linked
|
is_linked = _testinternalcapi.interpreter_refcount_linked
|
||||||
get_refcount = _testinternalcapi.get_interpreter_refcount
|
get_refcount, _, _ = self.get_refcount_helpers()
|
||||||
|
|
||||||
# A new interpreter will start out not linked, with a refcount of 0.
|
# A new interpreter will start out not linked, with a refcount of 0.
|
||||||
interpid = self.new_interpreter()
|
interpid = self.new_interpreter()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
from textwrap import dedent
|
import sys
|
||||||
|
from textwrap import dedent, indent
|
||||||
import threading
|
import threading
|
||||||
import types
|
import types
|
||||||
import unittest
|
import unittest
|
||||||
|
@ -10,8 +11,13 @@
|
||||||
# Raise SkipTest if subinterpreters not supported.
|
# Raise SkipTest if subinterpreters not supported.
|
||||||
_interpreters = import_helper.import_module('_xxsubinterpreters')
|
_interpreters = import_helper.import_module('_xxsubinterpreters')
|
||||||
from test.support import interpreters
|
from test.support import interpreters
|
||||||
from test.support.interpreters import InterpreterNotFoundError
|
from test.support.interpreters import (
|
||||||
from .utils import _captured_script, _run_output, _running, TestBase
|
InterpreterError, InterpreterNotFoundError, ExecutionFailed,
|
||||||
|
)
|
||||||
|
from .utils import (
|
||||||
|
_captured_script, _run_output, _running, TestBase,
|
||||||
|
requires_test_modules, _testinternalcapi,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ModuleTests(TestBase):
|
class ModuleTests(TestBase):
|
||||||
|
@ -157,6 +163,20 @@ def test_idempotent(self):
|
||||||
id2 = id(interp)
|
id2 = id(interp)
|
||||||
self.assertNotEqual(id1, id2)
|
self.assertNotEqual(id1, id2)
|
||||||
|
|
||||||
|
@requires_test_modules
|
||||||
|
def test_created_with_capi(self):
|
||||||
|
last = 0
|
||||||
|
for id, *_ in _interpreters.list_all():
|
||||||
|
last = max(last, id)
|
||||||
|
expected = _testinternalcapi.next_interpreter_id()
|
||||||
|
text = self.run_temp_from_capi(f"""
|
||||||
|
import {interpreters.__name__} as interpreters
|
||||||
|
interp = interpreters.get_current()
|
||||||
|
print(interp.id)
|
||||||
|
""")
|
||||||
|
interpid = eval(text)
|
||||||
|
self.assertEqual(interpid, expected)
|
||||||
|
|
||||||
|
|
||||||
class ListAllTests(TestBase):
|
class ListAllTests(TestBase):
|
||||||
|
|
||||||
|
@ -199,6 +219,33 @@ def test_idempotent(self):
|
||||||
for interp1, interp2 in zip(actual, expected):
|
for interp1, interp2 in zip(actual, expected):
|
||||||
self.assertIs(interp1, interp2)
|
self.assertIs(interp1, interp2)
|
||||||
|
|
||||||
|
def test_created_with_capi(self):
|
||||||
|
mainid, *_ = _interpreters.get_main()
|
||||||
|
interpid1 = _interpreters.create()
|
||||||
|
interpid2 = _interpreters.create()
|
||||||
|
interpid3 = _interpreters.create()
|
||||||
|
interpid4 = interpid3 + 1
|
||||||
|
interpid5 = interpid4 + 1
|
||||||
|
expected = [
|
||||||
|
(mainid,),
|
||||||
|
(interpid1,),
|
||||||
|
(interpid2,),
|
||||||
|
(interpid3,),
|
||||||
|
(interpid4,),
|
||||||
|
(interpid5,),
|
||||||
|
]
|
||||||
|
expected2 = expected[:-2]
|
||||||
|
text = self.run_temp_from_capi(f"""
|
||||||
|
import {interpreters.__name__} as interpreters
|
||||||
|
interp = interpreters.create()
|
||||||
|
print(
|
||||||
|
[(i.id,) for i in interpreters.list_all()])
|
||||||
|
""")
|
||||||
|
res = eval(text)
|
||||||
|
res2 = [(i.id,) for i in interpreters.list_all()]
|
||||||
|
self.assertEqual(res, expected)
|
||||||
|
self.assertEqual(res2, expected2)
|
||||||
|
|
||||||
|
|
||||||
class InterpreterObjectTests(TestBase):
|
class InterpreterObjectTests(TestBase):
|
||||||
|
|
||||||
|
@ -276,6 +323,7 @@ def test_main(self):
|
||||||
main = interpreters.get_main()
|
main = interpreters.get_main()
|
||||||
self.assertTrue(main.is_running())
|
self.assertTrue(main.is_running())
|
||||||
|
|
||||||
|
# XXX Is this still true?
|
||||||
@unittest.skip('Fails on FreeBSD')
|
@unittest.skip('Fails on FreeBSD')
|
||||||
def test_subinterpreter(self):
|
def test_subinterpreter(self):
|
||||||
interp = interpreters.create()
|
interp = interpreters.create()
|
||||||
|
@ -337,6 +385,55 @@ def task():
|
||||||
interp.exec('t.join()')
|
interp.exec('t.join()')
|
||||||
self.assertEqual(os.read(r_interp, 1), FINISHED)
|
self.assertEqual(os.read(r_interp, 1), FINISHED)
|
||||||
|
|
||||||
|
def test_created_with_capi(self):
|
||||||
|
script = dedent(f"""
|
||||||
|
import {interpreters.__name__} as interpreters
|
||||||
|
interp = interpreters.get_current()
|
||||||
|
print(interp.is_running())
|
||||||
|
""")
|
||||||
|
def parse_results(text):
|
||||||
|
self.assertNotEqual(text, "")
|
||||||
|
try:
|
||||||
|
return eval(text)
|
||||||
|
except Exception:
|
||||||
|
raise Exception(repr(text))
|
||||||
|
|
||||||
|
with self.subTest('running __main__ (from self)'):
|
||||||
|
with self.interpreter_from_capi() as interpid:
|
||||||
|
text = self.run_from_capi(interpid, script, main=True)
|
||||||
|
running = parse_results(text)
|
||||||
|
self.assertTrue(running)
|
||||||
|
|
||||||
|
with self.subTest('running, but not __main__ (from self)'):
|
||||||
|
text = self.run_temp_from_capi(script)
|
||||||
|
running = parse_results(text)
|
||||||
|
self.assertFalse(running)
|
||||||
|
|
||||||
|
with self.subTest('running __main__ (from other)'):
|
||||||
|
with self.interpreter_obj_from_capi() as (interp, interpid):
|
||||||
|
before = interp.is_running()
|
||||||
|
with self.running_from_capi(interpid, main=True):
|
||||||
|
during = interp.is_running()
|
||||||
|
after = interp.is_running()
|
||||||
|
self.assertFalse(before)
|
||||||
|
self.assertTrue(during)
|
||||||
|
self.assertFalse(after)
|
||||||
|
|
||||||
|
with self.subTest('running, but not __main__ (from other)'):
|
||||||
|
with self.interpreter_obj_from_capi() as (interp, interpid):
|
||||||
|
before = interp.is_running()
|
||||||
|
with self.running_from_capi(interpid, main=False):
|
||||||
|
during = interp.is_running()
|
||||||
|
after = interp.is_running()
|
||||||
|
self.assertFalse(before)
|
||||||
|
self.assertFalse(during)
|
||||||
|
self.assertFalse(after)
|
||||||
|
|
||||||
|
with self.subTest('not running (from other)'):
|
||||||
|
with self.interpreter_obj_from_capi() as (interp, _):
|
||||||
|
running = interp.is_running()
|
||||||
|
self.assertFalse(running)
|
||||||
|
|
||||||
|
|
||||||
class TestInterpreterClose(TestBase):
|
class TestInterpreterClose(TestBase):
|
||||||
|
|
||||||
|
@ -364,11 +461,11 @@ def test_all(self):
|
||||||
|
|
||||||
def test_main(self):
|
def test_main(self):
|
||||||
main, = interpreters.list_all()
|
main, = interpreters.list_all()
|
||||||
with self.assertRaises(interpreters.InterpreterError):
|
with self.assertRaises(InterpreterError):
|
||||||
main.close()
|
main.close()
|
||||||
|
|
||||||
def f():
|
def f():
|
||||||
with self.assertRaises(interpreters.InterpreterError):
|
with self.assertRaises(InterpreterError):
|
||||||
main.close()
|
main.close()
|
||||||
|
|
||||||
t = threading.Thread(target=f)
|
t = threading.Thread(target=f)
|
||||||
|
@ -419,12 +516,13 @@ def f():
|
||||||
t.start()
|
t.start()
|
||||||
t.join()
|
t.join()
|
||||||
|
|
||||||
|
# XXX Is this still true?
|
||||||
@unittest.skip('Fails on FreeBSD')
|
@unittest.skip('Fails on FreeBSD')
|
||||||
def test_still_running(self):
|
def test_still_running(self):
|
||||||
main, = interpreters.list_all()
|
main, = interpreters.list_all()
|
||||||
interp = interpreters.create()
|
interp = interpreters.create()
|
||||||
with _running(interp):
|
with _running(interp):
|
||||||
with self.assertRaises(interpreters.InterpreterError):
|
with self.assertRaises(InterpreterError):
|
||||||
interp.close()
|
interp.close()
|
||||||
self.assertTrue(interp.is_running())
|
self.assertTrue(interp.is_running())
|
||||||
|
|
||||||
|
@ -459,6 +557,52 @@ def task():
|
||||||
|
|
||||||
self.assertEqual(os.read(r_interp, 1), FINISHED)
|
self.assertEqual(os.read(r_interp, 1), FINISHED)
|
||||||
|
|
||||||
|
def test_created_with_capi(self):
|
||||||
|
script = dedent(f"""
|
||||||
|
import {interpreters.__name__} as interpreters
|
||||||
|
interp = interpreters.get_current()
|
||||||
|
interp.close()
|
||||||
|
""")
|
||||||
|
|
||||||
|
with self.subTest('running __main__ (from self)'):
|
||||||
|
with self.interpreter_from_capi() as interpid:
|
||||||
|
with self.assertRaisesRegex(ExecutionFailed,
|
||||||
|
'InterpreterError.*current'):
|
||||||
|
self.run_from_capi(interpid, script, main=True)
|
||||||
|
|
||||||
|
with self.subTest('running, but not __main__ (from self)'):
|
||||||
|
with self.assertRaisesRegex(ExecutionFailed,
|
||||||
|
'InterpreterError.*current'):
|
||||||
|
self.run_temp_from_capi(script)
|
||||||
|
|
||||||
|
with self.subTest('running __main__ (from other)'):
|
||||||
|
with self.interpreter_obj_from_capi() as (interp, interpid):
|
||||||
|
with self.running_from_capi(interpid, main=True):
|
||||||
|
with self.assertRaisesRegex(InterpreterError, 'running'):
|
||||||
|
interp.close()
|
||||||
|
# Make sure it wssn't closed.
|
||||||
|
self.assertTrue(
|
||||||
|
interp.is_running())
|
||||||
|
|
||||||
|
# The rest must be skipped until we deal with running threads when
|
||||||
|
# interp.close() is called.
|
||||||
|
return
|
||||||
|
|
||||||
|
with self.subTest('running, but not __main__ (from other)'):
|
||||||
|
with self.interpreter_obj_from_capi() as (interp, interpid):
|
||||||
|
with self.running_from_capi(interpid, main=False):
|
||||||
|
with self.assertRaisesRegex(InterpreterError, 'not managed'):
|
||||||
|
interp.close()
|
||||||
|
# Make sure it wssn't closed.
|
||||||
|
self.assertFalse(interp.is_running())
|
||||||
|
|
||||||
|
with self.subTest('not running (from other)'):
|
||||||
|
with self.interpreter_obj_from_capi() as (interp, _):
|
||||||
|
with self.assertRaisesRegex(InterpreterError, 'not managed'):
|
||||||
|
interp.close()
|
||||||
|
# Make sure it wssn't closed.
|
||||||
|
self.assertFalse(interp.is_running())
|
||||||
|
|
||||||
|
|
||||||
class TestInterpreterPrepareMain(TestBase):
|
class TestInterpreterPrepareMain(TestBase):
|
||||||
|
|
||||||
|
@ -511,26 +655,45 @@ def test_not_shareable(self):
|
||||||
interp.prepare_main(spam={'spam': 'eggs', 'foo': 'bar'})
|
interp.prepare_main(spam={'spam': 'eggs', 'foo': 'bar'})
|
||||||
|
|
||||||
# Make sure neither was actually bound.
|
# Make sure neither was actually bound.
|
||||||
with self.assertRaises(interpreters.ExecutionFailed):
|
with self.assertRaises(ExecutionFailed):
|
||||||
interp.exec('print(foo)')
|
interp.exec('print(foo)')
|
||||||
with self.assertRaises(interpreters.ExecutionFailed):
|
with self.assertRaises(ExecutionFailed):
|
||||||
interp.exec('print(spam)')
|
interp.exec('print(spam)')
|
||||||
|
|
||||||
|
def test_running(self):
|
||||||
|
interp = interpreters.create()
|
||||||
|
interp.prepare_main({'spam': True})
|
||||||
|
with self.running(interp):
|
||||||
|
with self.assertRaisesRegex(InterpreterError, 'running'):
|
||||||
|
interp.prepare_main({'spam': False})
|
||||||
|
interp.exec('assert spam is True')
|
||||||
|
|
||||||
|
@requires_test_modules
|
||||||
|
def test_created_with_capi(self):
|
||||||
|
with self.interpreter_from_capi() as interpid:
|
||||||
|
interp = interpreters.Interpreter(interpid)
|
||||||
|
interp.prepare_main({'spam': True})
|
||||||
|
rc = _testinternalcapi.exec_interpreter(interpid,
|
||||||
|
'assert spam is True')
|
||||||
|
assert rc == 0, rc
|
||||||
|
|
||||||
|
|
||||||
class TestInterpreterExec(TestBase):
|
class TestInterpreterExec(TestBase):
|
||||||
|
|
||||||
def test_success(self):
|
def test_success(self):
|
||||||
interp = interpreters.create()
|
interp = interpreters.create()
|
||||||
script, file = _captured_script('print("it worked!", end="")')
|
script, results = _captured_script('print("it worked!", end="")')
|
||||||
with file:
|
with results:
|
||||||
interp.exec(script)
|
interp.exec(script)
|
||||||
out = file.read()
|
results = results.final()
|
||||||
|
results.raise_if_failed()
|
||||||
|
out = results.stdout
|
||||||
|
|
||||||
self.assertEqual(out, 'it worked!')
|
self.assertEqual(out, 'it worked!')
|
||||||
|
|
||||||
def test_failure(self):
|
def test_failure(self):
|
||||||
interp = interpreters.create()
|
interp = interpreters.create()
|
||||||
with self.assertRaises(interpreters.ExecutionFailed):
|
with self.assertRaises(ExecutionFailed):
|
||||||
interp.exec('raise Exception')
|
interp.exec('raise Exception')
|
||||||
|
|
||||||
def test_display_preserved_exception(self):
|
def test_display_preserved_exception(self):
|
||||||
|
@ -583,15 +746,17 @@ def script():
|
||||||
|
|
||||||
def test_in_thread(self):
|
def test_in_thread(self):
|
||||||
interp = interpreters.create()
|
interp = interpreters.create()
|
||||||
script, file = _captured_script('print("it worked!", end="")')
|
script, results = _captured_script('print("it worked!", end="")')
|
||||||
with file:
|
with results:
|
||||||
def f():
|
def f():
|
||||||
interp.exec(script)
|
interp.exec(script)
|
||||||
|
|
||||||
t = threading.Thread(target=f)
|
t = threading.Thread(target=f)
|
||||||
t.start()
|
t.start()
|
||||||
t.join()
|
t.join()
|
||||||
out = file.read()
|
results = results.final()
|
||||||
|
results.raise_if_failed()
|
||||||
|
out = results.stdout
|
||||||
|
|
||||||
self.assertEqual(out, 'it worked!')
|
self.assertEqual(out, 'it worked!')
|
||||||
|
|
||||||
|
@ -618,6 +783,7 @@ def test_fork(self):
|
||||||
content = file.read()
|
content = file.read()
|
||||||
self.assertEqual(content, expected)
|
self.assertEqual(content, expected)
|
||||||
|
|
||||||
|
# XXX Is this still true?
|
||||||
@unittest.skip('Fails on FreeBSD')
|
@unittest.skip('Fails on FreeBSD')
|
||||||
def test_already_running(self):
|
def test_already_running(self):
|
||||||
interp = interpreters.create()
|
interp = interpreters.create()
|
||||||
|
@ -666,6 +832,11 @@ def task():
|
||||||
self.assertEqual(os.read(r_interp, 1), RAN)
|
self.assertEqual(os.read(r_interp, 1), RAN)
|
||||||
self.assertEqual(os.read(r_interp, 1), FINISHED)
|
self.assertEqual(os.read(r_interp, 1), FINISHED)
|
||||||
|
|
||||||
|
def test_created_with_capi(self):
|
||||||
|
with self.interpreter_obj_from_capi() as (interp, _):
|
||||||
|
with self.assertRaisesRegex(ExecutionFailed, 'it worked'):
|
||||||
|
interp.exec('raise Exception("it worked!")')
|
||||||
|
|
||||||
# test_xxsubinterpreters covers the remaining
|
# test_xxsubinterpreters covers the remaining
|
||||||
# Interpreter.exec() behavior.
|
# Interpreter.exec() behavior.
|
||||||
|
|
||||||
|
@ -830,7 +1001,7 @@ def test_call(self):
|
||||||
raise Exception((args, kwargs))
|
raise Exception((args, kwargs))
|
||||||
interp.call(callable)
|
interp.call(callable)
|
||||||
|
|
||||||
with self.assertRaises(interpreters.ExecutionFailed):
|
with self.assertRaises(ExecutionFailed):
|
||||||
interp.call(call_func_failure)
|
interp.call(call_func_failure)
|
||||||
|
|
||||||
def test_call_in_thread(self):
|
def test_call_in_thread(self):
|
||||||
|
@ -942,6 +1113,14 @@ class LowLevelTests(TestBase):
|
||||||
# encountered by the high-level module, thus they
|
# encountered by the high-level module, thus they
|
||||||
# mostly shouldn't matter as much.
|
# mostly shouldn't matter as much.
|
||||||
|
|
||||||
|
def interp_exists(self, interpid):
|
||||||
|
try:
|
||||||
|
_interpreters.is_running(interpid)
|
||||||
|
except InterpreterNotFoundError:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
def test_new_config(self):
|
def test_new_config(self):
|
||||||
# This test overlaps with
|
# This test overlaps with
|
||||||
# test.test_capi.test_misc.InterpreterConfigTests.
|
# test.test_capi.test_misc.InterpreterConfigTests.
|
||||||
|
@ -1064,46 +1243,107 @@ def test_new_config(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
_interpreters.new_config(gil=value)
|
_interpreters.new_config(gil=value)
|
||||||
|
|
||||||
def test_get_config(self):
|
def test_get_main(self):
|
||||||
# This test overlaps with
|
interpid, = _interpreters.get_main()
|
||||||
# test.test_capi.test_misc.InterpreterConfigTests.
|
self.assertEqual(interpid, 0)
|
||||||
|
|
||||||
|
def test_get_current(self):
|
||||||
|
with self.subTest('main'):
|
||||||
|
main, *_ = _interpreters.get_main()
|
||||||
|
interpid, = _interpreters.get_current()
|
||||||
|
self.assertEqual(interpid, main)
|
||||||
|
|
||||||
|
script = f"""
|
||||||
|
import {_interpreters.__name__} as _interpreters
|
||||||
|
interpid, = _interpreters.get_current()
|
||||||
|
print(interpid)
|
||||||
|
"""
|
||||||
|
def parse_stdout(text):
|
||||||
|
parts = text.split()
|
||||||
|
assert len(parts) == 1, parts
|
||||||
|
interpid, = parts
|
||||||
|
interpid = int(interpid)
|
||||||
|
return interpid,
|
||||||
|
|
||||||
|
with self.subTest('from _interpreters'):
|
||||||
|
orig = _interpreters.create()
|
||||||
|
text = self.run_and_capture(orig, script)
|
||||||
|
interpid, = parse_stdout(text)
|
||||||
|
self.assertEqual(interpid, orig)
|
||||||
|
|
||||||
|
with self.subTest('from C-API'):
|
||||||
|
last = 0
|
||||||
|
for id, *_ in _interpreters.list_all():
|
||||||
|
last = max(last, id)
|
||||||
|
expected = last + 1
|
||||||
|
text = self.run_temp_from_capi(script)
|
||||||
|
interpid, = parse_stdout(text)
|
||||||
|
self.assertEqual(interpid, expected)
|
||||||
|
|
||||||
|
def test_list_all(self):
|
||||||
|
mainid, *_ = _interpreters.get_main()
|
||||||
|
interpid1 = _interpreters.create()
|
||||||
|
interpid2 = _interpreters.create()
|
||||||
|
interpid3 = _interpreters.create()
|
||||||
|
expected = [
|
||||||
|
(mainid,),
|
||||||
|
(interpid1,),
|
||||||
|
(interpid2,),
|
||||||
|
(interpid3,),
|
||||||
|
]
|
||||||
|
|
||||||
with self.subTest('main'):
|
with self.subTest('main'):
|
||||||
expected = _interpreters.new_config('legacy')
|
res = _interpreters.list_all()
|
||||||
expected.gil = 'own'
|
self.assertEqual(res, expected)
|
||||||
interpid = _interpreters.get_main()
|
|
||||||
config = _interpreters.get_config(interpid)
|
|
||||||
self.assert_ns_equal(config, expected)
|
|
||||||
|
|
||||||
with self.subTest('isolated'):
|
with self.subTest('from _interpreters'):
|
||||||
expected = _interpreters.new_config('isolated')
|
text = self.run_and_capture(interpid2, f"""
|
||||||
interpid = _interpreters.create('isolated')
|
import {_interpreters.__name__} as _interpreters
|
||||||
config = _interpreters.get_config(interpid)
|
print(
|
||||||
self.assert_ns_equal(config, expected)
|
_interpreters.list_all())
|
||||||
|
""")
|
||||||
|
|
||||||
with self.subTest('legacy'):
|
res = eval(text)
|
||||||
expected = _interpreters.new_config('legacy')
|
self.assertEqual(res, expected)
|
||||||
interpid = _interpreters.create('legacy')
|
|
||||||
config = _interpreters.get_config(interpid)
|
with self.subTest('from C-API'):
|
||||||
self.assert_ns_equal(config, expected)
|
interpid4 = interpid3 + 1
|
||||||
|
interpid5 = interpid4 + 1
|
||||||
|
expected2 = expected + [
|
||||||
|
(interpid4,),
|
||||||
|
(interpid5,),
|
||||||
|
]
|
||||||
|
expected3 = expected + [
|
||||||
|
(interpid5,),
|
||||||
|
]
|
||||||
|
text = self.run_temp_from_capi(f"""
|
||||||
|
import {_interpreters.__name__} as _interpreters
|
||||||
|
_interpreters.create()
|
||||||
|
print(
|
||||||
|
_interpreters.list_all())
|
||||||
|
""")
|
||||||
|
res2 = eval(text)
|
||||||
|
res3 = _interpreters.list_all()
|
||||||
|
self.assertEqual(res2, expected2)
|
||||||
|
self.assertEqual(res3, expected3)
|
||||||
|
|
||||||
def test_create(self):
|
def test_create(self):
|
||||||
isolated = _interpreters.new_config('isolated')
|
isolated = _interpreters.new_config('isolated')
|
||||||
legacy = _interpreters.new_config('legacy')
|
legacy = _interpreters.new_config('legacy')
|
||||||
default = isolated
|
default = isolated
|
||||||
|
|
||||||
with self.subTest('no arg'):
|
with self.subTest('no args'):
|
||||||
interpid = _interpreters.create()
|
interpid = _interpreters.create()
|
||||||
config = _interpreters.get_config(interpid)
|
config = _interpreters.get_config(interpid)
|
||||||
self.assert_ns_equal(config, default)
|
self.assert_ns_equal(config, default)
|
||||||
|
|
||||||
with self.subTest('arg: None'):
|
with self.subTest('config: None'):
|
||||||
interpid = _interpreters.create(None)
|
interpid = _interpreters.create(None)
|
||||||
config = _interpreters.get_config(interpid)
|
config = _interpreters.get_config(interpid)
|
||||||
self.assert_ns_equal(config, default)
|
self.assert_ns_equal(config, default)
|
||||||
|
|
||||||
with self.subTest('arg: \'empty\''):
|
with self.subTest('config: \'empty\''):
|
||||||
with self.assertRaises(interpreters.InterpreterError):
|
with self.assertRaises(InterpreterError):
|
||||||
# The "empty" config isn't viable on its own.
|
# The "empty" config isn't viable on its own.
|
||||||
_interpreters.create('empty')
|
_interpreters.create('empty')
|
||||||
|
|
||||||
|
@ -1138,6 +1378,230 @@ def test_create(self):
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
_interpreters.create(orig)
|
_interpreters.create(orig)
|
||||||
|
|
||||||
|
@requires_test_modules
|
||||||
|
def test_destroy(self):
|
||||||
|
with self.subTest('from _interpreters'):
|
||||||
|
interpid = _interpreters.create()
|
||||||
|
before = [id for id, *_ in _interpreters.list_all()]
|
||||||
|
_interpreters.destroy(interpid)
|
||||||
|
after = [id for id, *_ in _interpreters.list_all()]
|
||||||
|
|
||||||
|
self.assertIn(interpid, before)
|
||||||
|
self.assertNotIn(interpid, after)
|
||||||
|
self.assertFalse(
|
||||||
|
self.interp_exists(interpid))
|
||||||
|
|
||||||
|
with self.subTest('main'):
|
||||||
|
interpid, *_ = _interpreters.get_main()
|
||||||
|
with self.assertRaises(InterpreterError):
|
||||||
|
# It is the current interpreter.
|
||||||
|
_interpreters.destroy(interpid)
|
||||||
|
|
||||||
|
with self.subTest('from C-API'):
|
||||||
|
interpid = _testinternalcapi.create_interpreter()
|
||||||
|
_interpreters.destroy(interpid)
|
||||||
|
self.assertFalse(
|
||||||
|
self.interp_exists(interpid))
|
||||||
|
|
||||||
|
def test_get_config(self):
|
||||||
|
# This test overlaps with
|
||||||
|
# test.test_capi.test_misc.InterpreterConfigTests.
|
||||||
|
|
||||||
|
with self.subTest('main'):
|
||||||
|
expected = _interpreters.new_config('legacy')
|
||||||
|
expected.gil = 'own'
|
||||||
|
interpid, *_ = _interpreters.get_main()
|
||||||
|
config = _interpreters.get_config(interpid)
|
||||||
|
self.assert_ns_equal(config, expected)
|
||||||
|
|
||||||
|
with self.subTest('main'):
|
||||||
|
expected = _interpreters.new_config('legacy')
|
||||||
|
expected.gil = 'own'
|
||||||
|
interpid, *_ = _interpreters.get_main()
|
||||||
|
config = _interpreters.get_config(interpid)
|
||||||
|
self.assert_ns_equal(config, expected)
|
||||||
|
|
||||||
|
with self.subTest('isolated'):
|
||||||
|
expected = _interpreters.new_config('isolated')
|
||||||
|
interpid = _interpreters.create('isolated')
|
||||||
|
config = _interpreters.get_config(interpid)
|
||||||
|
self.assert_ns_equal(config, expected)
|
||||||
|
|
||||||
|
with self.subTest('legacy'):
|
||||||
|
expected = _interpreters.new_config('legacy')
|
||||||
|
interpid = _interpreters.create('legacy')
|
||||||
|
config = _interpreters.get_config(interpid)
|
||||||
|
self.assert_ns_equal(config, expected)
|
||||||
|
|
||||||
|
with self.subTest('from C-API'):
|
||||||
|
orig = _interpreters.new_config('isolated')
|
||||||
|
with self.interpreter_from_capi(orig) as interpid:
|
||||||
|
config = _interpreters.get_config(interpid)
|
||||||
|
self.assert_ns_equal(config, orig)
|
||||||
|
|
||||||
|
@requires_test_modules
|
||||||
|
def test_whence(self):
|
||||||
|
with self.subTest('main'):
|
||||||
|
interpid, *_ = _interpreters.get_main()
|
||||||
|
whence = _interpreters.whence(interpid)
|
||||||
|
self.assertEqual(whence, _interpreters.WHENCE_RUNTIME)
|
||||||
|
|
||||||
|
with self.subTest('stdlib'):
|
||||||
|
interpid = _interpreters.create()
|
||||||
|
whence = _interpreters.whence(interpid)
|
||||||
|
self.assertEqual(whence, _interpreters.WHENCE_XI)
|
||||||
|
|
||||||
|
for orig, name in {
|
||||||
|
# XXX Also check WHENCE_UNKNOWN.
|
||||||
|
_interpreters.WHENCE_LEGACY_CAPI: 'legacy C-API',
|
||||||
|
_interpreters.WHENCE_CAPI: 'C-API',
|
||||||
|
_interpreters.WHENCE_XI: 'cross-interpreter C-API',
|
||||||
|
}.items():
|
||||||
|
with self.subTest(f'from C-API ({orig}: {name})'):
|
||||||
|
with self.interpreter_from_capi(whence=orig) as interpid:
|
||||||
|
whence = _interpreters.whence(interpid)
|
||||||
|
self.assertEqual(whence, orig)
|
||||||
|
|
||||||
|
with self.subTest('from C-API, running'):
|
||||||
|
text = self.run_temp_from_capi(dedent(f"""
|
||||||
|
import {_interpreters.__name__} as _interpreters
|
||||||
|
interpid, *_ = _interpreters.get_current()
|
||||||
|
print(_interpreters.whence(interpid))
|
||||||
|
"""),
|
||||||
|
config=True)
|
||||||
|
whence = eval(text)
|
||||||
|
self.assertEqual(whence, _interpreters.WHENCE_CAPI)
|
||||||
|
|
||||||
|
with self.subTest('from legacy C-API, running'):
|
||||||
|
...
|
||||||
|
text = self.run_temp_from_capi(dedent(f"""
|
||||||
|
import {_interpreters.__name__} as _interpreters
|
||||||
|
interpid, *_ = _interpreters.get_current()
|
||||||
|
print(_interpreters.whence(interpid))
|
||||||
|
"""),
|
||||||
|
config=False)
|
||||||
|
whence = eval(text)
|
||||||
|
self.assertEqual(whence, _interpreters.WHENCE_LEGACY_CAPI)
|
||||||
|
|
||||||
|
def test_is_running(self):
|
||||||
|
with self.subTest('main'):
|
||||||
|
interpid, *_ = _interpreters.get_main()
|
||||||
|
running = _interpreters.is_running(interpid)
|
||||||
|
self.assertTrue(running)
|
||||||
|
|
||||||
|
with self.subTest('from _interpreters (running)'):
|
||||||
|
interpid = _interpreters.create()
|
||||||
|
with self.running(interpid):
|
||||||
|
running = _interpreters.is_running(interpid)
|
||||||
|
self.assertTrue(running)
|
||||||
|
|
||||||
|
with self.subTest('from _interpreters (not running)'):
|
||||||
|
interpid = _interpreters.create()
|
||||||
|
running = _interpreters.is_running(interpid)
|
||||||
|
self.assertFalse(running)
|
||||||
|
|
||||||
|
with self.subTest('from C-API (running __main__)'):
|
||||||
|
with self.interpreter_from_capi() as interpid:
|
||||||
|
with self.running_from_capi(interpid, main=True):
|
||||||
|
running = _interpreters.is_running(interpid)
|
||||||
|
self.assertTrue(running)
|
||||||
|
|
||||||
|
with self.subTest('from C-API (running, but not __main__)'):
|
||||||
|
with self.interpreter_from_capi() as interpid:
|
||||||
|
with self.running_from_capi(interpid, main=False):
|
||||||
|
running = _interpreters.is_running(interpid)
|
||||||
|
self.assertFalse(running)
|
||||||
|
|
||||||
|
with self.subTest('from C-API (not running)'):
|
||||||
|
with self.interpreter_from_capi() as interpid:
|
||||||
|
running = _interpreters.is_running(interpid)
|
||||||
|
self.assertFalse(running)
|
||||||
|
|
||||||
|
def test_exec(self):
|
||||||
|
with self.subTest('run script'):
|
||||||
|
interpid = _interpreters.create()
|
||||||
|
script, results = _captured_script('print("it worked!", end="")')
|
||||||
|
with results:
|
||||||
|
exc = _interpreters.exec(interpid, script)
|
||||||
|
results = results.final()
|
||||||
|
results.raise_if_failed()
|
||||||
|
out = results.stdout
|
||||||
|
self.assertEqual(out, 'it worked!')
|
||||||
|
|
||||||
|
with self.subTest('uncaught exception'):
|
||||||
|
interpid = _interpreters.create()
|
||||||
|
script, results = _captured_script("""
|
||||||
|
raise Exception('uh-oh!')
|
||||||
|
print("it worked!", end="")
|
||||||
|
""")
|
||||||
|
with results:
|
||||||
|
exc = _interpreters.exec(interpid, script)
|
||||||
|
out = results.stdout()
|
||||||
|
self.assertEqual(out, '')
|
||||||
|
self.assert_ns_equal(exc, types.SimpleNamespace(
|
||||||
|
type=types.SimpleNamespace(
|
||||||
|
__name__='Exception',
|
||||||
|
__qualname__='Exception',
|
||||||
|
__module__='builtins',
|
||||||
|
),
|
||||||
|
msg='uh-oh!',
|
||||||
|
# We check these in other tests.
|
||||||
|
formatted=exc.formatted,
|
||||||
|
errdisplay=exc.errdisplay,
|
||||||
|
))
|
||||||
|
|
||||||
|
with self.subTest('from C-API'):
|
||||||
|
with self.interpreter_from_capi() as interpid:
|
||||||
|
exc = _interpreters.exec(interpid, 'raise Exception("it worked!")')
|
||||||
|
self.assertIsNot(exc, None)
|
||||||
|
self.assertEqual(exc.msg, 'it worked!')
|
||||||
|
|
||||||
|
def test_call(self):
|
||||||
|
with self.subTest('no args'):
|
||||||
|
interpid = _interpreters.create()
|
||||||
|
exc = _interpreters.call(interpid, call_func_return_shareable)
|
||||||
|
self.assertIs(exc, None)
|
||||||
|
|
||||||
|
with self.subTest('uncaught exception'):
|
||||||
|
interpid = _interpreters.create()
|
||||||
|
exc = _interpreters.call(interpid, call_func_failure)
|
||||||
|
self.assertEqual(exc, types.SimpleNamespace(
|
||||||
|
type=types.SimpleNamespace(
|
||||||
|
__name__='Exception',
|
||||||
|
__qualname__='Exception',
|
||||||
|
__module__='builtins',
|
||||||
|
),
|
||||||
|
msg='spam!',
|
||||||
|
# We check these in other tests.
|
||||||
|
formatted=exc.formatted,
|
||||||
|
errdisplay=exc.errdisplay,
|
||||||
|
))
|
||||||
|
|
||||||
|
def test_set___main___attrs(self):
|
||||||
|
with self.subTest('from _interpreters'):
|
||||||
|
interpid = _interpreters.create()
|
||||||
|
before1 = _interpreters.exec(interpid, 'assert spam == \'eggs\'')
|
||||||
|
before2 = _interpreters.exec(interpid, 'assert ham == 42')
|
||||||
|
self.assertEqual(before1.type.__name__, 'NameError')
|
||||||
|
self.assertEqual(before2.type.__name__, 'NameError')
|
||||||
|
|
||||||
|
_interpreters.set___main___attrs(interpid, dict(
|
||||||
|
spam='eggs',
|
||||||
|
ham=42,
|
||||||
|
))
|
||||||
|
after1 = _interpreters.exec(interpid, 'assert spam == \'eggs\'')
|
||||||
|
after2 = _interpreters.exec(interpid, 'assert ham == 42')
|
||||||
|
after3 = _interpreters.exec(interpid, 'assert spam == 42')
|
||||||
|
self.assertIs(after1, None)
|
||||||
|
self.assertIs(after2, None)
|
||||||
|
self.assertEqual(after3.type.__name__, 'AssertionError')
|
||||||
|
|
||||||
|
with self.subTest('from C-API'):
|
||||||
|
with self.interpreter_from_capi() as interpid:
|
||||||
|
_interpreters.set___main___attrs(interpid, {'spam': True})
|
||||||
|
exc = _interpreters.exec(interpid, 'assert spam is True')
|
||||||
|
self.assertIsNone(exc)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Test needs to be a package, so we can do relative imports.
|
# Test needs to be a package, so we can do relative imports.
|
||||||
|
|
|
@ -1,30 +1,344 @@
|
||||||
|
from collections import namedtuple
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import json
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
|
import pickle
|
||||||
|
import queue
|
||||||
|
#import select
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
from textwrap import dedent
|
from textwrap import dedent, indent
|
||||||
import threading
|
import threading
|
||||||
import types
|
import types
|
||||||
import unittest
|
import unittest
|
||||||
|
import warnings
|
||||||
|
|
||||||
from test import support
|
from test import support
|
||||||
from test.support import os_helper
|
from test.support import os_helper
|
||||||
|
from test.support import import_helper
|
||||||
|
|
||||||
|
_interpreters = import_helper.import_module('_xxsubinterpreters')
|
||||||
from test.support import interpreters
|
from test.support import interpreters
|
||||||
|
|
||||||
|
|
||||||
def _captured_script(script):
|
try:
|
||||||
r, w = os.pipe()
|
import _testinternalcapi
|
||||||
indented = script.replace('\n', '\n ')
|
import _testcapi
|
||||||
wrapped = dedent(f"""
|
except ImportError:
|
||||||
import contextlib
|
_testinternalcapi = None
|
||||||
with open({w}, 'w', encoding='utf-8') as spipe:
|
_testcapi = None
|
||||||
with contextlib.redirect_stdout(spipe):
|
|
||||||
|
def requires_test_modules(func):
|
||||||
|
return unittest.skipIf(_testinternalcapi is None, "test requires _testinternalcapi module")(func)
|
||||||
|
|
||||||
|
|
||||||
|
def _dump_script(text):
|
||||||
|
lines = text.splitlines()
|
||||||
|
print()
|
||||||
|
print('-' * 20)
|
||||||
|
for i, line in enumerate(lines, 1):
|
||||||
|
print(f' {i:>{len(str(len(lines)))}} {line}')
|
||||||
|
print('-' * 20)
|
||||||
|
|
||||||
|
|
||||||
|
def _close_file(file):
|
||||||
|
try:
|
||||||
|
if hasattr(file, 'close'):
|
||||||
|
file.close()
|
||||||
|
else:
|
||||||
|
os.close(file)
|
||||||
|
except OSError as exc:
|
||||||
|
if exc.errno != 9:
|
||||||
|
raise # re-raise
|
||||||
|
# It was closed already.
|
||||||
|
|
||||||
|
|
||||||
|
def pack_exception(exc=None):
|
||||||
|
captured = _interpreters.capture_exception(exc)
|
||||||
|
data = dict(captured.__dict__)
|
||||||
|
data['type'] = dict(captured.type.__dict__)
|
||||||
|
return json.dumps(data)
|
||||||
|
|
||||||
|
|
||||||
|
def unpack_exception(packed):
|
||||||
|
try:
|
||||||
|
data = json.loads(packed)
|
||||||
|
except json.decoder.JSONDecodeError:
|
||||||
|
warnings.warn('incomplete exception data', RuntimeWarning)
|
||||||
|
print(packed if isinstance(packed, str) else packed.decode('utf-8'))
|
||||||
|
return None
|
||||||
|
exc = types.SimpleNamespace(**data)
|
||||||
|
exc.type = types.SimpleNamespace(**exc.type)
|
||||||
|
return exc;
|
||||||
|
|
||||||
|
|
||||||
|
class CapturingResults:
|
||||||
|
|
||||||
|
STDIO = dedent("""\
|
||||||
|
with open({w_pipe}, 'wb', buffering=0) as _spipe_{stream}:
|
||||||
|
_captured_std{stream} = io.StringIO()
|
||||||
|
with contextlib.redirect_std{stream}(_captured_std{stream}):
|
||||||
|
#########################
|
||||||
|
# begin wrapped script
|
||||||
|
|
||||||
{indented}
|
{indented}
|
||||||
""")
|
|
||||||
return wrapped, open(r, encoding='utf-8')
|
# end wrapped script
|
||||||
|
#########################
|
||||||
|
text = _captured_std{stream}.getvalue()
|
||||||
|
_spipe_{stream}.write(text.encode('utf-8'))
|
||||||
|
""")[:-1]
|
||||||
|
EXC = dedent("""\
|
||||||
|
with open({w_pipe}, 'wb', buffering=0) as _spipe_exc:
|
||||||
|
try:
|
||||||
|
#########################
|
||||||
|
# begin wrapped script
|
||||||
|
|
||||||
|
{indented}
|
||||||
|
|
||||||
|
# end wrapped script
|
||||||
|
#########################
|
||||||
|
except Exception as exc:
|
||||||
|
text = _interp_utils.pack_exception(exc)
|
||||||
|
_spipe_exc.write(text.encode('utf-8'))
|
||||||
|
""")[:-1]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def wrap_script(cls, script, *, stdout=True, stderr=False, exc=False):
|
||||||
|
script = dedent(script).strip(os.linesep)
|
||||||
|
imports = [
|
||||||
|
f'import {__name__} as _interp_utils',
|
||||||
|
]
|
||||||
|
wrapped = script
|
||||||
|
|
||||||
|
# Handle exc.
|
||||||
|
if exc:
|
||||||
|
exc = os.pipe()
|
||||||
|
r_exc, w_exc = exc
|
||||||
|
indented = wrapped.replace('\n', '\n ')
|
||||||
|
wrapped = cls.EXC.format(
|
||||||
|
w_pipe=w_exc,
|
||||||
|
indented=indented,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
exc = None
|
||||||
|
|
||||||
|
# Handle stdout.
|
||||||
|
if stdout:
|
||||||
|
imports.extend([
|
||||||
|
'import contextlib, io',
|
||||||
|
])
|
||||||
|
stdout = os.pipe()
|
||||||
|
r_out, w_out = stdout
|
||||||
|
indented = wrapped.replace('\n', '\n ')
|
||||||
|
wrapped = cls.STDIO.format(
|
||||||
|
w_pipe=w_out,
|
||||||
|
indented=indented,
|
||||||
|
stream='out',
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
stdout = None
|
||||||
|
|
||||||
|
# Handle stderr.
|
||||||
|
if stderr == 'stdout':
|
||||||
|
stderr = None
|
||||||
|
elif stderr:
|
||||||
|
if not stdout:
|
||||||
|
imports.extend([
|
||||||
|
'import contextlib, io',
|
||||||
|
])
|
||||||
|
stderr = os.pipe()
|
||||||
|
r_err, w_err = stderr
|
||||||
|
indented = wrapped.replace('\n', '\n ')
|
||||||
|
wrapped = cls.STDIO.format(
|
||||||
|
w_pipe=w_err,
|
||||||
|
indented=indented,
|
||||||
|
stream='err',
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
stderr = None
|
||||||
|
|
||||||
|
if wrapped == script:
|
||||||
|
raise NotImplementedError
|
||||||
|
else:
|
||||||
|
for line in imports:
|
||||||
|
wrapped = f'{line}{os.linesep}{wrapped}'
|
||||||
|
|
||||||
|
results = cls(stdout, stderr, exc)
|
||||||
|
return wrapped, results
|
||||||
|
|
||||||
|
def __init__(self, out, err, exc):
|
||||||
|
self._rf_out = None
|
||||||
|
self._rf_err = None
|
||||||
|
self._rf_exc = None
|
||||||
|
self._w_out = None
|
||||||
|
self._w_err = None
|
||||||
|
self._w_exc = None
|
||||||
|
|
||||||
|
if out is not None:
|
||||||
|
r_out, w_out = out
|
||||||
|
self._rf_out = open(r_out, 'rb', buffering=0)
|
||||||
|
self._w_out = w_out
|
||||||
|
|
||||||
|
if err is not None:
|
||||||
|
r_err, w_err = err
|
||||||
|
self._rf_err = open(r_err, 'rb', buffering=0)
|
||||||
|
self._w_err = w_err
|
||||||
|
|
||||||
|
if exc is not None:
|
||||||
|
r_exc, w_exc = exc
|
||||||
|
self._rf_exc = open(r_exc, 'rb', buffering=0)
|
||||||
|
self._w_exc = w_exc
|
||||||
|
|
||||||
|
self._buf_out = b''
|
||||||
|
self._buf_err = b''
|
||||||
|
self._buf_exc = b''
|
||||||
|
self._exc = None
|
||||||
|
|
||||||
|
self._closed = False
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def closed(self):
|
||||||
|
return self._closed
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self._closed:
|
||||||
|
return
|
||||||
|
self._closed = True
|
||||||
|
|
||||||
|
if self._w_out is not None:
|
||||||
|
_close_file(self._w_out)
|
||||||
|
self._w_out = None
|
||||||
|
if self._w_err is not None:
|
||||||
|
_close_file(self._w_err)
|
||||||
|
self._w_err = None
|
||||||
|
if self._w_exc is not None:
|
||||||
|
_close_file(self._w_exc)
|
||||||
|
self._w_exc = None
|
||||||
|
|
||||||
|
self._capture()
|
||||||
|
|
||||||
|
if self._rf_out is not None:
|
||||||
|
_close_file(self._rf_out)
|
||||||
|
self._rf_out = None
|
||||||
|
if self._rf_err is not None:
|
||||||
|
_close_file(self._rf_err)
|
||||||
|
self._rf_err = None
|
||||||
|
if self._rf_exc is not None:
|
||||||
|
_close_file(self._rf_exc)
|
||||||
|
self._rf_exc = None
|
||||||
|
|
||||||
|
def _capture(self):
|
||||||
|
# Ideally this is called only after the script finishes
|
||||||
|
# (and thus has closed the write end of the pipe.
|
||||||
|
if self._rf_out is not None:
|
||||||
|
chunk = self._rf_out.read(100)
|
||||||
|
while chunk:
|
||||||
|
self._buf_out += chunk
|
||||||
|
chunk = self._rf_out.read(100)
|
||||||
|
if self._rf_err is not None:
|
||||||
|
chunk = self._rf_err.read(100)
|
||||||
|
while chunk:
|
||||||
|
self._buf_err += chunk
|
||||||
|
chunk = self._rf_err.read(100)
|
||||||
|
if self._rf_exc is not None:
|
||||||
|
chunk = self._rf_exc.read(100)
|
||||||
|
while chunk:
|
||||||
|
self._buf_exc += chunk
|
||||||
|
chunk = self._rf_exc.read(100)
|
||||||
|
|
||||||
|
def _unpack_stdout(self):
|
||||||
|
return self._buf_out.decode('utf-8')
|
||||||
|
|
||||||
|
def _unpack_stderr(self):
|
||||||
|
return self._buf_err.decode('utf-8')
|
||||||
|
|
||||||
|
def _unpack_exc(self):
|
||||||
|
if self._exc is not None:
|
||||||
|
return self._exc
|
||||||
|
if not self._buf_exc:
|
||||||
|
return None
|
||||||
|
self._exc = unpack_exception(self._buf_exc)
|
||||||
|
return self._exc
|
||||||
|
|
||||||
|
def stdout(self):
|
||||||
|
if self.closed:
|
||||||
|
return self.final().stdout
|
||||||
|
self._capture()
|
||||||
|
return self._unpack_stdout()
|
||||||
|
|
||||||
|
def stderr(self):
|
||||||
|
if self.closed:
|
||||||
|
return self.final().stderr
|
||||||
|
self._capture()
|
||||||
|
return self._unpack_stderr()
|
||||||
|
|
||||||
|
def exc(self):
|
||||||
|
if self.closed:
|
||||||
|
return self.final().exc
|
||||||
|
self._capture()
|
||||||
|
return self._unpack_exc()
|
||||||
|
|
||||||
|
def final(self, *, force=False):
|
||||||
|
try:
|
||||||
|
return self._final
|
||||||
|
except AttributeError:
|
||||||
|
if not self._closed:
|
||||||
|
if not force:
|
||||||
|
raise Exception('no final results available yet')
|
||||||
|
else:
|
||||||
|
return CapturedResults.Proxy(self)
|
||||||
|
self._final = CapturedResults(
|
||||||
|
self._unpack_stdout(),
|
||||||
|
self._unpack_stderr(),
|
||||||
|
self._unpack_exc(),
|
||||||
|
)
|
||||||
|
return self._final
|
||||||
|
|
||||||
|
|
||||||
|
class CapturedResults(namedtuple('CapturedResults', 'stdout stderr exc')):
|
||||||
|
|
||||||
|
class Proxy:
|
||||||
|
def __init__(self, capturing):
|
||||||
|
self._capturing = capturing
|
||||||
|
def _finish(self):
|
||||||
|
if self._capturing is None:
|
||||||
|
return
|
||||||
|
self._final = self._capturing.final()
|
||||||
|
self._capturing = None
|
||||||
|
def __iter__(self):
|
||||||
|
self._finish()
|
||||||
|
yield from self._final
|
||||||
|
def __len__(self):
|
||||||
|
self._finish()
|
||||||
|
return len(self._final)
|
||||||
|
def __getattr__(self, name):
|
||||||
|
self._finish()
|
||||||
|
if name.startswith('_'):
|
||||||
|
raise AttributeError(name)
|
||||||
|
return getattr(self._final, name)
|
||||||
|
|
||||||
|
def raise_if_failed(self):
|
||||||
|
if self.exc is not None:
|
||||||
|
raise interpreters.ExecutionFailed(self.exc)
|
||||||
|
|
||||||
|
|
||||||
|
def _captured_script(script, *, stdout=True, stderr=False, exc=False):
|
||||||
|
return CapturingResults.wrap_script(
|
||||||
|
script,
|
||||||
|
stdout=stdout,
|
||||||
|
stderr=stderr,
|
||||||
|
exc=exc,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def clean_up_interpreters():
|
def clean_up_interpreters():
|
||||||
|
@ -33,17 +347,17 @@ def clean_up_interpreters():
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
interp.close()
|
interp.close()
|
||||||
except RuntimeError:
|
except _interpreters.InterpreterError:
|
||||||
pass # already destroyed
|
pass # already destroyed
|
||||||
|
|
||||||
|
|
||||||
def _run_output(interp, request, init=None):
|
def _run_output(interp, request, init=None):
|
||||||
script, rpipe = _captured_script(request)
|
script, results = _captured_script(request)
|
||||||
with rpipe:
|
with results:
|
||||||
if init:
|
if init:
|
||||||
interp.prepare_main(init)
|
interp.prepare_main(init)
|
||||||
interp.exec(script)
|
interp.exec(script)
|
||||||
return rpipe.read()
|
return results.stdout()
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
|
@ -175,3 +489,184 @@ def assert_ns_equal(self, ns1, ns2, msg=None):
|
||||||
diff = f'namespace({diff})'
|
diff = f'namespace({diff})'
|
||||||
standardMsg = self._truncateMessage(standardMsg, diff)
|
standardMsg = self._truncateMessage(standardMsg, diff)
|
||||||
self.fail(self._formatMessage(msg, standardMsg))
|
self.fail(self._formatMessage(msg, standardMsg))
|
||||||
|
|
||||||
|
def _run_string(self, interp, script):
|
||||||
|
wrapped, results = _captured_script(script, exc=False)
|
||||||
|
#_dump_script(wrapped)
|
||||||
|
with results:
|
||||||
|
if isinstance(interp, interpreters.Interpreter):
|
||||||
|
interp.exec(script)
|
||||||
|
else:
|
||||||
|
err = _interpreters.run_string(interp, wrapped)
|
||||||
|
if err is not None:
|
||||||
|
return None, err
|
||||||
|
return results.stdout(), None
|
||||||
|
|
||||||
|
def run_and_capture(self, interp, script):
|
||||||
|
text, err = self._run_string(interp, script)
|
||||||
|
if err is not None:
|
||||||
|
raise interpreters.ExecutionFailed(err)
|
||||||
|
else:
|
||||||
|
return text
|
||||||
|
|
||||||
|
@requires_test_modules
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def interpreter_from_capi(self, config=None, whence=None):
|
||||||
|
if config is False:
|
||||||
|
if whence is None:
|
||||||
|
whence = _interpreters.WHENCE_LEGACY_CAPI
|
||||||
|
else:
|
||||||
|
assert whence in (_interpreters.WHENCE_LEGACY_CAPI,
|
||||||
|
_interpreters.WHENCE_UNKNOWN), repr(whence)
|
||||||
|
config = None
|
||||||
|
elif config is True:
|
||||||
|
config = _interpreters.new_config('default')
|
||||||
|
elif config is None:
|
||||||
|
if whence not in (
|
||||||
|
_interpreters.WHENCE_LEGACY_CAPI,
|
||||||
|
_interpreters.WHENCE_UNKNOWN,
|
||||||
|
):
|
||||||
|
config = _interpreters.new_config('legacy')
|
||||||
|
elif isinstance(config, str):
|
||||||
|
config = _interpreters.new_config(config)
|
||||||
|
|
||||||
|
if whence is None:
|
||||||
|
whence = _interpreters.WHENCE_XI
|
||||||
|
|
||||||
|
interpid = _testinternalcapi.create_interpreter(config, whence=whence)
|
||||||
|
try:
|
||||||
|
yield interpid
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
_testinternalcapi.destroy_interpreter(interpid)
|
||||||
|
except _interpreters.InterpreterNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def interpreter_obj_from_capi(self, config='legacy'):
|
||||||
|
with self.interpreter_from_capi(config) as interpid:
|
||||||
|
yield interpreters.Interpreter(interpid), interpid
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def capturing(self, script):
|
||||||
|
wrapped, capturing = _captured_script(script, stdout=True, exc=True)
|
||||||
|
#_dump_script(wrapped)
|
||||||
|
with capturing:
|
||||||
|
yield wrapped, capturing.final(force=True)
|
||||||
|
|
||||||
|
@requires_test_modules
|
||||||
|
def run_from_capi(self, interpid, script, *, main=False):
|
||||||
|
with self.capturing(script) as (wrapped, results):
|
||||||
|
rc = _testinternalcapi.exec_interpreter(interpid, wrapped, main=main)
|
||||||
|
assert rc == 0, rc
|
||||||
|
results.raise_if_failed()
|
||||||
|
return results.stdout
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def _running(self, run_interp, exec_interp):
|
||||||
|
token = b'\0'
|
||||||
|
r_in, w_in = self.pipe()
|
||||||
|
r_out, w_out = self.pipe()
|
||||||
|
|
||||||
|
def close():
|
||||||
|
_close_file(r_in)
|
||||||
|
_close_file(w_in)
|
||||||
|
_close_file(r_out)
|
||||||
|
_close_file(w_out)
|
||||||
|
|
||||||
|
# Start running (and wait).
|
||||||
|
script = dedent(f"""
|
||||||
|
import os
|
||||||
|
try:
|
||||||
|
# handshake
|
||||||
|
token = os.read({r_in}, 1)
|
||||||
|
os.write({w_out}, token)
|
||||||
|
# Wait for the "done" message.
|
||||||
|
os.read({r_in}, 1)
|
||||||
|
except BrokenPipeError:
|
||||||
|
pass
|
||||||
|
except OSError as exc:
|
||||||
|
if exc.errno != 9:
|
||||||
|
raise # re-raise
|
||||||
|
# It was closed already.
|
||||||
|
""")
|
||||||
|
failed = None
|
||||||
|
def run():
|
||||||
|
nonlocal failed
|
||||||
|
try:
|
||||||
|
run_interp(script)
|
||||||
|
except Exception as exc:
|
||||||
|
failed = exc
|
||||||
|
close()
|
||||||
|
t = threading.Thread(target=run)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
# handshake
|
||||||
|
try:
|
||||||
|
os.write(w_in, token)
|
||||||
|
token2 = os.read(r_out, 1)
|
||||||
|
assert token2 == token, (token2, token)
|
||||||
|
except OSError:
|
||||||
|
t.join()
|
||||||
|
if failed is not None:
|
||||||
|
raise failed
|
||||||
|
|
||||||
|
# CM __exit__()
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
# Send "done".
|
||||||
|
os.write(w_in, b'\0')
|
||||||
|
finally:
|
||||||
|
close()
|
||||||
|
t.join()
|
||||||
|
if failed is not None:
|
||||||
|
raise failed
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def running(self, interp):
|
||||||
|
if isinstance(interp, int):
|
||||||
|
interpid = interp
|
||||||
|
def exec_interp(script):
|
||||||
|
exc = _interpreters.exec(interpid, script)
|
||||||
|
assert exc is None, exc
|
||||||
|
run_interp = exec_interp
|
||||||
|
else:
|
||||||
|
def run_interp(script):
|
||||||
|
text = self.run_and_capture(interp, script)
|
||||||
|
assert text == '', repr(text)
|
||||||
|
def exec_interp(script):
|
||||||
|
interp.exec(script)
|
||||||
|
with self._running(run_interp, exec_interp):
|
||||||
|
yield
|
||||||
|
|
||||||
|
@requires_test_modules
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def running_from_capi(self, interpid, *, main=False):
|
||||||
|
def run_interp(script):
|
||||||
|
text = self.run_from_capi(interpid, script, main=main)
|
||||||
|
assert text == '', repr(text)
|
||||||
|
def exec_interp(script):
|
||||||
|
rc = _testinternalcapi.exec_interpreter(interpid, script)
|
||||||
|
assert rc == 0, rc
|
||||||
|
with self._running(run_interp, exec_interp):
|
||||||
|
yield
|
||||||
|
|
||||||
|
@requires_test_modules
|
||||||
|
def run_temp_from_capi(self, script, config='legacy'):
|
||||||
|
if config is False:
|
||||||
|
# Force using Py_NewInterpreter().
|
||||||
|
run_in_interp = (lambda s, c: _testcapi.run_in_subinterp(s))
|
||||||
|
config = None
|
||||||
|
else:
|
||||||
|
run_in_interp = _testinternalcapi.run_in_subinterp_with_config
|
||||||
|
if config is True:
|
||||||
|
config = 'default'
|
||||||
|
if isinstance(config, str):
|
||||||
|
config = _interpreters.new_config(config)
|
||||||
|
with self.capturing(script) as (wrapped, results):
|
||||||
|
rc = run_in_interp(wrapped, config)
|
||||||
|
assert rc == 0, rc
|
||||||
|
results.raise_if_failed()
|
||||||
|
return results.stdout
|
||||||
|
|
|
@ -19,20 +19,3 @@ clear_xid_class(PyTypeObject *cls)
|
||||||
return _PyCrossInterpreterData_UnregisterClass(cls);
|
return _PyCrossInterpreterData_UnregisterClass(cls);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
#ifdef RETURNS_INTERPID_OBJECT
|
|
||||||
static PyObject *
|
|
||||||
get_interpid_obj(PyInterpreterState *interp)
|
|
||||||
{
|
|
||||||
if (_PyInterpreterState_IDInitref(interp) != 0) {
|
|
||||||
return NULL;
|
|
||||||
};
|
|
||||||
int64_t id = PyInterpreterState_GetID(interp);
|
|
||||||
if (id < 0) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
assert(id < LLONG_MAX);
|
|
||||||
return PyLong_FromLongLong(id);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -1369,56 +1369,284 @@ dict_getitem_knownhash(PyObject *self, PyObject *args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* To run some code in a sub-interpreter. */
|
static int
|
||||||
|
_init_interp_config_from_object(PyInterpreterConfig *config, PyObject *obj)
|
||||||
|
{
|
||||||
|
if (obj == NULL) {
|
||||||
|
*config = (PyInterpreterConfig)_PyInterpreterConfig_INIT;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *dict = PyObject_GetAttrString(obj, "__dict__");
|
||||||
|
if (dict == NULL) {
|
||||||
|
PyErr_Format(PyExc_TypeError, "bad config %R", obj);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int res = _PyInterpreterConfig_InitFromDict(config, dict);
|
||||||
|
Py_DECREF(dict);
|
||||||
|
if (res < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyInterpreterState *
|
||||||
|
_new_interpreter(PyInterpreterConfig *config, long whence)
|
||||||
|
{
|
||||||
|
if (whence == _PyInterpreterState_WHENCE_XI) {
|
||||||
|
return _PyXI_NewInterpreter(config, NULL, NULL);
|
||||||
|
}
|
||||||
|
PyObject *exc = NULL;
|
||||||
|
PyInterpreterState *interp = NULL;
|
||||||
|
if (whence == _PyInterpreterState_WHENCE_UNKNOWN) {
|
||||||
|
assert(config == NULL);
|
||||||
|
interp = PyInterpreterState_New();
|
||||||
|
}
|
||||||
|
else if (whence == _PyInterpreterState_WHENCE_CAPI
|
||||||
|
|| whence == _PyInterpreterState_WHENCE_LEGACY_CAPI)
|
||||||
|
{
|
||||||
|
PyThreadState *tstate = NULL;
|
||||||
|
PyThreadState *save_tstate = PyThreadState_Swap(NULL);
|
||||||
|
if (whence == _PyInterpreterState_WHENCE_LEGACY_CAPI) {
|
||||||
|
assert(config == NULL);
|
||||||
|
tstate = Py_NewInterpreter();
|
||||||
|
PyThreadState_Swap(save_tstate);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyStatus status = Py_NewInterpreterFromConfig(&tstate, config);
|
||||||
|
PyThreadState_Swap(save_tstate);
|
||||||
|
if (PyStatus_Exception(status)) {
|
||||||
|
assert(tstate == NULL);
|
||||||
|
_PyErr_SetFromPyStatus(status);
|
||||||
|
exc = PyErr_GetRaisedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tstate != NULL) {
|
||||||
|
interp = PyThreadState_GetInterpreter(tstate);
|
||||||
|
// Throw away the initial tstate.
|
||||||
|
PyThreadState_Swap(tstate);
|
||||||
|
PyThreadState_Clear(tstate);
|
||||||
|
PyThreadState_Swap(save_tstate);
|
||||||
|
PyThreadState_Delete(tstate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"unsupported whence %ld", whence);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interp == NULL) {
|
||||||
|
PyErr_SetString(PyExc_InterpreterError,
|
||||||
|
"sub-interpreter creation failed");
|
||||||
|
if (exc != NULL) {
|
||||||
|
_PyErr_ChainExceptions1(exc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return interp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This exists mostly for testing the _interpreters module, as an
|
||||||
|
// alternative to _interpreters.create()
|
||||||
|
static PyObject *
|
||||||
|
create_interpreter(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
|
{
|
||||||
|
static char *kwlist[] = {"config", "whence", NULL};
|
||||||
|
PyObject *configobj = NULL;
|
||||||
|
long whence = _PyInterpreterState_WHENCE_XI;
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
|
||||||
|
"|O$l:create_interpreter", kwlist,
|
||||||
|
&configobj, &whence))
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (configobj == Py_None) {
|
||||||
|
configobj = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the config.
|
||||||
|
PyInterpreterConfig *config = NULL;
|
||||||
|
PyInterpreterConfig _config;
|
||||||
|
if (whence == _PyInterpreterState_WHENCE_UNKNOWN
|
||||||
|
|| whence == _PyInterpreterState_WHENCE_LEGACY_CAPI)
|
||||||
|
{
|
||||||
|
if (configobj != NULL) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "got unexpected config");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
config = &_config;
|
||||||
|
if (_init_interp_config_from_object(config, configobj) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the interpreter.
|
||||||
|
PyInterpreterState *interp = _new_interpreter(config, whence);
|
||||||
|
if (interp == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the ID.
|
||||||
|
PyObject *idobj = _PyInterpreterState_GetIDObject(interp);
|
||||||
|
if (idobj == NULL) {
|
||||||
|
_PyXI_EndInterpreter(interp, NULL, NULL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return idobj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This exists mostly for testing the _interpreters module, as an
|
||||||
|
// alternative to _interpreters.destroy()
|
||||||
|
static PyObject *
|
||||||
|
destroy_interpreter(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
|
{
|
||||||
|
static char *kwlist[] = {"id", NULL};
|
||||||
|
PyObject *idobj = NULL;
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
|
||||||
|
"O:destroy_interpreter", kwlist,
|
||||||
|
&idobj))
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_LookUpIDObject(idobj);
|
||||||
|
if (interp == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
_PyXI_EndInterpreter(interp, NULL, NULL);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This exists mostly for testing the _interpreters module, as an
|
||||||
|
// alternative to _interpreters.destroy()
|
||||||
|
static PyObject *
|
||||||
|
exec_interpreter(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
|
{
|
||||||
|
static char *kwlist[] = {"id", "code", "main", NULL};
|
||||||
|
PyObject *idobj;
|
||||||
|
const char *code;
|
||||||
|
int runningmain = 0;
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
|
||||||
|
"Os|$p:exec_interpreter", kwlist,
|
||||||
|
&idobj, &code, &runningmain))
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_LookUpIDObject(idobj);
|
||||||
|
if (interp == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *res = NULL;
|
||||||
|
PyThreadState *tstate = PyThreadState_New(interp);
|
||||||
|
_PyThreadState_SetWhence(tstate, _PyThreadState_WHENCE_EXEC);
|
||||||
|
|
||||||
|
PyThreadState *save_tstate = PyThreadState_Swap(tstate);
|
||||||
|
|
||||||
|
if (runningmain) {
|
||||||
|
if (_PyInterpreterState_SetRunningMain(interp) < 0) {
|
||||||
|
goto finally;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* only initialise 'cflags.cf_flags' to test backwards compatibility */
|
||||||
|
PyCompilerFlags cflags = {0};
|
||||||
|
int r = PyRun_SimpleStringFlags(code, &cflags);
|
||||||
|
if (PyErr_Occurred()) {
|
||||||
|
PyErr_PrintEx(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (runningmain) {
|
||||||
|
_PyInterpreterState_SetNotRunningMain(interp);
|
||||||
|
}
|
||||||
|
|
||||||
|
res = PyLong_FromLong(r);
|
||||||
|
|
||||||
|
finally:
|
||||||
|
PyThreadState_Clear(tstate);
|
||||||
|
PyThreadState_Swap(save_tstate);
|
||||||
|
PyThreadState_Delete(tstate);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* To run some code in a sub-interpreter.
|
||||||
|
|
||||||
|
Generally you can use test.support.interpreters,
|
||||||
|
but we keep this helper as a distinct implementation.
|
||||||
|
That's especially important for testing test.support.interpreters.
|
||||||
|
*/
|
||||||
static PyObject *
|
static PyObject *
|
||||||
run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
|
run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
{
|
{
|
||||||
const char *code;
|
const char *code;
|
||||||
PyObject *configobj;
|
PyObject *configobj;
|
||||||
static char *kwlist[] = {"code", "config", NULL};
|
int xi = 0;
|
||||||
|
static char *kwlist[] = {"code", "config", "xi", NULL};
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
|
||||||
"sO:run_in_subinterp_with_config", kwlist,
|
"sO|$p:run_in_subinterp_with_config", kwlist,
|
||||||
&code, &configobj))
|
&code, &configobj, &xi))
|
||||||
{
|
{
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyInterpreterConfig config;
|
PyInterpreterConfig config;
|
||||||
PyObject *dict = PyObject_GetAttrString(configobj, "__dict__");
|
if (_init_interp_config_from_object(&config, configobj) < 0) {
|
||||||
if (dict == NULL) {
|
|
||||||
PyErr_Format(PyExc_TypeError, "bad config %R", configobj);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
int res = _PyInterpreterConfig_InitFromDict(&config, dict);
|
|
||||||
Py_DECREF(dict);
|
|
||||||
if (res < 0) {
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyThreadState *mainstate = PyThreadState_Get();
|
|
||||||
|
|
||||||
PyThreadState_Swap(NULL);
|
|
||||||
|
|
||||||
PyThreadState *substate;
|
|
||||||
PyStatus status = Py_NewInterpreterFromConfig(&substate, &config);
|
|
||||||
if (PyStatus_Exception(status)) {
|
|
||||||
/* Since no new thread state was created, there is no exception to
|
|
||||||
propagate; raise a fresh one after swapping in the old thread
|
|
||||||
state. */
|
|
||||||
PyThreadState_Swap(mainstate);
|
|
||||||
_PyErr_SetFromPyStatus(status);
|
|
||||||
PyObject *exc = PyErr_GetRaisedException();
|
|
||||||
PyErr_SetString(PyExc_RuntimeError, "sub-interpreter creation failed");
|
|
||||||
_PyErr_ChainExceptions1(exc);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
assert(substate != NULL);
|
|
||||||
/* only initialise 'cflags.cf_flags' to test backwards compatibility */
|
/* only initialise 'cflags.cf_flags' to test backwards compatibility */
|
||||||
PyCompilerFlags cflags = {0};
|
PyCompilerFlags cflags = {0};
|
||||||
int r = PyRun_SimpleStringFlags(code, &cflags);
|
|
||||||
Py_EndInterpreter(substate);
|
|
||||||
|
|
||||||
PyThreadState_Swap(mainstate);
|
int r;
|
||||||
|
if (xi) {
|
||||||
|
PyThreadState *save_tstate;
|
||||||
|
PyThreadState *tstate;
|
||||||
|
|
||||||
|
/* Create an interpreter, staying switched to it. */
|
||||||
|
PyInterpreterState *interp = \
|
||||||
|
_PyXI_NewInterpreter(&config, &tstate, &save_tstate);
|
||||||
|
if (interp == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Exec the code in the new interpreter. */
|
||||||
|
r = PyRun_SimpleStringFlags(code, &cflags);
|
||||||
|
|
||||||
|
/* clean up post-exec. */
|
||||||
|
_PyXI_EndInterpreter(interp, tstate, &save_tstate);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PyThreadState *substate;
|
||||||
|
PyThreadState *mainstate = PyThreadState_Swap(NULL);
|
||||||
|
|
||||||
|
/* Create an interpreter, staying switched to it. */
|
||||||
|
PyStatus status = Py_NewInterpreterFromConfig(&substate, &config);
|
||||||
|
if (PyStatus_Exception(status)) {
|
||||||
|
/* Since no new thread state was created, there is no exception to
|
||||||
|
propagate; raise a fresh one after swapping in the old thread
|
||||||
|
state. */
|
||||||
|
PyThreadState_Swap(mainstate);
|
||||||
|
_PyErr_SetFromPyStatus(status);
|
||||||
|
PyObject *exc = PyErr_GetRaisedException();
|
||||||
|
PyErr_SetString(PyExc_InterpreterError,
|
||||||
|
"sub-interpreter creation failed");
|
||||||
|
_PyErr_ChainExceptions1(exc);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Exec the code in the new interpreter. */
|
||||||
|
r = PyRun_SimpleStringFlags(code, &cflags);
|
||||||
|
|
||||||
|
/* clean up post-exec. */
|
||||||
|
Py_EndInterpreter(substate);
|
||||||
|
PyThreadState_Swap(mainstate);
|
||||||
|
}
|
||||||
|
|
||||||
return PyLong_FromLong(r);
|
return PyLong_FromLong(r);
|
||||||
}
|
}
|
||||||
|
@ -1434,6 +1662,13 @@ normalize_interp_id(PyObject *self, PyObject *idobj)
|
||||||
return PyLong_FromLongLong(interpid);
|
return PyLong_FromLongLong(interpid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
next_interpreter_id(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||||
|
{
|
||||||
|
int64_t interpid = _PyRuntime.interpreters.next_id;
|
||||||
|
return PyLong_FromLongLong(interpid);
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
unused_interpreter_id(PyObject *self, PyObject *Py_UNUSED(ignored))
|
unused_interpreter_id(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||||
{
|
{
|
||||||
|
@ -1751,10 +1986,17 @@ static PyMethodDef module_functions[] = {
|
||||||
{"get_object_dict_values", get_object_dict_values, METH_O},
|
{"get_object_dict_values", get_object_dict_values, METH_O},
|
||||||
{"hamt", new_hamt, METH_NOARGS},
|
{"hamt", new_hamt, METH_NOARGS},
|
||||||
{"dict_getitem_knownhash", dict_getitem_knownhash, METH_VARARGS},
|
{"dict_getitem_knownhash", dict_getitem_knownhash, METH_VARARGS},
|
||||||
|
{"create_interpreter", _PyCFunction_CAST(create_interpreter),
|
||||||
|
METH_VARARGS | METH_KEYWORDS},
|
||||||
|
{"destroy_interpreter", _PyCFunction_CAST(destroy_interpreter),
|
||||||
|
METH_VARARGS | METH_KEYWORDS},
|
||||||
|
{"exec_interpreter", _PyCFunction_CAST(exec_interpreter),
|
||||||
|
METH_VARARGS | METH_KEYWORDS},
|
||||||
{"run_in_subinterp_with_config",
|
{"run_in_subinterp_with_config",
|
||||||
_PyCFunction_CAST(run_in_subinterp_with_config),
|
_PyCFunction_CAST(run_in_subinterp_with_config),
|
||||||
METH_VARARGS | METH_KEYWORDS},
|
METH_VARARGS | METH_KEYWORDS},
|
||||||
{"normalize_interp_id", normalize_interp_id, METH_O},
|
{"normalize_interp_id", normalize_interp_id, METH_O},
|
||||||
|
{"next_interpreter_id", next_interpreter_id, METH_NOARGS},
|
||||||
{"unused_interpreter_id", unused_interpreter_id, METH_NOARGS},
|
{"unused_interpreter_id", unused_interpreter_id, METH_NOARGS},
|
||||||
{"interpreter_exists", interpreter_exists, METH_O},
|
{"interpreter_exists", interpreter_exists, METH_O},
|
||||||
{"get_interpreter_refcount", get_interpreter_refcount, METH_O},
|
{"get_interpreter_refcount", get_interpreter_refcount, METH_O},
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
#include "pycore_crossinterp.h" // struct _xid
|
#include "pycore_crossinterp.h" // struct _xid
|
||||||
#include "pycore_interp.h" // _PyInterpreterState_LookUpID()
|
#include "pycore_interp.h" // _PyInterpreterState_LookUpID()
|
||||||
|
#include "pycore_pystate.h" // _PyInterpreterState_GetIDObject()
|
||||||
|
|
||||||
#ifdef MS_WINDOWS
|
#ifdef MS_WINDOWS
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
@ -17,9 +18,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define REGISTERS_HEAP_TYPES
|
#define REGISTERS_HEAP_TYPES
|
||||||
#define RETURNS_INTERPID_OBJECT
|
|
||||||
#include "_interpreters_common.h"
|
#include "_interpreters_common.h"
|
||||||
#undef RETURNS_INTERPID_OBJECT
|
|
||||||
#undef REGISTERS_HEAP_TYPES
|
#undef REGISTERS_HEAP_TYPES
|
||||||
|
|
||||||
|
|
||||||
|
@ -2909,7 +2908,7 @@ channelsmod_list_interpreters(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
goto except;
|
goto except;
|
||||||
}
|
}
|
||||||
if (res) {
|
if (res) {
|
||||||
interpid_obj = get_interpid_obj(interp);
|
interpid_obj = _PyInterpreterState_GetIDObject(interp);
|
||||||
if (interpid_obj == NULL) {
|
if (interpid_obj == NULL) {
|
||||||
goto except;
|
goto except;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,7 @@
|
||||||
|
|
||||||
#include "marshal.h" // PyMarshal_ReadObjectFromString()
|
#include "marshal.h" // PyMarshal_ReadObjectFromString()
|
||||||
|
|
||||||
#define RETURNS_INTERPID_OBJECT
|
|
||||||
#include "_interpreters_common.h"
|
#include "_interpreters_common.h"
|
||||||
#undef RETURNS_INTERPID_OBJECT
|
|
||||||
|
|
||||||
|
|
||||||
#define MODULE_NAME _xxsubinterpreters
|
#define MODULE_NAME _xxsubinterpreters
|
||||||
|
@ -425,59 +423,6 @@ config_from_object(PyObject *configobj, PyInterpreterConfig *config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyInterpreterState *
|
|
||||||
new_interpreter(PyInterpreterConfig *config, PyObject **p_idobj, PyThreadState **p_tstate)
|
|
||||||
{
|
|
||||||
PyThreadState *save_tstate = PyThreadState_Get();
|
|
||||||
assert(save_tstate != NULL);
|
|
||||||
PyThreadState *tstate = NULL;
|
|
||||||
// XXX Possible GILState issues?
|
|
||||||
PyStatus status = Py_NewInterpreterFromConfig(&tstate, config);
|
|
||||||
PyThreadState_Swap(save_tstate);
|
|
||||||
if (PyStatus_Exception(status)) {
|
|
||||||
/* Since no new thread state was created, there is no exception to
|
|
||||||
propagate; raise a fresh one after swapping in the old thread
|
|
||||||
state. */
|
|
||||||
_PyErr_SetFromPyStatus(status);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
assert(tstate != NULL);
|
|
||||||
PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate);
|
|
||||||
|
|
||||||
if (_PyInterpreterState_IDInitref(interp) < 0) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p_idobj != NULL) {
|
|
||||||
// We create the object using the original interpreter.
|
|
||||||
PyObject *idobj = get_interpid_obj(interp);
|
|
||||||
if (idobj == NULL) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
*p_idobj = idobj;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (p_tstate != NULL) {
|
|
||||||
*p_tstate = tstate;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
PyThreadState_Swap(tstate);
|
|
||||||
PyThreadState_Clear(tstate);
|
|
||||||
PyThreadState_Swap(save_tstate);
|
|
||||||
PyThreadState_Delete(tstate);
|
|
||||||
}
|
|
||||||
|
|
||||||
return interp;
|
|
||||||
|
|
||||||
error:
|
|
||||||
// XXX Possible GILState issues?
|
|
||||||
save_tstate = PyThreadState_Swap(tstate);
|
|
||||||
Py_EndInterpreter(tstate);
|
|
||||||
PyThreadState_Swap(save_tstate);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
_run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags)
|
_run_script(PyObject *ns, const char *codestr, Py_ssize_t codestrlen, int flags)
|
||||||
{
|
{
|
||||||
|
@ -546,6 +491,19 @@ _run_in_interpreter(PyInterpreterState *interp,
|
||||||
|
|
||||||
/* module level code ********************************************************/
|
/* module level code ********************************************************/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
get_summary(PyInterpreterState *interp)
|
||||||
|
{
|
||||||
|
PyObject *idobj = _PyInterpreterState_GetIDObject(interp);
|
||||||
|
if (idobj == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PyObject *res = PyTuple_Pack(1, idobj);
|
||||||
|
Py_DECREF(idobj);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
interp_new_config(PyObject *self, PyObject *args, PyObject *kwds)
|
interp_new_config(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
{
|
{
|
||||||
|
@ -606,8 +564,7 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject *idobj = NULL;
|
PyInterpreterState *interp = _PyXI_NewInterpreter(&config, NULL, NULL);
|
||||||
PyInterpreterState *interp = new_interpreter(&config, &idobj, NULL);
|
|
||||||
if (interp == NULL) {
|
if (interp == NULL) {
|
||||||
// XXX Move the chained exception to interpreters.create()?
|
// XXX Move the chained exception to interpreters.create()?
|
||||||
PyObject *exc = PyErr_GetRaisedException();
|
PyObject *exc = PyErr_GetRaisedException();
|
||||||
|
@ -617,6 +574,12 @@ interp_create(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject *idobj = _PyInterpreterState_GetIDObject(interp);
|
||||||
|
if (idobj == NULL) {
|
||||||
|
_PyXI_EndInterpreter(interp, NULL, NULL);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (reqrefs) {
|
if (reqrefs) {
|
||||||
// Decref to 0 will destroy the interpreter.
|
// Decref to 0 will destroy the interpreter.
|
||||||
_PyInterpreterState_RequireIDRef(interp, 1);
|
_PyInterpreterState_RequireIDRef(interp, 1);
|
||||||
|
@ -678,12 +641,7 @@ interp_destroy(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy the interpreter.
|
// Destroy the interpreter.
|
||||||
PyThreadState *tstate = PyThreadState_New(interp);
|
_PyXI_EndInterpreter(interp, NULL, NULL);
|
||||||
_PyThreadState_SetWhence(tstate, _PyThreadState_WHENCE_INTERP);
|
|
||||||
// XXX Possible GILState issues?
|
|
||||||
PyThreadState *save_tstate = PyThreadState_Swap(tstate);
|
|
||||||
Py_EndInterpreter(tstate);
|
|
||||||
PyThreadState_Swap(save_tstate);
|
|
||||||
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
@ -700,7 +658,7 @@ So does an unrecognized ID.");
|
||||||
static PyObject *
|
static PyObject *
|
||||||
interp_list_all(PyObject *self, PyObject *Py_UNUSED(ignored))
|
interp_list_all(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||||
{
|
{
|
||||||
PyObject *ids, *id;
|
PyObject *ids;
|
||||||
PyInterpreterState *interp;
|
PyInterpreterState *interp;
|
||||||
|
|
||||||
ids = PyList_New(0);
|
ids = PyList_New(0);
|
||||||
|
@ -710,14 +668,14 @@ interp_list_all(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||||
|
|
||||||
interp = PyInterpreterState_Head();
|
interp = PyInterpreterState_Head();
|
||||||
while (interp != NULL) {
|
while (interp != NULL) {
|
||||||
id = get_interpid_obj(interp);
|
PyObject *item = get_summary(interp);
|
||||||
if (id == NULL) {
|
if (item == NULL) {
|
||||||
Py_DECREF(ids);
|
Py_DECREF(ids);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
// insert at front of list
|
// insert at front of list
|
||||||
int res = PyList_Insert(ids, 0, id);
|
int res = PyList_Insert(ids, 0, item);
|
||||||
Py_DECREF(id);
|
Py_DECREF(item);
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
Py_DECREF(ids);
|
Py_DECREF(ids);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -730,7 +688,7 @@ interp_list_all(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||||
}
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(list_all_doc,
|
PyDoc_STRVAR(list_all_doc,
|
||||||
"list_all() -> [ID]\n\
|
"list_all() -> [(ID,)]\n\
|
||||||
\n\
|
\n\
|
||||||
Return a list containing the ID of every existing interpreter.");
|
Return a list containing the ID of every existing interpreter.");
|
||||||
|
|
||||||
|
@ -742,11 +700,11 @@ interp_get_current(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||||
if (interp == NULL) {
|
if (interp == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return get_interpid_obj(interp);
|
return get_summary(interp);
|
||||||
}
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(get_current_doc,
|
PyDoc_STRVAR(get_current_doc,
|
||||||
"get_current() -> ID\n\
|
"get_current() -> (ID,)\n\
|
||||||
\n\
|
\n\
|
||||||
Return the ID of current interpreter.");
|
Return the ID of current interpreter.");
|
||||||
|
|
||||||
|
@ -754,13 +712,12 @@ Return the ID of current interpreter.");
|
||||||
static PyObject *
|
static PyObject *
|
||||||
interp_get_main(PyObject *self, PyObject *Py_UNUSED(ignored))
|
interp_get_main(PyObject *self, PyObject *Py_UNUSED(ignored))
|
||||||
{
|
{
|
||||||
// Currently, 0 is always the main interpreter.
|
PyInterpreterState *interp = _PyInterpreterState_Main();
|
||||||
int64_t id = 0;
|
return get_summary(interp);
|
||||||
return PyLong_FromLongLong(id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(get_main_doc,
|
PyDoc_STRVAR(get_main_doc,
|
||||||
"get_main() -> ID\n\
|
"get_main() -> (ID,)\n\
|
||||||
\n\
|
\n\
|
||||||
Return the ID of main interpreter.");
|
Return the ID of main interpreter.");
|
||||||
|
|
||||||
|
@ -1194,6 +1151,32 @@ PyDoc_STRVAR(get_config_doc,
|
||||||
Return a representation of the config used to initialize the interpreter.");
|
Return a representation of the config used to initialize the interpreter.");
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
interp_whence(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
static char *kwlist[] = {"id", NULL};
|
||||||
|
PyObject *id;
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds,
|
||||||
|
"O:whence", kwlist, &id))
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyInterpreterState *interp = look_up_interp(id);
|
||||||
|
if (interp == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
long whence = _PyInterpreterState_GetWhence(interp);
|
||||||
|
return PyLong_FromLong(whence);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(whence_doc,
|
||||||
|
"whence(id) -> int\n\
|
||||||
|
\n\
|
||||||
|
Return an identifier for where the interpreter was created.");
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
interp_incref(PyObject *self, PyObject *args, PyObject *kwds)
|
interp_incref(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
{
|
{
|
||||||
|
@ -1242,9 +1225,78 @@ interp_decref(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
capture_exception(PyObject *self, PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
static char *kwlist[] = {"exc", NULL};
|
||||||
|
PyObject *exc_arg = NULL;
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds,
|
||||||
|
"|O:capture_exception", kwlist,
|
||||||
|
&exc_arg))
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *exc = exc_arg;
|
||||||
|
if (exc == NULL || exc == Py_None) {
|
||||||
|
exc = PyErr_GetRaisedException();
|
||||||
|
if (exc == NULL) {
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!PyExceptionInstance_Check(exc)) {
|
||||||
|
PyErr_Format(PyExc_TypeError, "expected exception, got %R", exc);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
PyObject *captured = NULL;
|
||||||
|
|
||||||
|
_PyXI_excinfo info = {0};
|
||||||
|
if (_PyXI_InitExcInfo(&info, exc) < 0) {
|
||||||
|
goto finally;
|
||||||
|
}
|
||||||
|
captured = _PyXI_ExcInfoAsObject(&info);
|
||||||
|
if (captured == NULL) {
|
||||||
|
goto finally;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *formatted = _PyXI_FormatExcInfo(&info);
|
||||||
|
if (formatted == NULL) {
|
||||||
|
Py_CLEAR(captured);
|
||||||
|
goto finally;
|
||||||
|
}
|
||||||
|
int res = PyObject_SetAttrString(captured, "formatted", formatted);
|
||||||
|
Py_DECREF(formatted);
|
||||||
|
if (res < 0) {
|
||||||
|
Py_CLEAR(captured);
|
||||||
|
goto finally;
|
||||||
|
}
|
||||||
|
|
||||||
|
finally:
|
||||||
|
_PyXI_ClearExcInfo(&info);
|
||||||
|
if (exc != exc_arg) {
|
||||||
|
if (PyErr_Occurred()) {
|
||||||
|
PyErr_SetRaisedException(exc);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_PyErr_ChainExceptions1(exc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return captured;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(capture_exception_doc,
|
||||||
|
"capture_exception(exc=None) -> types.SimpleNamespace\n\
|
||||||
|
\n\
|
||||||
|
Return a snapshot of an exception. If \"exc\" is None\n\
|
||||||
|
then the current exception, if any, is used (but not cleared).\n\
|
||||||
|
\n\
|
||||||
|
The returned snapshot is the same as what _interpreters.exec() returns.");
|
||||||
|
|
||||||
|
|
||||||
static PyMethodDef module_functions[] = {
|
static PyMethodDef module_functions[] = {
|
||||||
{"new_config", _PyCFunction_CAST(interp_new_config),
|
{"new_config", _PyCFunction_CAST(interp_new_config),
|
||||||
METH_VARARGS | METH_KEYWORDS, new_config_doc},
|
METH_VARARGS | METH_KEYWORDS, new_config_doc},
|
||||||
|
|
||||||
{"create", _PyCFunction_CAST(interp_create),
|
{"create", _PyCFunction_CAST(interp_create),
|
||||||
METH_VARARGS | METH_KEYWORDS, create_doc},
|
METH_VARARGS | METH_KEYWORDS, create_doc},
|
||||||
{"destroy", _PyCFunction_CAST(interp_destroy),
|
{"destroy", _PyCFunction_CAST(interp_destroy),
|
||||||
|
@ -1260,6 +1312,8 @@ static PyMethodDef module_functions[] = {
|
||||||
METH_VARARGS | METH_KEYWORDS, is_running_doc},
|
METH_VARARGS | METH_KEYWORDS, is_running_doc},
|
||||||
{"get_config", _PyCFunction_CAST(interp_get_config),
|
{"get_config", _PyCFunction_CAST(interp_get_config),
|
||||||
METH_VARARGS | METH_KEYWORDS, get_config_doc},
|
METH_VARARGS | METH_KEYWORDS, get_config_doc},
|
||||||
|
{"whence", _PyCFunction_CAST(interp_whence),
|
||||||
|
METH_VARARGS | METH_KEYWORDS, whence_doc},
|
||||||
{"exec", _PyCFunction_CAST(interp_exec),
|
{"exec", _PyCFunction_CAST(interp_exec),
|
||||||
METH_VARARGS | METH_KEYWORDS, exec_doc},
|
METH_VARARGS | METH_KEYWORDS, exec_doc},
|
||||||
{"call", _PyCFunction_CAST(interp_call),
|
{"call", _PyCFunction_CAST(interp_call),
|
||||||
|
@ -1271,14 +1325,18 @@ static PyMethodDef module_functions[] = {
|
||||||
|
|
||||||
{"set___main___attrs", _PyCFunction_CAST(interp_set___main___attrs),
|
{"set___main___attrs", _PyCFunction_CAST(interp_set___main___attrs),
|
||||||
METH_VARARGS, set___main___attrs_doc},
|
METH_VARARGS, set___main___attrs_doc},
|
||||||
{"is_shareable", _PyCFunction_CAST(object_is_shareable),
|
|
||||||
METH_VARARGS | METH_KEYWORDS, is_shareable_doc},
|
|
||||||
|
|
||||||
{"incref", _PyCFunction_CAST(interp_incref),
|
{"incref", _PyCFunction_CAST(interp_incref),
|
||||||
METH_VARARGS | METH_KEYWORDS, NULL},
|
METH_VARARGS | METH_KEYWORDS, NULL},
|
||||||
{"decref", _PyCFunction_CAST(interp_decref),
|
{"decref", _PyCFunction_CAST(interp_decref),
|
||||||
METH_VARARGS | METH_KEYWORDS, NULL},
|
METH_VARARGS | METH_KEYWORDS, NULL},
|
||||||
|
|
||||||
|
{"is_shareable", _PyCFunction_CAST(object_is_shareable),
|
||||||
|
METH_VARARGS | METH_KEYWORDS, is_shareable_doc},
|
||||||
|
|
||||||
|
{"capture_exception", _PyCFunction_CAST(capture_exception),
|
||||||
|
METH_VARARGS | METH_KEYWORDS, capture_exception_doc},
|
||||||
|
|
||||||
{NULL, NULL} /* sentinel */
|
{NULL, NULL} /* sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1295,6 +1353,19 @@ module_exec(PyObject *mod)
|
||||||
PyInterpreterState *interp = PyInterpreterState_Get();
|
PyInterpreterState *interp = PyInterpreterState_Get();
|
||||||
module_state *state = get_module_state(mod);
|
module_state *state = get_module_state(mod);
|
||||||
|
|
||||||
|
#define ADD_WHENCE(NAME) \
|
||||||
|
if (PyModule_AddIntConstant(mod, "WHENCE_" #NAME, \
|
||||||
|
_PyInterpreterState_WHENCE_##NAME) < 0) \
|
||||||
|
{ \
|
||||||
|
goto error; \
|
||||||
|
}
|
||||||
|
ADD_WHENCE(UNKNOWN)
|
||||||
|
ADD_WHENCE(RUNTIME)
|
||||||
|
ADD_WHENCE(LEGACY_CAPI)
|
||||||
|
ADD_WHENCE(CAPI)
|
||||||
|
ADD_WHENCE(XI)
|
||||||
|
#undef ADD_WHENCE
|
||||||
|
|
||||||
// exceptions
|
// exceptions
|
||||||
if (PyModule_AddType(mod, (PyTypeObject *)PyExc_InterpreterError) < 0) {
|
if (PyModule_AddType(mod, (PyTypeObject *)PyExc_InterpreterError) < 0) {
|
||||||
goto error;
|
goto error;
|
||||||
|
|
|
@ -468,7 +468,7 @@ _release_xid_data(_PyCrossInterpreterData *data, int rawfree)
|
||||||
/***********************/
|
/***********************/
|
||||||
|
|
||||||
static int
|
static int
|
||||||
_excinfo_init_type(struct _excinfo_type *info, PyObject *exc)
|
_excinfo_init_type_from_exception(struct _excinfo_type *info, PyObject *exc)
|
||||||
{
|
{
|
||||||
/* Note that this copies directly rather than into an intermediate
|
/* Note that this copies directly rather than into an intermediate
|
||||||
struct and does not clear on error. If we need that then we
|
struct and does not clear on error. If we need that then we
|
||||||
|
@ -504,7 +504,7 @@ _excinfo_init_type(struct _excinfo_type *info, PyObject *exc)
|
||||||
}
|
}
|
||||||
info->qualname = _copy_string_obj_raw(strobj, NULL);
|
info->qualname = _copy_string_obj_raw(strobj, NULL);
|
||||||
Py_DECREF(strobj);
|
Py_DECREF(strobj);
|
||||||
if (info->name == NULL) {
|
if (info->qualname == NULL) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -515,10 +515,51 @@ _excinfo_init_type(struct _excinfo_type *info, PyObject *exc)
|
||||||
}
|
}
|
||||||
info->module = _copy_string_obj_raw(strobj, NULL);
|
info->module = _copy_string_obj_raw(strobj, NULL);
|
||||||
Py_DECREF(strobj);
|
Py_DECREF(strobj);
|
||||||
|
if (info->module == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
_excinfo_init_type_from_object(struct _excinfo_type *info, PyObject *exctype)
|
||||||
|
{
|
||||||
|
PyObject *strobj = NULL;
|
||||||
|
|
||||||
|
// __name__
|
||||||
|
strobj = PyObject_GetAttrString(exctype, "__name__");
|
||||||
|
if (strobj == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
info->name = _copy_string_obj_raw(strobj, NULL);
|
||||||
|
Py_DECREF(strobj);
|
||||||
if (info->name == NULL) {
|
if (info->name == NULL) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// __qualname__
|
||||||
|
strobj = PyObject_GetAttrString(exctype, "__qualname__");
|
||||||
|
if (strobj == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
info->qualname = _copy_string_obj_raw(strobj, NULL);
|
||||||
|
Py_DECREF(strobj);
|
||||||
|
if (info->qualname == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// __module__
|
||||||
|
strobj = PyObject_GetAttrString(exctype, "__module__");
|
||||||
|
if (strobj == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
info->module = _copy_string_obj_raw(strobj, NULL);
|
||||||
|
Py_DECREF(strobj);
|
||||||
|
if (info->module == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -584,7 +625,7 @@ _PyXI_excinfo_Clear(_PyXI_excinfo *info)
|
||||||
*info = (_PyXI_excinfo){{NULL}};
|
*info = (_PyXI_excinfo){{NULL}};
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject *
|
PyObject *
|
||||||
_PyXI_excinfo_format(_PyXI_excinfo *info)
|
_PyXI_excinfo_format(_PyXI_excinfo *info)
|
||||||
{
|
{
|
||||||
const char *module, *qualname;
|
const char *module, *qualname;
|
||||||
|
@ -627,7 +668,7 @@ _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
|
||||||
}
|
}
|
||||||
const char *failure = NULL;
|
const char *failure = NULL;
|
||||||
|
|
||||||
if (_excinfo_init_type(&info->type, exc) < 0) {
|
if (_excinfo_init_type_from_exception(&info->type, exc) < 0) {
|
||||||
failure = "error while initializing exception type snapshot";
|
failure = "error while initializing exception type snapshot";
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
@ -672,6 +713,57 @@ _PyXI_excinfo_InitFromException(_PyXI_excinfo *info, PyObject *exc)
|
||||||
return failure;
|
return failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
_PyXI_excinfo_InitFromObject(_PyXI_excinfo *info, PyObject *obj)
|
||||||
|
{
|
||||||
|
const char *failure = NULL;
|
||||||
|
|
||||||
|
PyObject *exctype = PyObject_GetAttrString(obj, "type");
|
||||||
|
if (exctype == NULL) {
|
||||||
|
failure = "exception snapshot missing 'type' attribute";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
int res = _excinfo_init_type_from_object(&info->type, exctype);
|
||||||
|
Py_DECREF(exctype);
|
||||||
|
if (res < 0) {
|
||||||
|
failure = "error while initializing exception type snapshot";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the exception message.
|
||||||
|
PyObject *msgobj = PyObject_GetAttrString(obj, "msg");
|
||||||
|
if (msgobj == NULL) {
|
||||||
|
failure = "exception snapshot missing 'msg' attribute";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
info->msg = _copy_string_obj_raw(msgobj, NULL);
|
||||||
|
Py_DECREF(msgobj);
|
||||||
|
if (info->msg == NULL) {
|
||||||
|
failure = "error while copying exception message";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pickle a traceback.TracebackException.
|
||||||
|
PyObject *errdisplay = PyObject_GetAttrString(obj, "errdisplay");
|
||||||
|
if (errdisplay == NULL) {
|
||||||
|
failure = "exception snapshot missing 'errdisplay' attribute";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
info->errdisplay = _copy_string_obj_raw(errdisplay, NULL);
|
||||||
|
Py_DECREF(errdisplay);
|
||||||
|
if (info->errdisplay == NULL) {
|
||||||
|
failure = "error while copying exception error display";
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
error:
|
||||||
|
assert(failure != NULL);
|
||||||
|
_PyXI_excinfo_Clear(info);
|
||||||
|
return failure;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
_PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype)
|
_PyXI_excinfo_Apply(_PyXI_excinfo *info, PyObject *exctype)
|
||||||
{
|
{
|
||||||
|
@ -825,6 +917,47 @@ _PyXI_excinfo_AsObject(_PyXI_excinfo *info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
_PyXI_InitExcInfo(_PyXI_excinfo *info, PyObject *exc)
|
||||||
|
{
|
||||||
|
assert(!PyErr_Occurred());
|
||||||
|
if (exc == NULL || exc == Py_None) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "missing exc");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const char *failure;
|
||||||
|
if (PyExceptionInstance_Check(exc) || PyExceptionClass_Check(exc)) {
|
||||||
|
failure = _PyXI_excinfo_InitFromException(info, exc);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
failure = _PyXI_excinfo_InitFromObject(info, exc);
|
||||||
|
}
|
||||||
|
if (failure != NULL) {
|
||||||
|
PyErr_SetString(PyExc_Exception, failure);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
_PyXI_FormatExcInfo(_PyXI_excinfo *info)
|
||||||
|
{
|
||||||
|
return _PyXI_excinfo_format(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
_PyXI_ExcInfoAsObject(_PyXI_excinfo *info)
|
||||||
|
{
|
||||||
|
return _PyXI_excinfo_AsObject(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_PyXI_ClearExcInfo(_PyXI_excinfo *info)
|
||||||
|
{
|
||||||
|
_PyXI_excinfo_Clear(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/***************************/
|
/***************************/
|
||||||
/* short-term data sharing */
|
/* short-term data sharing */
|
||||||
/***************************/
|
/***************************/
|
||||||
|
@ -1682,3 +1815,95 @@ _PyXI_FiniTypes(PyInterpreterState *interp)
|
||||||
{
|
{
|
||||||
fini_exceptions(interp);
|
fini_exceptions(interp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*************/
|
||||||
|
/* other API */
|
||||||
|
/*************/
|
||||||
|
|
||||||
|
PyInterpreterState *
|
||||||
|
_PyXI_NewInterpreter(PyInterpreterConfig *config,
|
||||||
|
PyThreadState **p_tstate, PyThreadState **p_save_tstate)
|
||||||
|
{
|
||||||
|
PyThreadState *save_tstate = PyThreadState_Swap(NULL);
|
||||||
|
assert(save_tstate != NULL);
|
||||||
|
|
||||||
|
PyThreadState *tstate;
|
||||||
|
PyStatus status = Py_NewInterpreterFromConfig(&tstate, config);
|
||||||
|
if (PyStatus_Exception(status)) {
|
||||||
|
// Since no new thread state was created, there is no exception
|
||||||
|
// to propagate; raise a fresh one after swapping back in the
|
||||||
|
// old thread state.
|
||||||
|
PyThreadState_Swap(save_tstate);
|
||||||
|
_PyErr_SetFromPyStatus(status);
|
||||||
|
PyObject *exc = PyErr_GetRaisedException();
|
||||||
|
PyErr_SetString(PyExc_InterpreterError,
|
||||||
|
"sub-interpreter creation failed");
|
||||||
|
_PyErr_ChainExceptions1(exc);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
assert(tstate != NULL);
|
||||||
|
PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate);
|
||||||
|
|
||||||
|
_PyInterpreterState_SetWhence(interp, _PyInterpreterState_WHENCE_XI);
|
||||||
|
|
||||||
|
if (p_tstate != NULL) {
|
||||||
|
// We leave the new thread state as the current one.
|
||||||
|
*p_tstate = tstate;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Throw away the initial tstate.
|
||||||
|
PyThreadState_Clear(tstate);
|
||||||
|
PyThreadState_Swap(save_tstate);
|
||||||
|
PyThreadState_Delete(tstate);
|
||||||
|
save_tstate = NULL;
|
||||||
|
}
|
||||||
|
if (p_save_tstate != NULL) {
|
||||||
|
*p_save_tstate = save_tstate;
|
||||||
|
}
|
||||||
|
return interp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_PyXI_EndInterpreter(PyInterpreterState *interp,
|
||||||
|
PyThreadState *tstate, PyThreadState **p_save_tstate)
|
||||||
|
{
|
||||||
|
PyThreadState *save_tstate = NULL;
|
||||||
|
PyThreadState *cur_tstate = PyThreadState_GET();
|
||||||
|
if (tstate == NULL) {
|
||||||
|
if (PyThreadState_GetInterpreter(cur_tstate) == interp) {
|
||||||
|
tstate = cur_tstate;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tstate = PyThreadState_New(interp);
|
||||||
|
_PyThreadState_SetWhence(tstate, _PyThreadState_WHENCE_INTERP);
|
||||||
|
assert(tstate != NULL);
|
||||||
|
save_tstate = PyThreadState_Swap(tstate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
assert(PyThreadState_GetInterpreter(tstate) == interp);
|
||||||
|
if (tstate != cur_tstate) {
|
||||||
|
assert(PyThreadState_GetInterpreter(cur_tstate) != interp);
|
||||||
|
save_tstate = PyThreadState_Swap(tstate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long whence = _PyInterpreterState_GetWhence(interp);
|
||||||
|
assert(whence != _PyInterpreterState_WHENCE_RUNTIME);
|
||||||
|
if (whence == _PyInterpreterState_WHENCE_UNKNOWN) {
|
||||||
|
assert(!interp->_ready);
|
||||||
|
PyThreadState *tstate = PyThreadState_New(interp);
|
||||||
|
save_tstate = PyThreadState_Swap(tstate);
|
||||||
|
_PyInterpreterState_Clear(tstate);
|
||||||
|
PyInterpreterState_Delete(interp);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Py_EndInterpreter(tstate);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p_save_tstate != NULL) {
|
||||||
|
save_tstate = *p_save_tstate;
|
||||||
|
}
|
||||||
|
PyThreadState_Swap(save_tstate);
|
||||||
|
}
|
||||||
|
|
|
@ -6,9 +6,9 @@ static PyTypeObject _PyExc_InterpreterError = {
|
||||||
.tp_name = "interpreters.InterpreterError",
|
.tp_name = "interpreters.InterpreterError",
|
||||||
.tp_doc = PyDoc_STR("A cross-interpreter operation failed"),
|
.tp_doc = PyDoc_STR("A cross-interpreter operation failed"),
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
|
||||||
//.tp_traverse = ((PyTypeObject *)PyExc_BaseException)->tp_traverse,
|
//.tp_traverse = ((PyTypeObject *)PyExc_Exception)->tp_traverse,
|
||||||
//.tp_clear = ((PyTypeObject *)PyExc_BaseException)->tp_clear,
|
//.tp_clear = ((PyTypeObject *)PyExc_Exception)->tp_clear,
|
||||||
//.tp_base = (PyTypeObject *)PyExc_BaseException,
|
//.tp_base = (PyTypeObject *)PyExc_Exception,
|
||||||
};
|
};
|
||||||
PyObject *PyExc_InterpreterError = (PyObject *)&_PyExc_InterpreterError;
|
PyObject *PyExc_InterpreterError = (PyObject *)&_PyExc_InterpreterError;
|
||||||
|
|
||||||
|
@ -19,8 +19,8 @@ static PyTypeObject _PyExc_InterpreterNotFoundError = {
|
||||||
.tp_name = "interpreters.InterpreterNotFoundError",
|
.tp_name = "interpreters.InterpreterNotFoundError",
|
||||||
.tp_doc = PyDoc_STR("An interpreter was not found"),
|
.tp_doc = PyDoc_STR("An interpreter was not found"),
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
|
||||||
//.tp_traverse = ((PyTypeObject *)PyExc_BaseException)->tp_traverse,
|
//.tp_traverse = ((PyTypeObject *)PyExc_Exception)->tp_traverse,
|
||||||
//.tp_clear = ((PyTypeObject *)PyExc_BaseException)->tp_clear,
|
//.tp_clear = ((PyTypeObject *)PyExc_Exception)->tp_clear,
|
||||||
.tp_base = &_PyExc_InterpreterError,
|
.tp_base = &_PyExc_InterpreterError,
|
||||||
};
|
};
|
||||||
PyObject *PyExc_InterpreterNotFoundError = (PyObject *)&_PyExc_InterpreterNotFoundError;
|
PyObject *PyExc_InterpreterNotFoundError = (PyObject *)&_PyExc_InterpreterNotFoundError;
|
||||||
|
@ -61,7 +61,7 @@ _get_not_shareable_error_type(PyInterpreterState *interp)
|
||||||
static int
|
static int
|
||||||
init_exceptions(PyInterpreterState *interp)
|
init_exceptions(PyInterpreterState *interp)
|
||||||
{
|
{
|
||||||
PyTypeObject *base = (PyTypeObject *)PyExc_BaseException;
|
PyTypeObject *base = (PyTypeObject *)PyExc_Exception;
|
||||||
|
|
||||||
// builtin static types
|
// builtin static types
|
||||||
|
|
||||||
|
|
|
@ -477,6 +477,7 @@ pyinit_core_reconfigure(_PyRuntimeState *runtime,
|
||||||
if (interp == NULL) {
|
if (interp == NULL) {
|
||||||
return _PyStatus_ERR("can't make main interpreter");
|
return _PyStatus_ERR("can't make main interpreter");
|
||||||
}
|
}
|
||||||
|
assert(interp->_ready);
|
||||||
|
|
||||||
status = _PyConfig_Write(config, runtime);
|
status = _PyConfig_Write(config, runtime);
|
||||||
if (_PyStatus_EXCEPTION(status)) {
|
if (_PyStatus_EXCEPTION(status)) {
|
||||||
|
@ -631,6 +632,8 @@ pycore_create_interpreter(_PyRuntimeState *runtime,
|
||||||
}
|
}
|
||||||
assert(interp != NULL);
|
assert(interp != NULL);
|
||||||
assert(_Py_IsMainInterpreter(interp));
|
assert(_Py_IsMainInterpreter(interp));
|
||||||
|
_PyInterpreterState_SetWhence(interp, _PyInterpreterState_WHENCE_RUNTIME);
|
||||||
|
interp->_ready = 1;
|
||||||
|
|
||||||
status = _PyConfig_Copy(&interp->config, src_config);
|
status = _PyConfig_Copy(&interp->config, src_config);
|
||||||
if (_PyStatus_EXCEPTION(status)) {
|
if (_PyStatus_EXCEPTION(status)) {
|
||||||
|
@ -2120,7 +2123,8 @@ Py_Finalize(void)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static PyStatus
|
static PyStatus
|
||||||
new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config)
|
new_interpreter(PyThreadState **tstate_p,
|
||||||
|
const PyInterpreterConfig *config, long whence)
|
||||||
{
|
{
|
||||||
PyStatus status;
|
PyStatus status;
|
||||||
|
|
||||||
|
@ -2143,6 +2147,8 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config)
|
||||||
*tstate_p = NULL;
|
*tstate_p = NULL;
|
||||||
return _PyStatus_OK();
|
return _PyStatus_OK();
|
||||||
}
|
}
|
||||||
|
_PyInterpreterState_SetWhence(interp, whence);
|
||||||
|
interp->_ready = 1;
|
||||||
|
|
||||||
// XXX Might new_interpreter() have been called without the GIL held?
|
// XXX Might new_interpreter() have been called without the GIL held?
|
||||||
PyThreadState *save_tstate = _PyThreadState_GET();
|
PyThreadState *save_tstate = _PyThreadState_GET();
|
||||||
|
@ -2231,15 +2237,17 @@ PyStatus
|
||||||
Py_NewInterpreterFromConfig(PyThreadState **tstate_p,
|
Py_NewInterpreterFromConfig(PyThreadState **tstate_p,
|
||||||
const PyInterpreterConfig *config)
|
const PyInterpreterConfig *config)
|
||||||
{
|
{
|
||||||
return new_interpreter(tstate_p, config);
|
long whence = _PyInterpreterState_WHENCE_CAPI;
|
||||||
|
return new_interpreter(tstate_p, config, whence);
|
||||||
}
|
}
|
||||||
|
|
||||||
PyThreadState *
|
PyThreadState *
|
||||||
Py_NewInterpreter(void)
|
Py_NewInterpreter(void)
|
||||||
{
|
{
|
||||||
PyThreadState *tstate = NULL;
|
PyThreadState *tstate = NULL;
|
||||||
|
long whence = _PyInterpreterState_WHENCE_LEGACY_CAPI;
|
||||||
const PyInterpreterConfig config = _PyInterpreterConfig_LEGACY_INIT;
|
const PyInterpreterConfig config = _PyInterpreterConfig_LEGACY_INIT;
|
||||||
PyStatus status = new_interpreter(&tstate, &config);
|
PyStatus status = new_interpreter(&tstate, &config, whence);
|
||||||
if (_PyStatus_EXCEPTION(status)) {
|
if (_PyStatus_EXCEPTION(status)) {
|
||||||
Py_ExitStatusException(status);
|
Py_ExitStatusException(status);
|
||||||
}
|
}
|
||||||
|
|
|
@ -583,6 +583,8 @@ free_interpreter(PyInterpreterState *interp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline int check_interpreter_whence(long);
|
||||||
|
|
||||||
/* Get the interpreter state to a minimal consistent state.
|
/* Get the interpreter state to a minimal consistent state.
|
||||||
Further init happens in pylifecycle.c before it can be used.
|
Further init happens in pylifecycle.c before it can be used.
|
||||||
All fields not initialized here are expected to be zeroed out,
|
All fields not initialized here are expected to be zeroed out,
|
||||||
|
@ -605,12 +607,17 @@ free_interpreter(PyInterpreterState *interp)
|
||||||
static PyStatus
|
static PyStatus
|
||||||
init_interpreter(PyInterpreterState *interp,
|
init_interpreter(PyInterpreterState *interp,
|
||||||
_PyRuntimeState *runtime, int64_t id,
|
_PyRuntimeState *runtime, int64_t id,
|
||||||
PyInterpreterState *next)
|
PyInterpreterState *next,
|
||||||
|
long whence)
|
||||||
{
|
{
|
||||||
if (interp->_initialized) {
|
if (interp->_initialized) {
|
||||||
return _PyStatus_ERR("interpreter already initialized");
|
return _PyStatus_ERR("interpreter already initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert(interp->_whence == _PyInterpreterState_WHENCE_NOTSET);
|
||||||
|
assert(check_interpreter_whence(whence) == 0);
|
||||||
|
interp->_whence = whence;
|
||||||
|
|
||||||
assert(runtime != NULL);
|
assert(runtime != NULL);
|
||||||
interp->runtime = runtime;
|
interp->runtime = runtime;
|
||||||
|
|
||||||
|
@ -718,8 +725,9 @@ _PyInterpreterState_New(PyThreadState *tstate, PyInterpreterState **pinterp)
|
||||||
}
|
}
|
||||||
interpreters->head = interp;
|
interpreters->head = interp;
|
||||||
|
|
||||||
|
long whence = _PyInterpreterState_WHENCE_UNKNOWN;
|
||||||
status = init_interpreter(interp, runtime,
|
status = init_interpreter(interp, runtime,
|
||||||
id, old_head);
|
id, old_head, whence);
|
||||||
if (_PyStatus_EXCEPTION(status)) {
|
if (_PyStatus_EXCEPTION(status)) {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
@ -1103,6 +1111,34 @@ _PyInterpreterState_ReinitRunningMain(PyThreadState *tstate)
|
||||||
// accessors
|
// accessors
|
||||||
//----------
|
//----------
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
check_interpreter_whence(long whence)
|
||||||
|
{
|
||||||
|
if(whence < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (whence > _PyInterpreterState_WHENCE_MAX) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long
|
||||||
|
_PyInterpreterState_GetWhence(PyInterpreterState *interp)
|
||||||
|
{
|
||||||
|
assert(check_interpreter_whence(interp->_whence) == 0);
|
||||||
|
return interp->_whence;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_PyInterpreterState_SetWhence(PyInterpreterState *interp, long whence)
|
||||||
|
{
|
||||||
|
assert(interp->_whence != _PyInterpreterState_WHENCE_NOTSET);
|
||||||
|
assert(check_interpreter_whence(whence) == 0);
|
||||||
|
interp->_whence = whence;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
PyUnstable_InterpreterState_GetMainModule(PyInterpreterState *interp)
|
PyUnstable_InterpreterState_GetMainModule(PyInterpreterState *interp)
|
||||||
{
|
{
|
||||||
|
@ -1114,6 +1150,7 @@ PyUnstable_InterpreterState_GetMainModule(PyInterpreterState *interp)
|
||||||
return PyMapping_GetItemString(modules, "__main__");
|
return PyMapping_GetItemString(modules, "__main__");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
PyObject *
|
PyObject *
|
||||||
PyInterpreterState_GetDict(PyInterpreterState *interp)
|
PyInterpreterState_GetDict(PyInterpreterState *interp)
|
||||||
{
|
{
|
||||||
|
@ -1176,6 +1213,20 @@ PyInterpreterState_GetID(PyInterpreterState *interp)
|
||||||
return interp->id;
|
return interp->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject *
|
||||||
|
_PyInterpreterState_GetIDObject(PyInterpreterState *interp)
|
||||||
|
{
|
||||||
|
if (_PyInterpreterState_IDInitref(interp) != 0) {
|
||||||
|
return NULL;
|
||||||
|
};
|
||||||
|
int64_t interpid = interp->id;
|
||||||
|
if (interpid < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
assert(interpid < LLONG_MAX);
|
||||||
|
return PyLong_FromLongLong(interpid);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
_PyInterpreterState_IDInitref(PyInterpreterState *interp)
|
_PyInterpreterState_IDInitref(PyInterpreterState *interp)
|
||||||
|
|
Loading…
Reference in New Issue