Skip to content

Commit 00b137c

Browse files
authored
bpo-35233: Fix _PyMainInterpreterConfig_Copy() (GH-10519)
* Fix _PyMainInterpreterConfig_Copy(): copy 'install_signal_handlers' attribute * Add _PyMainInterpreterConfig_AsDict() * Add unit tests on the main interpreter configuration to test_embed.InitConfigTests * test.pythoninfo: log also main_config
1 parent f966e53 commit 00b137c

File tree

6 files changed

+215
-67
lines changed

6 files changed

+215
-67
lines changed

Include/pylifecycle.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ PyAPI_FUNC(void) _PyMainInterpreterConfig_Clear(_PyMainInterpreterConfig *);
3737
PyAPI_FUNC(int) _PyMainInterpreterConfig_Copy(
3838
_PyMainInterpreterConfig *config,
3939
const _PyMainInterpreterConfig *config2);
40+
PyAPI_FUNC(PyObject*) _PyMainInterpreterConfig_AsDict(
41+
const _PyMainInterpreterConfig *config);
4042

4143
PyAPI_FUNC(_PyInitError) _Py_InitializeMainInterpreter(
4244
PyInterpreterState *interp,

Lib/test/pythoninfo.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -534,15 +534,25 @@ def collect_gdbm(info_add):
534534
info_add('gdbm.GDBM_VERSION', '.'.join(map(str, _GDBM_VERSION)))
535535

536536

537-
def collect_get_coreconfig(info_add):
537+
def collect_get_config(info_add):
538+
# Dump _PyCoreConfig and _PyMainInterpreterConfig
538539
try:
539540
from _testcapi import get_coreconfig
540541
except ImportError:
541-
return
542+
pass
543+
else:
544+
config = get_coreconfig()
545+
for key in sorted(config):
546+
info_add('core_config[%s]' % key, repr(config[key]))
542547

543-
config = get_coreconfig()
544-
for key in sorted(config):
545-
info_add('coreconfig[%s]' % key, repr(config[key]))
548+
try:
549+
from _testcapi import get_mainconfig
550+
except ImportError:
551+
pass
552+
else:
553+
config = get_mainconfig()
554+
for key in sorted(config):
555+
info_add('main_config[%s]' % key, repr(config[key]))
546556

547557

548558
def collect_info(info):
@@ -573,7 +583,7 @@ def collect_info(info):
573583
collect_resource,
574584
collect_cc,
575585
collect_gdbm,
576-
collect_get_coreconfig,
586+
collect_get_config,
577587

578588
# Collecting from tests should be last as they have side effects.
579589
collect_test_socket,

Lib/test/test_embed.py

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,11 @@ def test_initialize_pymain(self):
255255

256256
class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
257257
maxDiff = 4096
258+
CORE_CONFIG_REGEX = re.compile(r"^core_config\[([^]]*)\] = (.*)$")
259+
MAIN_CONFIG_REGEX = re.compile(r"^main_config\[([^]]*)\] = (.*)$")
258260
UTF8_MODE_ERRORS = ('surrogatepass' if sys.platform == 'win32'
259261
else 'surrogateescape')
260-
DEFAULT_CONFIG = {
262+
DEFAULT_CORE_CONFIG = {
261263
'install_signal_handlers': 1,
262264
'use_environment': 1,
263265
'use_hash_seed': 0,
@@ -338,7 +340,7 @@ def get_filesystem_encoding(self, isolated, env):
338340
return out.split()
339341

340342
def check_config(self, testname, expected):
341-
expected = dict(self.DEFAULT_CONFIG, **expected)
343+
expected = dict(self.DEFAULT_CORE_CONFIG, **expected)
342344

343345
env = dict(os.environ)
344346
for key in list(env):
@@ -367,11 +369,39 @@ def check_config(self, testname, expected):
367369
out, err = self.run_embedded_interpreter(testname, env=env)
368370
# Ignore err
369371

370-
config = {}
372+
core_config = {}
373+
main_config = {}
371374
for line in out.splitlines():
372-
key, value = line.split(' = ', 1)
373-
config[key] = value
374-
self.assertEqual(config, expected)
375+
match = self.CORE_CONFIG_REGEX.match(line)
376+
if match is not None:
377+
key = match.group(1)
378+
value = match.group(2)
379+
core_config[key] = value
380+
else:
381+
match = self.MAIN_CONFIG_REGEX.match(line)
382+
if match is None:
383+
raise ValueError(f"failed to parse line {line!r}")
384+
key = match.group(1)
385+
value = match.group(2)
386+
main_config[key] = value
387+
self.assertEqual(core_config, expected)
388+
389+
pycache_prefix = core_config['pycache_prefix']
390+
if pycache_prefix != NULL_STR:
391+
pycache_prefix = repr(pycache_prefix)
392+
else:
393+
pycache_prefix = "NULL"
394+
expected_main = {
395+
'install_signal_handlers': core_config['install_signal_handlers'],
396+
'argv': '[]',
397+
'prefix': repr(sys.prefix),
398+
'base_prefix': repr(sys.base_prefix),
399+
'base_exec_prefix': repr(sys.base_exec_prefix),
400+
'warnoptions': '[]',
401+
'xoptions': '{}',
402+
'pycache_prefix': pycache_prefix,
403+
}
404+
self.assertEqual(main_config, expected_main)
375405

376406
def test_init_default_config(self):
377407
self.check_config("init_default_config", {})

Modules/_testcapimodule.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4702,6 +4702,15 @@ get_coreconfig(PyObject *self, PyObject *Py_UNUSED(args))
47024702
}
47034703

47044704

4705+
static PyObject *
4706+
get_mainconfig(PyObject *self, PyObject *Py_UNUSED(args))
4707+
{
4708+
PyInterpreterState *interp = _PyInterpreterState_Get();
4709+
const _PyMainInterpreterConfig *config = &interp->config;
4710+
return _PyMainInterpreterConfig_AsDict(config);
4711+
}
4712+
4713+
47054714
#ifdef Py_REF_DEBUG
47064715
static PyObject *
47074716
negative_refcount(PyObject *self, PyObject *Py_UNUSED(args))
@@ -4948,6 +4957,7 @@ static PyMethodDef TestMethods[] = {
49484957
{"EncodeLocaleEx", encode_locale_ex, METH_VARARGS},
49494958
{"DecodeLocaleEx", decode_locale_ex, METH_VARARGS},
49504959
{"get_coreconfig", get_coreconfig, METH_NOARGS},
4960+
{"get_mainconfig", get_mainconfig, METH_NOARGS},
49514961
#ifdef Py_REF_DEBUG
49524962
{"negative_refcount", negative_refcount, METH_NOARGS},
49534963
#endif

Modules/main.c

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,31 +1441,88 @@ _PyMainInterpreterConfig_Copy(_PyMainInterpreterConfig *config,
14411441
{
14421442
_PyMainInterpreterConfig_Clear(config);
14431443

1444-
#define COPY_ATTR(ATTR) \
1444+
#define COPY_ATTR(ATTR) config->ATTR = config2->ATTR
1445+
#define COPY_OBJ_ATTR(OBJ_ATTR) \
14451446
do { \
1446-
if (config2->ATTR != NULL) { \
1447-
config->ATTR = config_copy_attr(config2->ATTR); \
1448-
if (config->ATTR == NULL) { \
1447+
if (config2->OBJ_ATTR != NULL) { \
1448+
config->OBJ_ATTR = config_copy_attr(config2->OBJ_ATTR); \
1449+
if (config->OBJ_ATTR == NULL) { \
14491450
return -1; \
14501451
} \
14511452
} \
14521453
} while (0)
14531454

1454-
COPY_ATTR(argv);
1455-
COPY_ATTR(executable);
1456-
COPY_ATTR(prefix);
1457-
COPY_ATTR(base_prefix);
1458-
COPY_ATTR(exec_prefix);
1459-
COPY_ATTR(base_exec_prefix);
1460-
COPY_ATTR(warnoptions);
1461-
COPY_ATTR(xoptions);
1462-
COPY_ATTR(module_search_path);
1463-
COPY_ATTR(pycache_prefix);
1455+
COPY_ATTR(install_signal_handlers);
1456+
COPY_OBJ_ATTR(argv);
1457+
COPY_OBJ_ATTR(executable);
1458+
COPY_OBJ_ATTR(prefix);
1459+
COPY_OBJ_ATTR(base_prefix);
1460+
COPY_OBJ_ATTR(exec_prefix);
1461+
COPY_OBJ_ATTR(base_exec_prefix);
1462+
COPY_OBJ_ATTR(warnoptions);
1463+
COPY_OBJ_ATTR(xoptions);
1464+
COPY_OBJ_ATTR(module_search_path);
1465+
COPY_OBJ_ATTR(pycache_prefix);
14641466
#undef COPY_ATTR
1467+
#undef COPY_OBJ_ATTR
14651468
return 0;
14661469
}
14671470

14681471

1472+
PyObject*
1473+
_PyMainInterpreterConfig_AsDict(const _PyMainInterpreterConfig *config)
1474+
{
1475+
PyObject *dict, *obj;
1476+
int res;
1477+
1478+
dict = PyDict_New();
1479+
if (dict == NULL) {
1480+
return NULL;
1481+
}
1482+
1483+
#define SET_ITEM(KEY, ATTR) \
1484+
do { \
1485+
obj = config->ATTR; \
1486+
if (obj == NULL) { \
1487+
obj = Py_None; \
1488+
} \
1489+
res = PyDict_SetItemString(dict, (KEY), obj); \
1490+
if (res < 0) { \
1491+
goto fail; \
1492+
} \
1493+
} while (0)
1494+
1495+
obj = PyLong_FromLong(config->install_signal_handlers);
1496+
if (obj == NULL) {
1497+
goto fail;
1498+
}
1499+
res = PyDict_SetItemString(dict, "install_signal_handlers", obj);
1500+
Py_DECREF(obj);
1501+
if (res < 0) {
1502+
goto fail;
1503+
}
1504+
1505+
SET_ITEM("argv", argv);
1506+
SET_ITEM("executable", executable);
1507+
SET_ITEM("prefix", prefix);
1508+
SET_ITEM("base_prefix", base_prefix);
1509+
SET_ITEM("exec_prefix", exec_prefix);
1510+
SET_ITEM("base_exec_prefix", base_exec_prefix);
1511+
SET_ITEM("warnoptions", warnoptions);
1512+
SET_ITEM("xoptions", xoptions);
1513+
SET_ITEM("module_search_path", module_search_path);
1514+
SET_ITEM("pycache_prefix", pycache_prefix);
1515+
1516+
return dict;
1517+
1518+
fail:
1519+
Py_DECREF(dict);
1520+
return NULL;
1521+
1522+
#undef SET_ITEM
1523+
}
1524+
1525+
14691526
_PyInitError
14701527
_PyMainInterpreterConfig_Read(_PyMainInterpreterConfig *main_config,
14711528
const _PyCoreConfig *config)

0 commit comments

Comments
 (0)