Skip to content

bpo-29778: test_embed tests the path configuration #21306

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/internal/pycore_pathconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
82 changes: 67 additions & 15 deletions Lib/test/test_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -535,7 +560,8 @@ def _get_expected_config(self):
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()
Expand All @@ -545,6 +571,11 @@ def get_expected_config(self, expected_preconfig, expected, env, api,
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
Expand Down Expand Up @@ -637,8 +668,19 @@ def check_global_config(self, configs):

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()
Expand All @@ -657,9 +699,14 @@ def check_all_configs(self, testname, expected_config=None,
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:
Expand All @@ -669,7 +716,9 @@ def check_all_configs(self, testname, expected_config=None,
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,
Expand All @@ -686,6 +735,7 @@ def check_all_configs(self, testname, expected_config=None,
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):
Expand Down Expand Up @@ -1258,22 +1308,24 @@ def test_init_pyvenv_cfg(self):
'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:
Expand Down
45 changes: 1 addition & 44 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,53 +18,10 @@
#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 *
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();
}


Expand Down
14 changes: 11 additions & 3 deletions Python/initconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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:
Expand Down
74 changes: 74 additions & 0 deletions Python/pathconfig.c
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,80 @@ pathconfig_set_from_config(_PyPathConfig *pathconfig, const PyConfig *config)
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)
Expand Down