From 8f42748ded5e978fe8a924115179d45a74a6363b Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 8 Jul 2020 00:20:37 +0200 Subject: [PATCH] bpo-29778: test_embed tests the path configuration (GH-21306) --- Include/internal/pycore_pathconfig.h | 1 + Lib/test/test_embed.py | 82 +++++++++++++++++++++++----- Modules/_testinternalcapi.c | 45 +-------------- Python/initconfig.c | 14 ++++- Python/pathconfig.c | 74 +++++++++++++++++++++++++ 5 files changed, 154 insertions(+), 62 deletions(-) diff --git a/Include/internal/pycore_pathconfig.h b/Include/internal/pycore_pathconfig.h index 42d61b1ca26..15447f54490 100644 --- a/Include/internal/pycore_pathconfig.h +++ b/Include/internal/pycore_pathconfig.h @@ -65,6 +65,7 @@ extern wchar_t* _Py_GetDLLPath(void); extern PyStatus _PyConfig_WritePathConfig(const PyConfig *config); extern void _Py_DumpPathConfig(PyThreadState *tstate); +extern PyObject* _PyPathConfig_AsDict(void); #ifdef __cplusplus } diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 44d2596d9eb..2b740521e72 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -471,6 +471,31 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): ('Py_LegacyWindowsStdioFlag', 'legacy_windows_stdio'), )) + # path config + if MS_WINDOWS: + PATH_CONFIG = { + 'isolated': -1, + 'site_import': -1, + 'python3_dll': GET_DEFAULT_CONFIG, + } + else: + PATH_CONFIG = {} + # other keys are copied by COPY_PATH_CONFIG + + COPY_PATH_CONFIG = [ + # Copy core config to global config for expected values + 'prefix', + 'exec_prefix', + 'program_name', + 'home', + # program_full_path and module_search_path are copied indirectly from + # the core configuration in check_path_config(). + ] + if MS_WINDOWS: + COPY_PATH_CONFIG.extend(( + 'base_executable', + )) + EXPECTED_CONFIG = None @classmethod @@ -535,7 +560,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): configs[config_key] = config return configs - def get_expected_config(self, expected_preconfig, expected, env, api, + def get_expected_config(self, expected_preconfig, expected, + expected_pathconfig, env, api, modify_path_cb=None): cls = self.__class__ configs = self._get_expected_config() @@ -545,6 +571,11 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): if value is self.GET_DEFAULT_CONFIG: expected_preconfig[key] = pre_config[key] + path_config = configs['path_config'] + for key, value in expected_pathconfig.items(): + if value is self.GET_DEFAULT_CONFIG: + expected_pathconfig[key] = path_config[key] + if not expected_preconfig['configure_locale'] or api == API_COMPAT: # there is no easy way to get the locale encoding before # setlocale(LC_CTYPE, "") is called: don't test encodings @@ -637,8 +668,19 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): self.assertEqual(configs['global_config'], expected) + def check_path_config(self, configs, expected): + config = configs['config'] + + for key in self.COPY_PATH_CONFIG: + expected[key] = config[key] + expected['module_search_path'] = os.path.pathsep.join(config['module_search_paths']) + expected['program_full_path'] = config['executable'] + + self.assertEqual(configs['path_config'], expected) + def check_all_configs(self, testname, expected_config=None, - expected_preconfig=None, modify_path_cb=None, + expected_preconfig=None, expected_pathconfig=None, + modify_path_cb=None, stderr=None, *, api, preconfig_api=None, env=None, ignore_stderr=False, cwd=None): new_env = remove_python_envvars() @@ -657,9 +699,14 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): if expected_preconfig is None: expected_preconfig = {} expected_preconfig = dict(default_preconfig, **expected_preconfig) + if expected_config is None: expected_config = {} + if expected_pathconfig is None: + expected_pathconfig = {} + expected_pathconfig = dict(self.PATH_CONFIG, **expected_pathconfig) + if api == API_PYTHON: default_config = self.CONFIG_PYTHON elif api == API_ISOLATED: @@ -669,7 +716,9 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): expected_config = dict(default_config, **expected_config) self.get_expected_config(expected_preconfig, - expected_config, env, + expected_config, + expected_pathconfig, + env, api, modify_path_cb) out, err = self.run_embedded_interpreter(testname, @@ -686,6 +735,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): self.check_pre_config(configs, expected_preconfig) self.check_config(configs, expected_config) self.check_global_config(configs) + self.check_path_config(configs, expected_pathconfig) return configs def test_init_default_config(self): @@ -1258,22 +1308,24 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'executable': executable, 'module_search_paths': paths, } + path_config = {} if MS_WINDOWS: config['base_prefix'] = pyvenv_home config['prefix'] = pyvenv_home - env = self.copy_paths_by_env(config) - actual = self.check_all_configs("test_init_compat_config", config, - api=API_COMPAT, env=env, - ignore_stderr=True, cwd=tmpdir) - if MS_WINDOWS: - self.assertEqual( - actual['windows']['python3_dll'], - os.path.join( - tmpdir, - os.path.basename(self.EXPECTED_CONFIG['windows']['python3_dll']) - ) - ) + ver = sys.version_info + dll = f'python{ver.major}' + if debug_build(executable): + dll += '_d' + dll += '.DLL' + dll = os.path.join(os.path.dirname(executable), dll) + path_config['python3_dll'] = dll + + env = self.copy_paths_by_env(config) + self.check_all_configs("test_init_compat_config", config, + expected_pathconfig=path_config, + api=API_COMPAT, env=env, + ignore_stderr=True, cwd=tmpdir) def test_global_pathconfig(self): # Test C API functions getting the path configuration: diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index f08bf8d83d4..ad74af8363e 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -18,53 +18,10 @@ #include "pycore_gc.h" // PyGC_Head -#ifdef MS_WINDOWS -#include - -static int -_add_windows_config(PyObject *configs) -{ - HMODULE hPython3; - wchar_t py3path[MAX_PATH]; - PyObject *dict = PyDict_New(); - PyObject *obj = NULL; - if (!dict) { - return -1; - } - - hPython3 = GetModuleHandleW(PY3_DLLNAME); - if (hPython3 && GetModuleFileNameW(hPython3, py3path, MAX_PATH)) { - obj = PyUnicode_FromWideChar(py3path, -1); - } else { - obj = Py_None; - Py_INCREF(obj); - } - if (obj && - !PyDict_SetItemString(dict, "python3_dll", obj) && - !PyDict_SetItemString(configs, "windows", dict)) { - Py_DECREF(obj); - Py_DECREF(dict); - return 0; - } - Py_DECREF(obj); - Py_DECREF(dict); - return -1; -} -#endif - - static PyObject * get_configs(PyObject *self, PyObject *Py_UNUSED(args)) { - PyObject *dict = _Py_GetConfigsAsDict(); -#ifdef MS_WINDOWS - if (dict) { - if (_add_windows_config(dict) < 0) { - Py_CLEAR(dict); - } - } -#endif - return dict; + return _Py_GetConfigsAsDict(); } diff --git a/Python/initconfig.c b/Python/initconfig.c index 86285c77e23..64286763b62 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -868,9 +868,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2) static PyObject * config_as_dict(const PyConfig *config) { - PyObject *dict; - - dict = PyDict_New(); + PyObject *dict = PyDict_New(); if (dict == NULL) { return NULL; } @@ -2643,6 +2641,16 @@ _Py_GetConfigsAsDict(void) } Py_CLEAR(dict); + /* path config */ + dict = _PyPathConfig_AsDict(); + if (dict == NULL) { + goto error; + } + if (PyDict_SetItemString(result, "path_config", dict) < 0) { + goto error; + } + Py_CLEAR(dict); + return result; error: diff --git a/Python/pathconfig.c b/Python/pathconfig.c index 9a302213e77..12a684a66b7 100644 --- a/Python/pathconfig.c +++ b/Python/pathconfig.c @@ -186,6 +186,80 @@ done: return status; } +PyObject * +_PyPathConfig_AsDict(void) +{ + PyObject *dict = PyDict_New(); + if (dict == NULL) { + return NULL; + } + +#define SET_ITEM(KEY, EXPR) \ + do { \ + PyObject *obj = (EXPR); \ + if (obj == NULL) { \ + goto fail; \ + } \ + int res = PyDict_SetItemString(dict, KEY, obj); \ + Py_DECREF(obj); \ + if (res < 0) { \ + goto fail; \ + } \ + } while (0) +#define SET_ITEM_STR(KEY) \ + SET_ITEM(#KEY, \ + (_Py_path_config.KEY \ + ? PyUnicode_FromWideChar(_Py_path_config.KEY, -1) \ + : (Py_INCREF(Py_None), Py_None))) +#define SET_ITEM_INT(KEY) \ + SET_ITEM(#KEY, PyLong_FromLong(_Py_path_config.KEY)) + + SET_ITEM_STR(program_full_path); + SET_ITEM_STR(prefix); + SET_ITEM_STR(exec_prefix); + SET_ITEM_STR(module_search_path); + SET_ITEM_STR(program_name); + SET_ITEM_STR(home); +#ifdef MS_WINDOWS + SET_ITEM_INT(isolated); + SET_ITEM_INT(site_import); + SET_ITEM_STR(base_executable); + + { + wchar_t py3path[MAX_PATH]; + HMODULE hPython3 = GetModuleHandleW(PY3_DLLNAME); + PyObject *obj; + if (hPython3 + && GetModuleFileNameW(hPython3, py3path, Py_ARRAY_LENGTH(py3path))) + { + obj = PyUnicode_FromWideChar(py3path, -1); + if (obj == NULL) { + goto fail; + } + } + else { + obj = Py_None; + Py_INCREF(obj); + } + if (PyDict_SetItemString(dict, "python3_dll", obj) < 0) { + Py_DECREF(obj); + goto fail; + } + Py_DECREF(obj); + } +#endif + +#undef SET_ITEM +#undef SET_ITEM_STR +#undef SET_ITEM_INT + + return dict; + +fail: + Py_DECREF(dict); + return NULL; +} + PyStatus _PyConfig_WritePathConfig(const PyConfig *config)