mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
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
18 changed files with 2015 additions and 421 deletions
|
@ -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 *
|
||||
run_in_subinterp_with_config(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||
{
|
||||
const char *code;
|
||||
PyObject *configobj;
|
||||
static char *kwlist[] = {"code", "config", NULL};
|
||||
int xi = 0;
|
||||
static char *kwlist[] = {"code", "config", "xi", NULL};
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
|
||||
"sO:run_in_subinterp_with_config", kwlist,
|
||||
&code, &configobj))
|
||||
"sO|$p:run_in_subinterp_with_config", kwlist,
|
||||
&code, &configobj, &xi))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyInterpreterConfig config;
|
||||
PyObject *dict = PyObject_GetAttrString(configobj, "__dict__");
|
||||
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) {
|
||||
if (_init_interp_config_from_object(&config, configobj) < 0) {
|
||||
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 */
|
||||
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);
|
||||
}
|
||||
|
@ -1434,6 +1662,13 @@ normalize_interp_id(PyObject *self, PyObject *idobj)
|
|||
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 *
|
||||
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},
|
||||
{"hamt", new_hamt, METH_NOARGS},
|
||||
{"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",
|
||||
_PyCFunction_CAST(run_in_subinterp_with_config),
|
||||
METH_VARARGS | METH_KEYWORDS},
|
||||
{"normalize_interp_id", normalize_interp_id, METH_O},
|
||||
{"next_interpreter_id", next_interpreter_id, METH_NOARGS},
|
||||
{"unused_interpreter_id", unused_interpreter_id, METH_NOARGS},
|
||||
{"interpreter_exists", interpreter_exists, METH_O},
|
||||
{"get_interpreter_refcount", get_interpreter_refcount, METH_O},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue