From 00b137c72f90fbc39a6cd7e48b37c58d19977180 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 13 Nov 2018 19:59:26 +0100 Subject: 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 --- Include/pylifecycle.h | 2 + Lib/test/pythoninfo.py | 22 ++++++--- Lib/test/test_embed.py | 42 +++++++++++++--- Modules/_testcapimodule.c | 10 ++++ Modules/main.c | 85 ++++++++++++++++++++++++++------ Programs/_testembed.c | 121 ++++++++++++++++++++++++++++++---------------- 6 files changed, 215 insertions(+), 67 deletions(-) diff --git a/Include/pylifecycle.h b/Include/pylifecycle.h index ca1f24f..3d9365d 100644 --- a/Include/pylifecycle.h +++ b/Include/pylifecycle.h @@ -37,6 +37,8 @@ PyAPI_FUNC(void) _PyMainInterpreterConfig_Clear(_PyMainInterpreterConfig *); PyAPI_FUNC(int) _PyMainInterpreterConfig_Copy( _PyMainInterpreterConfig *config, const _PyMainInterpreterConfig *config2); +PyAPI_FUNC(PyObject*) _PyMainInterpreterConfig_AsDict( + const _PyMainInterpreterConfig *config); PyAPI_FUNC(_PyInitError) _Py_InitializeMainInterpreter( PyInterpreterState *interp, diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 21763d5..2b5d6e2 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -534,15 +534,25 @@ def collect_gdbm(info_add): info_add('gdbm.GDBM_VERSION', '.'.join(map(str, _GDBM_VERSION))) -def collect_get_coreconfig(info_add): +def collect_get_config(info_add): + # Dump _PyCoreConfig and _PyMainInterpreterConfig try: from _testcapi import get_coreconfig except ImportError: - return + pass + else: + config = get_coreconfig() + for key in sorted(config): + info_add('core_config[%s]' % key, repr(config[key])) - config = get_coreconfig() - for key in sorted(config): - info_add('coreconfig[%s]' % key, repr(config[key])) + try: + from _testcapi import get_mainconfig + except ImportError: + pass + else: + config = get_mainconfig() + for key in sorted(config): + info_add('main_config[%s]' % key, repr(config[key])) def collect_info(info): @@ -573,7 +583,7 @@ def collect_info(info): collect_resource, collect_cc, collect_gdbm, - collect_get_coreconfig, + collect_get_config, # Collecting from tests should be last as they have side effects. collect_test_socket, diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 80233a5..cd10376 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -255,9 +255,11 @@ class EmbeddingTests(EmbeddingTestsMixin, unittest.TestCase): class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): maxDiff = 4096 + CORE_CONFIG_REGEX = re.compile(r"^core_config\[([^]]*)\] = (.*)$") + MAIN_CONFIG_REGEX = re.compile(r"^main_config\[([^]]*)\] = (.*)$") UTF8_MODE_ERRORS = ('surrogatepass' if sys.platform == 'win32' else 'surrogateescape') - DEFAULT_CONFIG = { + DEFAULT_CORE_CONFIG = { 'install_signal_handlers': 1, 'use_environment': 1, 'use_hash_seed': 0, @@ -338,7 +340,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): return out.split() def check_config(self, testname, expected): - expected = dict(self.DEFAULT_CONFIG, **expected) + expected = dict(self.DEFAULT_CORE_CONFIG, **expected) env = dict(os.environ) for key in list(env): @@ -367,11 +369,39 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): out, err = self.run_embedded_interpreter(testname, env=env) # Ignore err - config = {} + core_config = {} + main_config = {} for line in out.splitlines(): - key, value = line.split(' = ', 1) - config[key] = value - self.assertEqual(config, expected) + match = self.CORE_CONFIG_REGEX.match(line) + if match is not None: + key = match.group(1) + value = match.group(2) + core_config[key] = value + else: + match = self.MAIN_CONFIG_REGEX.match(line) + if match is None: + raise ValueError(f"failed to parse line {line!r}") + key = match.group(1) + value = match.group(2) + main_config[key] = value + self.assertEqual(core_config, expected) + + pycache_prefix = core_config['pycache_prefix'] + if pycache_prefix != NULL_STR: + pycache_prefix = repr(pycache_prefix) + else: + pycache_prefix = "NULL" + expected_main = { + 'install_signal_handlers': core_config['install_signal_handlers'], + 'argv': '[]', + 'prefix': repr(sys.prefix), + 'base_prefix': repr(sys.base_prefix), + 'base_exec_prefix': repr(sys.base_exec_prefix), + 'warnoptions': '[]', + 'xoptions': '{}', + 'pycache_prefix': pycache_prefix, + } + self.assertEqual(main_config, expected_main) def test_init_default_config(self): self.check_config("init_default_config", {}) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index bc1f630..205b668 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -4702,6 +4702,15 @@ get_coreconfig(PyObject *self, PyObject *Py_UNUSED(args)) } +static PyObject * +get_mainconfig(PyObject *self, PyObject *Py_UNUSED(args)) +{ + PyInterpreterState *interp = _PyInterpreterState_Get(); + const _PyMainInterpreterConfig *config = &interp->config; + return _PyMainInterpreterConfig_AsDict(config); +} + + #ifdef Py_REF_DEBUG static PyObject * negative_refcount(PyObject *self, PyObject *Py_UNUSED(args)) @@ -4948,6 +4957,7 @@ static PyMethodDef TestMethods[] = { {"EncodeLocaleEx", encode_locale_ex, METH_VARARGS}, {"DecodeLocaleEx", decode_locale_ex, METH_VARARGS}, {"get_coreconfig", get_coreconfig, METH_NOARGS}, + {"get_mainconfig", get_mainconfig, METH_NOARGS}, #ifdef Py_REF_DEBUG {"negative_refcount", negative_refcount, METH_NOARGS}, #endif diff --git a/Modules/main.c b/Modules/main.c index 281707a..3e51ef8 100644 --- a/Modules/main.c +++ b/Modules/main.c @@ -1441,31 +1441,88 @@ _PyMainInterpreterConfig_Copy(_PyMainInterpreterConfig *config, { _PyMainInterpreterConfig_Clear(config); -#define COPY_ATTR(ATTR) \ +#define COPY_ATTR(ATTR) config->ATTR = config2->ATTR +#define COPY_OBJ_ATTR(OBJ_ATTR) \ do { \ - if (config2->ATTR != NULL) { \ - config->ATTR = config_copy_attr(config2->ATTR); \ - if (config->ATTR == NULL) { \ + if (config2->OBJ_ATTR != NULL) { \ + config->OBJ_ATTR = config_copy_attr(config2->OBJ_ATTR); \ + if (config->OBJ_ATTR == NULL) { \ return -1; \ } \ } \ } while (0) - COPY_ATTR(argv); - COPY_ATTR(executable); - COPY_ATTR(prefix); - COPY_ATTR(base_prefix); - COPY_ATTR(exec_prefix); - COPY_ATTR(base_exec_prefix); - COPY_ATTR(warnoptions); - COPY_ATTR(xoptions); - COPY_ATTR(module_search_path); - COPY_ATTR(pycache_prefix); + COPY_ATTR(install_signal_handlers); + COPY_OBJ_ATTR(argv); + COPY_OBJ_ATTR(executable); + COPY_OBJ_ATTR(prefix); + COPY_OBJ_ATTR(base_prefix); + COPY_OBJ_ATTR(exec_prefix); + COPY_OBJ_ATTR(base_exec_prefix); + COPY_OBJ_ATTR(warnoptions); + COPY_OBJ_ATTR(xoptions); + COPY_OBJ_ATTR(module_search_path); + COPY_OBJ_ATTR(pycache_prefix); #undef COPY_ATTR +#undef COPY_OBJ_ATTR return 0; } +PyObject* +_PyMainInterpreterConfig_AsDict(const _PyMainInterpreterConfig *config) +{ + PyObject *dict, *obj; + int res; + + dict = PyDict_New(); + if (dict == NULL) { + return NULL; + } + +#define SET_ITEM(KEY, ATTR) \ + do { \ + obj = config->ATTR; \ + if (obj == NULL) { \ + obj = Py_None; \ + } \ + res = PyDict_SetItemString(dict, (KEY), obj); \ + if (res < 0) { \ + goto fail; \ + } \ + } while (0) + + obj = PyLong_FromLong(config->install_signal_handlers); + if (obj == NULL) { + goto fail; + } + res = PyDict_SetItemString(dict, "install_signal_handlers", obj); + Py_DECREF(obj); + if (res < 0) { + goto fail; + } + + SET_ITEM("argv", argv); + SET_ITEM("executable", executable); + SET_ITEM("prefix", prefix); + SET_ITEM("base_prefix", base_prefix); + SET_ITEM("exec_prefix", exec_prefix); + SET_ITEM("base_exec_prefix", base_exec_prefix); + SET_ITEM("warnoptions", warnoptions); + SET_ITEM("xoptions", xoptions); + SET_ITEM("module_search_path", module_search_path); + SET_ITEM("pycache_prefix", pycache_prefix); + + return dict; + +fail: + Py_DECREF(dict); + return NULL; + +#undef SET_ITEM +} + + _PyInitError _PyMainInterpreterConfig_Read(_PyMainInterpreterConfig *main_config, const _PyCoreConfig *config) diff --git a/Programs/_testembed.c b/Programs/_testembed.c index 99772ea..2706071 100644 --- a/Programs/_testembed.c +++ b/Programs/_testembed.c @@ -293,7 +293,7 @@ static int test_initialize_pymain(void) static void -dump_config(void) +dump_core_config(void) { #define ASSERT_EQUAL(a, b) \ if ((a) != (b)) { \ @@ -309,37 +309,37 @@ dump_config(void) PyInterpreterState *interp = _PyInterpreterState_Get(); _PyCoreConfig *config = &interp->core_config; - printf("install_signal_handlers = %i\n", config->install_signal_handlers); + printf("core_config[install_signal_handlers] = %i\n", config->install_signal_handlers); - printf("use_environment = %i\n", config->use_environment); + printf("core_config[use_environment] = %i\n", config->use_environment); ASSERT_EQUAL(config->use_environment, !Py_IgnoreEnvironmentFlag); - printf("use_hash_seed = %i\n", config->use_hash_seed); - printf("hash_seed = %lu\n", config->hash_seed); + printf("core_config[use_hash_seed] = %i\n", config->use_hash_seed); + printf("core_config[hash_seed] = %lu\n", config->hash_seed); - printf("allocator = %s\n", config->allocator); + printf("core_config[allocator] = %s\n", config->allocator); - printf("dev_mode = %i\n", config->dev_mode); - printf("faulthandler = %i\n", config->faulthandler); - printf("tracemalloc = %i\n", config->tracemalloc); - printf("import_time = %i\n", config->import_time); - printf("show_ref_count = %i\n", config->show_ref_count); - printf("show_alloc_count = %i\n", config->show_alloc_count); - printf("dump_refs = %i\n", config->dump_refs); - printf("malloc_stats = %i\n", config->malloc_stats); + printf("core_config[dev_mode] = %i\n", config->dev_mode); + printf("core_config[faulthandler] = %i\n", config->faulthandler); + printf("core_config[tracemalloc] = %i\n", config->tracemalloc); + printf("core_config[import_time] = %i\n", config->import_time); + printf("core_config[show_ref_count] = %i\n", config->show_ref_count); + printf("core_config[show_alloc_count] = %i\n", config->show_alloc_count); + printf("core_config[dump_refs] = %i\n", config->dump_refs); + printf("core_config[malloc_stats] = %i\n", config->malloc_stats); - printf("filesystem_encoding = %s\n", config->filesystem_encoding); - printf("filesystem_errors = %s\n", config->filesystem_errors); - printf("coerce_c_locale = %i\n", config->coerce_c_locale); - printf("coerce_c_locale_warn = %i\n", config->coerce_c_locale_warn); - printf("utf8_mode = %i\n", config->utf8_mode); + printf("core_config[filesystem_encoding] = %s\n", config->filesystem_encoding); + printf("core_config[filesystem_errors] = %s\n", config->filesystem_errors); + printf("core_config[coerce_c_locale] = %i\n", config->coerce_c_locale); + printf("core_config[coerce_c_locale_warn] = %i\n", config->coerce_c_locale_warn); + printf("core_config[utf8_mode] = %i\n", config->utf8_mode); - printf("pycache_prefix = %ls\n", config->pycache_prefix); - printf("program_name = %ls\n", config->program_name); + printf("core_config[pycache_prefix] = %ls\n", config->pycache_prefix); + printf("core_config[program_name] = %ls\n", config->program_name); ASSERT_STR_EQUAL(config->program_name, Py_GetProgramName()); - printf("argc = %i\n", config->argc); - printf("argv = ["); + printf("core_config[argc] = %i\n", config->argc); + printf("core_config[argv] = ["); for (int i=0; i < config->argc; i++) { if (i) { printf(", "); @@ -348,7 +348,7 @@ dump_config(void) } printf("]\n"); - printf("program = %ls\n", config->program); + printf("core_config[program] = %ls\n", config->program); /* FIXME: test xoptions */ /* FIXME: test warnoptions */ /* FIXME: test module_search_path_env */ @@ -361,36 +361,75 @@ dump_config(void) /* FIXME: test base_exec_prefix */ /* FIXME: test dll_path */ - printf("isolated = %i\n", config->isolated); + printf("core_config[isolated] = %i\n", config->isolated); ASSERT_EQUAL(config->isolated, Py_IsolatedFlag); - printf("site_import = %i\n", config->site_import); - printf("bytes_warning = %i\n", config->bytes_warning); - printf("inspect = %i\n", config->inspect); - printf("interactive = %i\n", config->interactive); - printf("optimization_level = %i\n", config->optimization_level); - printf("parser_debug = %i\n", config->parser_debug); - printf("write_bytecode = %i\n", config->write_bytecode); - printf("verbose = %i\n", config->verbose); + printf("core_config[site_import] = %i\n", config->site_import); + printf("core_config[bytes_warning] = %i\n", config->bytes_warning); + printf("core_config[inspect] = %i\n", config->inspect); + printf("core_config[interactive] = %i\n", config->interactive); + printf("core_config[optimization_level] = %i\n", config->optimization_level); + printf("core_config[parser_debug] = %i\n", config->parser_debug); + printf("core_config[write_bytecode] = %i\n", config->write_bytecode); + printf("core_config[verbose] = %i\n", config->verbose); ASSERT_EQUAL(config->verbose, Py_VerboseFlag); - printf("quiet = %i\n", config->quiet); - printf("user_site_directory = %i\n", config->user_site_directory); - printf("buffered_stdio = %i\n", config->buffered_stdio); + printf("core_config[quiet] = %i\n", config->quiet); + printf("core_config[user_site_directory] = %i\n", config->user_site_directory); + printf("core_config[buffered_stdio] = %i\n", config->buffered_stdio); ASSERT_EQUAL(config->buffered_stdio, !Py_UnbufferedStdioFlag); - printf("stdio_encoding = %s\n", config->stdio_encoding); - printf("stdio_errors = %s\n", config->stdio_errors); + printf("core_config[stdio_encoding] = %s\n", config->stdio_encoding); + printf("core_config[stdio_errors] = %s\n", config->stdio_errors); /* FIXME: test legacy_windows_fs_encoding */ /* FIXME: test legacy_windows_stdio */ - printf("_install_importlib = %i\n", config->_install_importlib); - printf("_check_hash_pycs_mode = %s\n", config->_check_hash_pycs_mode); - printf("_frozen = %i\n", config->_frozen); + printf("core_config[_install_importlib] = %i\n", config->_install_importlib); + printf("core_config[_check_hash_pycs_mode] = %s\n", config->_check_hash_pycs_mode); + printf("core_config[_frozen] = %i\n", config->_frozen); #undef ASSERT_EQUAL #undef ASSERT_STR_EQUAL } +static void +dump_main_config(void) +{ + PyInterpreterState *interp = _PyInterpreterState_Get(); + _PyMainInterpreterConfig *config = &interp->config; + + printf("main_config[install_signal_handlers] = %i\n", config->install_signal_handlers); +#define DUMP_ATTR(ATTR) \ + do { \ + if (config->ATTR != NULL) { \ + PySys_FormatStdout("main_config[" #ATTR "] = %R\n", config->ATTR); \ + } \ + else { \ + PySys_FormatStdout("main_config[" #ATTR "] = NULL\n"); \ + } \ + } while (0) + + DUMP_ATTR(argv); + /* FIXME: DUMP_ATTR(executable); */ + DUMP_ATTR(prefix); + DUMP_ATTR(base_prefix); + DUMP_ATTR(base_exec_prefix); + DUMP_ATTR(warnoptions); + DUMP_ATTR(xoptions); + /* FIXME: DUMP_ATTR(module_search_path); */ + DUMP_ATTR(pycache_prefix); + +#undef DUMP_ATTR +} + + +static void +dump_config(void) +{ + dump_core_config(); + dump_main_config(); +} + + static int test_init_default_config(void) { _testembed_Py_Initialize(); -- cgit v0.12