Skip to content

Commit 406ff91

Browse files
vstinnerarun-mani-j
authored andcommitted
bpo-29778: test_embed tests the path configuration (pythonGH-21306)
1 parent 4e45729 commit 406ff91

File tree

5 files changed

+154
-62
lines changed

5 files changed

+154
-62
lines changed

Include/internal/pycore_pathconfig.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ extern wchar_t* _Py_GetDLLPath(void);
6565

6666
extern PyStatus _PyConfig_WritePathConfig(const PyConfig *config);
6767
extern void _Py_DumpPathConfig(PyThreadState *tstate);
68+
extern PyObject* _PyPathConfig_AsDict(void);
6869

6970
#ifdef __cplusplus
7071
}

Lib/test/test_embed.py

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,31 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
471471
('Py_LegacyWindowsStdioFlag', 'legacy_windows_stdio'),
472472
))
473473

474+
# path config
475+
if MS_WINDOWS:
476+
PATH_CONFIG = {
477+
'isolated': -1,
478+
'site_import': -1,
479+
'python3_dll': GET_DEFAULT_CONFIG,
480+
}
481+
else:
482+
PATH_CONFIG = {}
483+
# other keys are copied by COPY_PATH_CONFIG
484+
485+
COPY_PATH_CONFIG = [
486+
# Copy core config to global config for expected values
487+
'prefix',
488+
'exec_prefix',
489+
'program_name',
490+
'home',
491+
# program_full_path and module_search_path are copied indirectly from
492+
# the core configuration in check_path_config().
493+
]
494+
if MS_WINDOWS:
495+
COPY_PATH_CONFIG.extend((
496+
'base_executable',
497+
))
498+
474499
EXPECTED_CONFIG = None
475500

476501
@classmethod
@@ -535,7 +560,8 @@ def _get_expected_config(self):
535560
configs[config_key] = config
536561
return configs
537562

538-
def get_expected_config(self, expected_preconfig, expected, env, api,
563+
def get_expected_config(self, expected_preconfig, expected,
564+
expected_pathconfig, env, api,
539565
modify_path_cb=None):
540566
cls = self.__class__
541567
configs = self._get_expected_config()
@@ -545,6 +571,11 @@ def get_expected_config(self, expected_preconfig, expected, env, api,
545571
if value is self.GET_DEFAULT_CONFIG:
546572
expected_preconfig[key] = pre_config[key]
547573

574+
path_config = configs['path_config']
575+
for key, value in expected_pathconfig.items():
576+
if value is self.GET_DEFAULT_CONFIG:
577+
expected_pathconfig[key] = path_config[key]
578+
548579
if not expected_preconfig['configure_locale'] or api == API_COMPAT:
549580
# there is no easy way to get the locale encoding before
550581
# setlocale(LC_CTYPE, "") is called: don't test encodings
@@ -637,8 +668,19 @@ def check_global_config(self, configs):
637668

638669
self.assertEqual(configs['global_config'], expected)
639670

671+
def check_path_config(self, configs, expected):
672+
config = configs['config']
673+
674+
for key in self.COPY_PATH_CONFIG:
675+
expected[key] = config[key]
676+
expected['module_search_path'] = os.path.pathsep.join(config['module_search_paths'])
677+
expected['program_full_path'] = config['executable']
678+
679+
self.assertEqual(configs['path_config'], expected)
680+
640681
def check_all_configs(self, testname, expected_config=None,
641-
expected_preconfig=None, modify_path_cb=None,
682+
expected_preconfig=None, expected_pathconfig=None,
683+
modify_path_cb=None,
642684
stderr=None, *, api, preconfig_api=None,
643685
env=None, ignore_stderr=False, cwd=None):
644686
new_env = remove_python_envvars()
@@ -657,9 +699,14 @@ def check_all_configs(self, testname, expected_config=None,
657699
if expected_preconfig is None:
658700
expected_preconfig = {}
659701
expected_preconfig = dict(default_preconfig, **expected_preconfig)
702+
660703
if expected_config is None:
661704
expected_config = {}
662705

706+
if expected_pathconfig is None:
707+
expected_pathconfig = {}
708+
expected_pathconfig = dict(self.PATH_CONFIG, **expected_pathconfig)
709+
663710
if api == API_PYTHON:
664711
default_config = self.CONFIG_PYTHON
665712
elif api == API_ISOLATED:
@@ -669,7 +716,9 @@ def check_all_configs(self, testname, expected_config=None,
669716
expected_config = dict(default_config, **expected_config)
670717

671718
self.get_expected_config(expected_preconfig,
672-
expected_config, env,
719+
expected_config,
720+
expected_pathconfig,
721+
env,
673722
api, modify_path_cb)
674723

675724
out, err = self.run_embedded_interpreter(testname,
@@ -686,6 +735,7 @@ def check_all_configs(self, testname, expected_config=None,
686735
self.check_pre_config(configs, expected_preconfig)
687736
self.check_config(configs, expected_config)
688737
self.check_global_config(configs)
738+
self.check_path_config(configs, expected_pathconfig)
689739
return configs
690740

691741
def test_init_default_config(self):
@@ -1258,22 +1308,24 @@ def test_init_pyvenv_cfg(self):
12581308
'executable': executable,
12591309
'module_search_paths': paths,
12601310
}
1311+
path_config = {}
12611312
if MS_WINDOWS:
12621313
config['base_prefix'] = pyvenv_home
12631314
config['prefix'] = pyvenv_home
1264-
env = self.copy_paths_by_env(config)
1265-
actual = self.check_all_configs("test_init_compat_config", config,
1266-
api=API_COMPAT, env=env,
1267-
ignore_stderr=True, cwd=tmpdir)
1268-
if MS_WINDOWS:
1269-
self.assertEqual(
1270-
actual['windows']['python3_dll'],
1271-
os.path.join(
1272-
tmpdir,
1273-
os.path.basename(self.EXPECTED_CONFIG['windows']['python3_dll'])
1274-
)
1275-
)
12761315

1316+
ver = sys.version_info
1317+
dll = f'python{ver.major}'
1318+
if debug_build(executable):
1319+
dll += '_d'
1320+
dll += '.DLL'
1321+
dll = os.path.join(os.path.dirname(executable), dll)
1322+
path_config['python3_dll'] = dll
1323+
1324+
env = self.copy_paths_by_env(config)
1325+
self.check_all_configs("test_init_compat_config", config,
1326+
expected_pathconfig=path_config,
1327+
api=API_COMPAT, env=env,
1328+
ignore_stderr=True, cwd=tmpdir)
12771329

12781330
def test_global_pathconfig(self):
12791331
# Test C API functions getting the path configuration:

Modules/_testinternalcapi.c

Lines changed: 1 addition & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -18,53 +18,10 @@
1818
#include "pycore_gc.h" // PyGC_Head
1919

2020

21-
#ifdef MS_WINDOWS
22-
#include <windows.h>
23-
24-
static int
25-
_add_windows_config(PyObject *configs)
26-
{
27-
HMODULE hPython3;
28-
wchar_t py3path[MAX_PATH];
29-
PyObject *dict = PyDict_New();
30-
PyObject *obj = NULL;
31-
if (!dict) {
32-
return -1;
33-
}
34-
35-
hPython3 = GetModuleHandleW(PY3_DLLNAME);
36-
if (hPython3 && GetModuleFileNameW(hPython3, py3path, MAX_PATH)) {
37-
obj = PyUnicode_FromWideChar(py3path, -1);
38-
} else {
39-
obj = Py_None;
40-
Py_INCREF(obj);
41-
}
42-
if (obj &&
43-
!PyDict_SetItemString(dict, "python3_dll", obj) &&
44-
!PyDict_SetItemString(configs, "windows", dict)) {
45-
Py_DECREF(obj);
46-
Py_DECREF(dict);
47-
return 0;
48-
}
49-
Py_DECREF(obj);
50-
Py_DECREF(dict);
51-
return -1;
52-
}
53-
#endif
54-
55-
5621
static PyObject *
5722
get_configs(PyObject *self, PyObject *Py_UNUSED(args))
5823
{
59-
PyObject *dict = _Py_GetConfigsAsDict();
60-
#ifdef MS_WINDOWS
61-
if (dict) {
62-
if (_add_windows_config(dict) < 0) {
63-
Py_CLEAR(dict);
64-
}
65-
}
66-
#endif
67-
return dict;
24+
return _Py_GetConfigsAsDict();
6825
}
6926

7027

Python/initconfig.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -868,9 +868,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
868868
static PyObject *
869869
config_as_dict(const PyConfig *config)
870870
{
871-
PyObject *dict;
872-
873-
dict = PyDict_New();
871+
PyObject *dict = PyDict_New();
874872
if (dict == NULL) {
875873
return NULL;
876874
}
@@ -2643,6 +2641,16 @@ _Py_GetConfigsAsDict(void)
26432641
}
26442642
Py_CLEAR(dict);
26452643

2644+
/* path config */
2645+
dict = _PyPathConfig_AsDict();
2646+
if (dict == NULL) {
2647+
goto error;
2648+
}
2649+
if (PyDict_SetItemString(result, "path_config", dict) < 0) {
2650+
goto error;
2651+
}
2652+
Py_CLEAR(dict);
2653+
26462654
return result;
26472655

26482656
error:

Python/pathconfig.c

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,80 @@ pathconfig_set_from_config(_PyPathConfig *pathconfig, const PyConfig *config)
186186
return status;
187187
}
188188

189+
PyObject *
190+
_PyPathConfig_AsDict(void)
191+
{
192+
PyObject *dict = PyDict_New();
193+
if (dict == NULL) {
194+
return NULL;
195+
}
196+
197+
#define SET_ITEM(KEY, EXPR) \
198+
do { \
199+
PyObject *obj = (EXPR); \
200+
if (obj == NULL) { \
201+
goto fail; \
202+
} \
203+
int res = PyDict_SetItemString(dict, KEY, obj); \
204+
Py_DECREF(obj); \
205+
if (res < 0) { \
206+
goto fail; \
207+
} \
208+
} while (0)
209+
#define SET_ITEM_STR(KEY) \
210+
SET_ITEM(#KEY, \
211+
(_Py_path_config.KEY \
212+
? PyUnicode_FromWideChar(_Py_path_config.KEY, -1) \
213+
: (Py_INCREF(Py_None), Py_None)))
214+
#define SET_ITEM_INT(KEY) \
215+
SET_ITEM(#KEY, PyLong_FromLong(_Py_path_config.KEY))
216+
217+
SET_ITEM_STR(program_full_path);
218+
SET_ITEM_STR(prefix);
219+
SET_ITEM_STR(exec_prefix);
220+
SET_ITEM_STR(module_search_path);
221+
SET_ITEM_STR(program_name);
222+
SET_ITEM_STR(home);
223+
#ifdef MS_WINDOWS
224+
SET_ITEM_INT(isolated);
225+
SET_ITEM_INT(site_import);
226+
SET_ITEM_STR(base_executable);
227+
228+
{
229+
wchar_t py3path[MAX_PATH];
230+
HMODULE hPython3 = GetModuleHandleW(PY3_DLLNAME);
231+
PyObject *obj;
232+
if (hPython3
233+
&& GetModuleFileNameW(hPython3, py3path, Py_ARRAY_LENGTH(py3path)))
234+
{
235+
obj = PyUnicode_FromWideChar(py3path, -1);
236+
if (obj == NULL) {
237+
goto fail;
238+
}
239+
}
240+
else {
241+
obj = Py_None;
242+
Py_INCREF(obj);
243+
}
244+
if (PyDict_SetItemString(dict, "python3_dll", obj) < 0) {
245+
Py_DECREF(obj);
246+
goto fail;
247+
}
248+
Py_DECREF(obj);
249+
}
250+
#endif
251+
252+
#undef SET_ITEM
253+
#undef SET_ITEM_STR
254+
#undef SET_ITEM_INT
255+
256+
return dict;
257+
258+
fail:
259+
Py_DECREF(dict);
260+
return NULL;
261+
}
262+
189263

190264
PyStatus
191265
_PyConfig_WritePathConfig(const PyConfig *config)

0 commit comments

Comments
 (0)