bpo-29778: test_embed tests the path configuration (GH-21306)

This commit is contained in:
Victor Stinner 2020-07-08 00:20:37 +02:00 committed by GitHub
parent 6ae2780be0
commit 8f42748ded
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 154 additions and 62 deletions

View File

@ -65,6 +65,7 @@ extern wchar_t* _Py_GetDLLPath(void);
extern PyStatus _PyConfig_WritePathConfig(const PyConfig *config); extern PyStatus _PyConfig_WritePathConfig(const PyConfig *config);
extern void _Py_DumpPathConfig(PyThreadState *tstate); extern void _Py_DumpPathConfig(PyThreadState *tstate);
extern PyObject* _PyPathConfig_AsDict(void);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -471,6 +471,31 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
('Py_LegacyWindowsStdioFlag', 'legacy_windows_stdio'), ('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 EXPECTED_CONFIG = None
@classmethod @classmethod
@ -535,7 +560,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
configs[config_key] = config configs[config_key] = config
return configs 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): modify_path_cb=None):
cls = self.__class__ cls = self.__class__
configs = self._get_expected_config() configs = self._get_expected_config()
@ -545,6 +571,11 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
if value is self.GET_DEFAULT_CONFIG: if value is self.GET_DEFAULT_CONFIG:
expected_preconfig[key] = pre_config[key] 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: if not expected_preconfig['configure_locale'] or api == API_COMPAT:
# there is no easy way to get the locale encoding before # there is no easy way to get the locale encoding before
# setlocale(LC_CTYPE, "") is called: don't test encodings # setlocale(LC_CTYPE, "") is called: don't test encodings
@ -637,8 +668,19 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
self.assertEqual(configs['global_config'], expected) 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, 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, stderr=None, *, api, preconfig_api=None,
env=None, ignore_stderr=False, cwd=None): env=None, ignore_stderr=False, cwd=None):
new_env = remove_python_envvars() new_env = remove_python_envvars()
@ -657,9 +699,14 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
if expected_preconfig is None: if expected_preconfig is None:
expected_preconfig = {} expected_preconfig = {}
expected_preconfig = dict(default_preconfig, **expected_preconfig) expected_preconfig = dict(default_preconfig, **expected_preconfig)
if expected_config is None: if expected_config is None:
expected_config = {} expected_config = {}
if expected_pathconfig is None:
expected_pathconfig = {}
expected_pathconfig = dict(self.PATH_CONFIG, **expected_pathconfig)
if api == API_PYTHON: if api == API_PYTHON:
default_config = self.CONFIG_PYTHON default_config = self.CONFIG_PYTHON
elif api == API_ISOLATED: elif api == API_ISOLATED:
@ -669,7 +716,9 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
expected_config = dict(default_config, **expected_config) expected_config = dict(default_config, **expected_config)
self.get_expected_config(expected_preconfig, self.get_expected_config(expected_preconfig,
expected_config, env, expected_config,
expected_pathconfig,
env,
api, modify_path_cb) api, modify_path_cb)
out, err = self.run_embedded_interpreter(testname, 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_pre_config(configs, expected_preconfig)
self.check_config(configs, expected_config) self.check_config(configs, expected_config)
self.check_global_config(configs) self.check_global_config(configs)
self.check_path_config(configs, expected_pathconfig)
return configs return configs
def test_init_default_config(self): def test_init_default_config(self):
@ -1258,22 +1308,24 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'executable': executable, 'executable': executable,
'module_search_paths': paths, 'module_search_paths': paths,
} }
path_config = {}
if MS_WINDOWS: if MS_WINDOWS:
config['base_prefix'] = pyvenv_home config['base_prefix'] = pyvenv_home
config['prefix'] = pyvenv_home config['prefix'] = pyvenv_home
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) env = self.copy_paths_by_env(config)
actual = self.check_all_configs("test_init_compat_config", config, self.check_all_configs("test_init_compat_config", config,
expected_pathconfig=path_config,
api=API_COMPAT, env=env, api=API_COMPAT, env=env,
ignore_stderr=True, cwd=tmpdir) 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'])
)
)
def test_global_pathconfig(self): def test_global_pathconfig(self):
# Test C API functions getting the path configuration: # Test C API functions getting the path configuration:

View File

@ -18,53 +18,10 @@
#include "pycore_gc.h" // PyGC_Head #include "pycore_gc.h" // PyGC_Head
#ifdef MS_WINDOWS
#include <windows.h>
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 * static PyObject *
get_configs(PyObject *self, PyObject *Py_UNUSED(args)) get_configs(PyObject *self, PyObject *Py_UNUSED(args))
{ {
PyObject *dict = _Py_GetConfigsAsDict(); return _Py_GetConfigsAsDict();
#ifdef MS_WINDOWS
if (dict) {
if (_add_windows_config(dict) < 0) {
Py_CLEAR(dict);
}
}
#endif
return dict;
} }

View File

@ -868,9 +868,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
static PyObject * static PyObject *
config_as_dict(const PyConfig *config) config_as_dict(const PyConfig *config)
{ {
PyObject *dict; PyObject *dict = PyDict_New();
dict = PyDict_New();
if (dict == NULL) { if (dict == NULL) {
return NULL; return NULL;
} }
@ -2643,6 +2641,16 @@ _Py_GetConfigsAsDict(void)
} }
Py_CLEAR(dict); 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; return result;
error: error:

View File

@ -186,6 +186,80 @@ done:
return status; 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 PyStatus
_PyConfig_WritePathConfig(const PyConfig *config) _PyConfig_WritePathConfig(const PyConfig *config)