Skip to content

Commit a6537fb

Browse files
authored
bpo-35313: Fix test_embed when run from venv (GH-10713)
test_embed.InitConfigTests now gets the expected configuration from a child process run with -S to not run the site module.
1 parent f0e0f20 commit a6537fb

File tree

1 file changed

+68
-63
lines changed

1 file changed

+68
-63
lines changed

Lib/test/test_embed.py

Lines changed: 68 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import re
1010
import subprocess
1111
import sys
12+
import textwrap
1213

1314

1415
MS_WINDOWS = (os.name == 'nt')
@@ -265,6 +266,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
265266
'executable',
266267
'module_search_paths',
267268
)
269+
# Mark config which should be get by get_default_config()
270+
GET_DEFAULT_CONFIG = object()
268271
DEFAULT_CORE_CONFIG = {
269272
'install_signal_handlers': 1,
270273
'use_environment': 1,
@@ -280,9 +283,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
280283
'dump_refs': 0,
281284
'malloc_stats': 0,
282285

283-
# None means that the value is get by get_locale_encoding()
284-
'filesystem_encoding': None,
285-
'filesystem_errors': None,
286+
'filesystem_encoding': GET_DEFAULT_CONFIG,
287+
'filesystem_errors': GET_DEFAULT_CONFIG,
286288

287289
'utf8_mode': 0,
288290
'coerce_c_locale': 0,
@@ -298,10 +300,11 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
298300

299301
'module_search_path_env': None,
300302
'home': None,
301-
'prefix': sys.prefix,
302-
'base_prefix': sys.base_prefix,
303-
'exec_prefix': sys.exec_prefix,
304-
'base_exec_prefix': sys.base_exec_prefix,
303+
304+
'prefix': GET_DEFAULT_CONFIG,
305+
'base_prefix': GET_DEFAULT_CONFIG,
306+
'exec_prefix': GET_DEFAULT_CONFIG,
307+
'base_exec_prefix': GET_DEFAULT_CONFIG,
305308

306309
'isolated': 0,
307310
'site_import': 1,
@@ -316,9 +319,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
316319
'user_site_directory': 1,
317320
'buffered_stdio': 1,
318321

319-
# None means that the value is get by get_stdio_encoding()
320-
'stdio_encoding': None,
321-
'stdio_errors': None,
322+
'stdio_encoding': GET_DEFAULT_CONFIG,
323+
'stdio_errors': GET_DEFAULT_CONFIG,
322324

323325
'_install_importlib': 1,
324326
'_check_hash_pycs_mode': 'default',
@@ -379,35 +381,6 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
379381
('Py_LegacyWindowsStdioFlag', 'legacy_windows_stdio'),
380382
))
381383

382-
def get_stdio_encoding(self, env):
383-
code = 'import sys; print(sys.stdout.encoding, sys.stdout.errors)'
384-
args = (sys.executable, '-c', code)
385-
proc = subprocess.run(args, env=env, text=True,
386-
stdout=subprocess.PIPE,
387-
stderr=subprocess.STDOUT)
388-
if proc.returncode:
389-
raise Exception(f"failed to get the stdio encoding: stdout={proc.stdout!r}")
390-
out = proc.stdout.rstrip()
391-
return out.split()
392-
393-
def get_filesystem_encoding(self, isolated, env):
394-
code = ('import codecs, locale, sys; '
395-
'print(sys.getfilesystemencoding(), '
396-
'sys.getfilesystemencodeerrors())')
397-
args = (sys.executable, '-c', code)
398-
env = dict(env)
399-
if not isolated:
400-
env['PYTHONCOERCECLOCALE'] = '0'
401-
env['PYTHONUTF8'] = '0'
402-
proc = subprocess.run(args, text=True, env=env,
403-
stdout=subprocess.PIPE,
404-
stderr=subprocess.PIPE)
405-
if proc.returncode:
406-
raise Exception(f"failed to get the locale encoding: "
407-
f"stdout={proc.stdout!r} stderr={proc.stderr!r}")
408-
out = proc.stdout.rstrip()
409-
return out.split()
410-
411384
def main_xoptions(self, xoptions_list):
412385
xoptions = {}
413386
for opt in xoptions_list:
@@ -423,27 +396,61 @@ def check_main_config(self, config):
423396
main_config = config['main_config']
424397

425398
# main config
426-
expected_main = {}
399+
expected = {}
427400
for key in self.COPY_MAIN_CONFIG:
428-
expected_main[key] = core_config[key]
429-
expected_main['module_search_path'] = core_config['module_search_paths']
430-
expected_main['xoptions'] = self.main_xoptions(core_config['xoptions'])
431-
self.assertEqual(main_config, expected_main)
401+
expected[key] = core_config[key]
402+
expected['module_search_path'] = core_config['module_search_paths']
403+
expected['xoptions'] = self.main_xoptions(core_config['xoptions'])
404+
self.assertEqual(main_config, expected)
432405

433-
def check_core_config(self, config, expected, env):
434-
if expected['stdio_encoding'] is None or expected['stdio_errors'] is None:
435-
res = self.get_stdio_encoding(env)
436-
if expected['stdio_encoding'] is None:
437-
expected['stdio_encoding'] = res[0]
438-
if expected['stdio_errors'] is None:
439-
expected['stdio_errors'] = res[1]
440-
if expected['filesystem_encoding'] is None or expected['filesystem_errors'] is None:
441-
res = self.get_filesystem_encoding(expected['isolated'], env)
442-
if expected['filesystem_encoding'] is None:
443-
expected['filesystem_encoding'] = res[0]
444-
if expected['filesystem_errors'] is None:
445-
expected['filesystem_errors'] = res[1]
406+
def get_expected_config(self, expected, env):
407+
expected = dict(self.DEFAULT_CORE_CONFIG, **expected)
446408

409+
code = textwrap.dedent('''
410+
import json
411+
import locale
412+
import sys
413+
414+
data = {
415+
'stdio_encoding': sys.stdout.encoding,
416+
'stdio_errors': sys.stdout.errors,
417+
'prefix': sys.prefix,
418+
'base_prefix': sys.base_prefix,
419+
'exec_prefix': sys.exec_prefix,
420+
'base_exec_prefix': sys.base_exec_prefix,
421+
'filesystem_encoding': sys.getfilesystemencoding(),
422+
'filesystem_errors': sys.getfilesystemencodeerrors(),
423+
}
424+
425+
data = json.dumps(data)
426+
data = data.encode('utf-8')
427+
sys.stdout.buffer.write(data)
428+
sys.stdout.buffer.flush()
429+
''')
430+
431+
# Use -S to not import the site module: get the proper configuration
432+
# when test_embed is run from a venv (bpo-35313)
433+
args = (sys.executable, '-S', '-c', code)
434+
env = dict(env)
435+
if not expected['isolated']:
436+
env['PYTHONCOERCECLOCALE'] = '0'
437+
env['PYTHONUTF8'] = '0'
438+
proc = subprocess.run(args, env=env,
439+
stdout=subprocess.PIPE,
440+
stderr=subprocess.STDOUT)
441+
if proc.returncode:
442+
raise Exception(f"failed to get the default config: "
443+
f"stdout={proc.stdout!r} stderr={proc.stderr!r}")
444+
stdout = proc.stdout.decode('utf-8')
445+
config = json.loads(stdout)
446+
447+
for key, value in expected.items():
448+
if value is self.GET_DEFAULT_CONFIG:
449+
expected[key] = config[key]
450+
return expected
451+
452+
def check_core_config(self, config, expected, env):
453+
expected = self.get_expected_config(expected, env)
447454
core_config = dict(config['core_config'])
448455
for key in self.UNTESTED_CORE_CONFIG:
449456
core_config.pop(key, None)
@@ -452,20 +459,18 @@ def check_core_config(self, config, expected, env):
452459
def check_global_config(self, config):
453460
core_config = config['core_config']
454461

455-
expected_global = dict(self.DEFAULT_GLOBAL_CONFIG)
462+
expected = dict(self.DEFAULT_GLOBAL_CONFIG)
456463
for item in self.COPY_GLOBAL_CONFIG:
457464
if len(item) == 3:
458465
global_key, core_key, opposite = item
459-
expected_global[global_key] = 0 if core_config[core_key] else 1
466+
expected[global_key] = 0 if core_config[core_key] else 1
460467
else:
461468
global_key, core_key = item
462-
expected_global[global_key] = core_config[core_key]
469+
expected[global_key] = core_config[core_key]
463470

464-
self.assertEqual(config['global_config'], expected_global)
471+
self.assertEqual(config['global_config'], expected)
465472

466473
def check_config(self, testname, expected):
467-
expected = dict(self.DEFAULT_CORE_CONFIG, **expected)
468-
469474
env = dict(os.environ)
470475
# Remove PYTHON* environment variables to get deterministic environment
471476
for key in list(env):

0 commit comments

Comments
 (0)