Skip to content

Commit 425717f

Browse files
authored
bpo-36763: Fix encoding/locale tests in test_embed (pythonGH-13443)
* Fix encoding and locale tests in test_embed.InitConfigTests. * InitConfigTests now only computes EXPECTED_CONFIG once. * Add tests for PYTHONWARNINGS and PYTHONPATH env vars
1 parent 9932fd9 commit 425717f

File tree

2 files changed

+113
-96
lines changed

2 files changed

+113
-96
lines changed

Lib/test/test_embed.py

Lines changed: 103 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -296,13 +296,16 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
296296
})
297297
PYTHON_PRE_CONFIG = dict(DEFAULT_PRE_CONFIG,
298298
parse_argv=1,
299+
coerce_c_locale=GET_DEFAULT_CONFIG,
300+
utf8_mode=GET_DEFAULT_CONFIG,
299301
)
300302
ISOLATED_PRE_CONFIG = dict(DEFAULT_PRE_CONFIG,
301303
configure_locale=0,
302304
isolated=1,
303305
use_environment=0,
304306
utf8_mode=0,
305307
dev_mode=0,
308+
coerce_c_locale=0,
306309
)
307310

308311
COPY_PRE_CONFIG = [
@@ -435,6 +438,8 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
435438
('Py_LegacyWindowsStdioFlag', 'legacy_windows_stdio'),
436439
))
437440

441+
EXPECTED_CONFIG = None
442+
438443
def main_xoptions(self, xoptions_list):
439444
xoptions = {}
440445
for opt in xoptions_list:
@@ -445,37 +450,15 @@ def main_xoptions(self, xoptions_list):
445450
xoptions[opt] = True
446451
return xoptions
447452

448-
def get_expected_config(self, expected_preconfig, expected, env, api,
449-
add_path=None):
450-
if api == CONFIG_INIT_PYTHON:
451-
default_config = self.PYTHON_CORE_CONFIG
452-
elif api == CONFIG_INIT_ISOLATED:
453-
default_config = self.ISOLATED_CORE_CONFIG
454-
else:
455-
default_config = self.DEFAULT_CORE_CONFIG
456-
expected = dict(default_config, **expected)
457-
expected['_config_init'] = api
458-
453+
def _get_expected_config(self, env):
459454
code = textwrap.dedent('''
460455
import json
461456
import sys
462457
import _testinternalcapi
463458
464459
configs = _testinternalcapi.get_configs()
465-
core_config = configs['core_config']
466-
data = {
467-
'stdio_encoding': sys.stdout.encoding,
468-
'stdio_errors': sys.stdout.errors,
469-
'prefix': sys.prefix,
470-
'base_prefix': sys.base_prefix,
471-
'exec_prefix': sys.exec_prefix,
472-
'base_exec_prefix': sys.base_exec_prefix,
473-
'filesystem_encoding': sys.getfilesystemencoding(),
474-
'filesystem_errors': sys.getfilesystemencodeerrors(),
475-
'module_search_paths': core_config['module_search_paths'],
476-
}
477-
478-
data = json.dumps(data)
460+
461+
data = json.dumps(configs)
479462
data = data.encode('utf-8')
480463
sys.stdout.buffer.write(data)
481464
sys.stdout.buffer.flush()
@@ -484,10 +467,6 @@ def get_expected_config(self, expected_preconfig, expected, env, api,
484467
# Use -S to not import the site module: get the proper configuration
485468
# when test_embed is run from a venv (bpo-35313)
486469
args = [sys.executable, '-S', '-c', code]
487-
env = dict(env)
488-
if not expected['isolated']:
489-
env['PYTHONCOERCECLOCALE'] = '0'
490-
env['PYTHONUTF8'] = '0'
491470
proc = subprocess.run(args, env=env,
492471
stdout=subprocess.PIPE,
493472
stderr=subprocess.STDOUT)
@@ -496,10 +475,46 @@ def get_expected_config(self, expected_preconfig, expected, env, api,
496475
f"stdout={proc.stdout!r} stderr={proc.stderr!r}")
497476
stdout = proc.stdout.decode('utf-8')
498477
try:
499-
config = json.loads(stdout)
478+
return json.loads(stdout)
500479
except json.JSONDecodeError:
501480
self.fail(f"fail to decode stdout: {stdout!r}")
502481

482+
def get_expected_config(self, expected_preconfig, expected, env, api,
483+
add_path=None):
484+
cls = self.__class__
485+
if cls.EXPECTED_CONFIG is None:
486+
cls.EXPECTED_CONFIG = self._get_expected_config(env)
487+
configs = {key: dict(value)
488+
for key, value in self.EXPECTED_CONFIG.items()}
489+
490+
pre_config = configs['pre_config']
491+
for key, value in expected_preconfig.items():
492+
if value is self.GET_DEFAULT_CONFIG:
493+
expected_preconfig[key] = pre_config[key]
494+
495+
if not expected_preconfig['configure_locale'] or api == CONFIG_INIT:
496+
# there is no easy way to get the locale encoding before
497+
# setlocale(LC_CTYPE, "") is called: don't test encodings
498+
for key in ('filesystem_encoding', 'filesystem_errors',
499+
'stdio_encoding', 'stdio_errors'):
500+
expected[key] = self.IGNORE_CONFIG
501+
502+
if not expected_preconfig['configure_locale']:
503+
# UTF-8 Mode depends on the locale. There is no easy way
504+
# to guess if UTF-8 Mode will be enabled or not if the locale
505+
# is not configured.
506+
expected_preconfig['utf8_mode'] = self.IGNORE_CONFIG
507+
508+
if expected_preconfig['utf8_mode'] == 1:
509+
if expected['filesystem_encoding'] is self.GET_DEFAULT_CONFIG:
510+
expected['filesystem_encoding'] = 'utf-8'
511+
if expected['filesystem_errors'] is self.GET_DEFAULT_CONFIG:
512+
expected['filesystem_errors'] = self.UTF8_MODE_ERRORS
513+
if expected['stdio_encoding'] is self.GET_DEFAULT_CONFIG:
514+
expected['stdio_encoding'] = 'utf-8'
515+
if expected['stdio_errors'] is self.GET_DEFAULT_CONFIG:
516+
expected['stdio_errors'] = 'surrogateescape'
517+
503518
if expected['executable'] is self.GET_DEFAULT_CONFIG:
504519
if sys.platform == 'win32':
505520
expected['executable'] = self.test_exe
@@ -511,24 +526,28 @@ def get_expected_config(self, expected_preconfig, expected, env, api,
511526
if expected['program_name'] is self.GET_DEFAULT_CONFIG:
512527
expected['program_name'] = './_testembed'
513528

529+
core_config = configs['core_config']
514530
for key, value in expected.items():
515531
if value is self.GET_DEFAULT_CONFIG:
516-
expected[key] = config[key]
532+
expected[key] = core_config[key]
517533

534+
prepend_path = expected['module_search_path_env']
535+
if prepend_path is not None:
536+
expected['module_search_paths'] = [prepend_path, *expected['module_search_paths']]
518537
if add_path is not None:
519-
expected['module_search_paths'].append(add_path)
520-
521-
if not expected_preconfig['configure_locale']:
522-
# there is no easy way to get the locale encoding before
523-
# setlocale(LC_CTYPE, "") is called: don't test encodings
524-
for key in ('filesystem_encoding', 'filesystem_errors',
525-
'stdio_encoding', 'stdio_errors'):
526-
expected[key] = self.IGNORE_CONFIG
538+
expected['module_search_paths'] = [*expected['module_search_paths'], add_path]
527539

528-
return expected
540+
for key in self.COPY_PRE_CONFIG:
541+
if key not in expected_preconfig:
542+
expected_preconfig[key] = expected[key]
529543

530544
def check_pre_config(self, config, expected):
531-
self.assertEqual(config['pre_config'], expected)
545+
pre_config = dict(config['pre_config'])
546+
for key, value in list(expected.items()):
547+
if value is self.IGNORE_CONFIG:
548+
del pre_config[key]
549+
del expected[key]
550+
self.assertEqual(pre_config, expected)
532551

533552
def check_core_config(self, config, expected):
534553
core_config = dict(config['core_config'])
@@ -567,10 +586,6 @@ def check_config(self, testname, expected_config=None, expected_preconfig=None,
567586
for key in list(env):
568587
if key.startswith('PYTHON'):
569588
del env[key]
570-
# Disable C locale coercion and UTF-8 mode to not depend
571-
# on the current locale
572-
env['PYTHONCOERCECLOCALE'] = '0'
573-
env['PYTHONUTF8'] = '0'
574589

575590
if api == CONFIG_INIT_ISOLATED:
576591
default_preconfig = self.ISOLATED_PRE_CONFIG
@@ -583,12 +598,19 @@ def check_config(self, testname, expected_config=None, expected_preconfig=None,
583598
expected_preconfig = dict(default_preconfig, **expected_preconfig)
584599
if expected_config is None:
585600
expected_config = {}
586-
expected_config = self.get_expected_config(expected_preconfig,
587-
expected_config, env,
588-
api, add_path)
589-
for key in self.COPY_PRE_CONFIG:
590-
if key not in expected_preconfig:
591-
expected_preconfig[key] = expected_config[key]
601+
602+
if api == CONFIG_INIT_PYTHON:
603+
default_config = self.PYTHON_CORE_CONFIG
604+
elif api == CONFIG_INIT_ISOLATED:
605+
default_config = self.ISOLATED_CORE_CONFIG
606+
else:
607+
default_config = self.DEFAULT_CORE_CONFIG
608+
expected_config = dict(default_config, **expected_config)
609+
expected_config['_config_init'] = api
610+
611+
self.get_expected_config(expected_preconfig,
612+
expected_config, env,
613+
api, add_path)
592614

593615
out, err = self.run_embedded_interpreter(testname, env=env)
594616
if stderr is None and not expected_config['verbose']:
@@ -624,10 +646,6 @@ def test_init_global_config(self):
624646
'quiet': 1,
625647
'buffered_stdio': 0,
626648

627-
'stdio_encoding': 'utf-8',
628-
'stdio_errors': 'surrogateescape',
629-
'filesystem_encoding': 'utf-8',
630-
'filesystem_errors': self.UTF8_MODE_ERRORS,
631649
'user_site_directory': 0,
632650
'pathconfig_warnings': 0,
633651
}
@@ -650,8 +668,6 @@ def test_init_from_config(self):
650668

651669
'stdio_encoding': 'iso8859-1',
652670
'stdio_errors': 'replace',
653-
'filesystem_encoding': 'utf-8',
654-
'filesystem_errors': self.UTF8_MODE_ERRORS,
655671

656672
'pycache_prefix': 'conf_pycache_prefix',
657673
'program_name': './conf_program_name',
@@ -679,43 +695,42 @@ def test_init_from_config(self):
679695
}
680696
self.check_config("init_from_config", config, preconfig)
681697

682-
INIT_ENV_PRECONFIG = {
683-
'allocator': PYMEM_ALLOCATOR_MALLOC,
684-
}
685-
INIT_ENV_CONFIG = {
686-
'use_hash_seed': 1,
687-
'hash_seed': 42,
688-
'tracemalloc': 2,
689-
'import_time': 1,
690-
'malloc_stats': 1,
691-
'inspect': 1,
692-
'optimization_level': 2,
693-
'pycache_prefix': 'env_pycache_prefix',
694-
'write_bytecode': 0,
695-
'verbose': 1,
696-
'buffered_stdio': 0,
697-
'stdio_encoding': 'iso8859-1',
698-
'stdio_errors': 'replace',
699-
'user_site_directory': 0,
700-
'faulthandler': 1,
701-
}
702-
703698
def test_init_env(self):
704-
self.check_config("init_env", self.INIT_ENV_CONFIG, self.INIT_ENV_PRECONFIG)
699+
preconfig = {
700+
'allocator': PYMEM_ALLOCATOR_MALLOC,
701+
}
702+
config = {
703+
'use_hash_seed': 1,
704+
'hash_seed': 42,
705+
'tracemalloc': 2,
706+
'import_time': 1,
707+
'malloc_stats': 1,
708+
'inspect': 1,
709+
'optimization_level': 2,
710+
'module_search_path_env': '/my/path',
711+
'pycache_prefix': 'env_pycache_prefix',
712+
'write_bytecode': 0,
713+
'verbose': 1,
714+
'buffered_stdio': 0,
715+
'stdio_encoding': 'iso8859-1',
716+
'stdio_errors': 'replace',
717+
'user_site_directory': 0,
718+
'faulthandler': 1,
719+
'warnoptions': ['EnvVar'],
720+
}
721+
self.check_config("init_env", config, preconfig)
705722

706723
def test_init_env_dev_mode(self):
707-
preconfig = dict(self.INIT_ENV_PRECONFIG,
708-
allocator=PYMEM_ALLOCATOR_DEBUG)
709-
config = dict(self.INIT_ENV_CONFIG,
710-
dev_mode=1,
724+
preconfig = dict(allocator=PYMEM_ALLOCATOR_DEBUG)
725+
config = dict(dev_mode=1,
726+
faulthandler=1,
711727
warnoptions=['default'])
712728
self.check_config("init_env_dev_mode", config, preconfig)
713729

714730
def test_init_env_dev_mode_alloc(self):
715-
preconfig = dict(self.INIT_ENV_PRECONFIG,
716-
allocator=PYMEM_ALLOCATOR_MALLOC)
717-
config = dict(self.INIT_ENV_CONFIG,
718-
dev_mode=1,
731+
preconfig = dict(allocator=PYMEM_ALLOCATOR_MALLOC)
732+
config = dict(dev_mode=1,
733+
faulthandler=1,
719734
warnoptions=['default'])
720735
self.check_config("init_env_dev_mode_alloc", config, preconfig)
721736

@@ -800,6 +815,7 @@ def test_init_dont_configure_locale(self):
800815
# _PyPreConfig.configure_locale=0
801816
preconfig = {
802817
'configure_locale': 0,
818+
'coerce_c_locale': 0,
803819
}
804820
self.check_config("init_dont_configure_locale", {}, preconfig,
805821
api=CONFIG_INIT_PYTHON)

Programs/_testembed.c

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ static int test_init_dont_parse_argv(void)
568568
}
569569

570570

571-
static void set_all_env_vars(void)
571+
static void set_most_env_vars(void)
572572
{
573573
putenv("PYTHONHASHSEED=42");
574574
putenv("PYTHONMALLOC=malloc");
@@ -585,13 +585,15 @@ static void set_all_env_vars(void)
585585
putenv("PYTHONNOUSERSITE=1");
586586
putenv("PYTHONFAULTHANDLER=1");
587587
putenv("PYTHONIOENCODING=iso8859-1:replace");
588-
/* FIXME: test PYTHONWARNINGS */
589-
/* FIXME: test PYTHONEXECUTABLE */
590-
/* FIXME: test PYTHONHOME */
591-
/* FIXME: test PYTHONDEBUG */
592-
/* FIXME: test PYTHONDUMPREFS */
593-
/* FIXME: test PYTHONCOERCECLOCALE */
594-
/* FIXME: test PYTHONPATH */
588+
}
589+
590+
591+
static void set_all_env_vars(void)
592+
{
593+
set_most_env_vars();
594+
595+
putenv("PYTHONWARNINGS=EnvVar");
596+
putenv("PYTHONPATH=/my/path");
595597
}
596598

597599

@@ -609,7 +611,6 @@ static int test_init_env(void)
609611

610612
static void set_all_env_vars_dev_mode(void)
611613
{
612-
set_all_env_vars();
613614
putenv("PYTHONMALLOC=");
614615
putenv("PYTHONFAULTHANDLER=");
615616
putenv("PYTHONDEVMODE=1");

0 commit comments

Comments
 (0)