Skip to content

Commit 9bd44aa

Browse files
authored
gh-157006: Fix test_embed when PYTHONSTARTUP is set (#157007)
gh-157006: Fix test_embed when PYTHONSTARTUP set Remove python-specific environment variables in test_embed cases. Helpers now start with a `PYTHON*` environment variable free copy of `os.environ` then add back in known runtime changing flags. This makes it so specific host configuration, such as PYTHON_GIL is kept while isolating tests from unintended changes. Individual tests which need to check specific behaviors can pass `env=` to validate embedded interpreter behavior.
1 parent 4c497b1 commit 9bd44aa

1 file changed

Lines changed: 25 additions & 30 deletions

File tree

‎Lib/test/test_embed.py‎

Lines changed: 25 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@
4848
INIT_LOOPS = 4
4949
MAX_HASH_SEED = 4294967295
5050

51+
# Environment variables that change runtime but should not break embedding.
52+
RUNTIME_ENVVARS = ('PYTHON_GIL', 'PYTHON_JIT_STRESS', 'PYTHON_UOPS_OPTIMIZE')
53+
5154
ABI_THREAD = 't' if support.Py_GIL_DISABLED else ''
5255
# PLATSTDLIB_LANDMARK copied from Modules/getpath.py
5356
if os.name == 'nt':
@@ -133,21 +136,24 @@ def tearDown(self):
133136

134137
def run_embedded_interpreter(self, *args, env=None,
135138
timeout=None, returncode=0, input=None,
136-
cwd=None):
139+
cwd=None, runtime_envvars=True):
137140
"""Runs a test in the embedded interpreter"""
138141
cmd = [self.test_exe]
139142
cmd.extend(args)
140-
if env is not None and MS_WINDOWS:
141-
# Windows requires at least the SYSTEMROOT environment variable to
142-
# start Python.
143-
env = env.copy()
144-
env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
143+
safe_env = remove_python_envvars()
144+
# Copy across runtime-modifying variables.
145+
if runtime_envvars:
146+
for name in RUNTIME_ENVVARS:
147+
if name in os.environ:
148+
safe_env[name] = os.environ[name]
149+
if env:
150+
safe_env.update(env)
145151

146152
kwargs = dict(
147153
stdout=subprocess.PIPE,
148154
stderr=subprocess.PIPE,
149155
universal_newlines=True,
150-
env=env,
156+
env=safe_env,
151157
cwd=cwd,
152158
)
153159
if input is not None:
@@ -323,7 +329,7 @@ def test_inittab_submodule_singlephase(self):
323329

324330
def test_forced_io_encoding(self):
325331
# Checks forced configuration of embedded interpreter IO streams
326-
env = dict(os.environ, PYTHONIOENCODING="utf-8:surrogateescape")
332+
env = {'PYTHONIOENCODING': 'utf-8:surrogateescape'}
327333
out, err = self.run_embedded_interpreter("test_forced_io_encoding", env=env)
328334
if support.verbose > 1:
329335
print()
@@ -369,7 +375,7 @@ def test_pre_initialization_api(self):
369375
Checks some key parts of the C-API that need to work before the runtime
370376
is initialized (via Py_Initialize()).
371377
"""
372-
env = dict(os.environ, PYTHONPATH=os.pathsep.join(sys.path))
378+
env = {'PYTHONPATH': os.pathsep.join(sys.path)}
373379
out, err = self.run_embedded_interpreter("test_pre_initialization_api", env=env)
374380
if support.verbose > 1:
375381
print()
@@ -390,8 +396,7 @@ def test_pre_initialization_sys_options(self):
390396
Checks that sys.warnoptions and sys._xoptions can be set before the
391397
runtime is initialized (otherwise they won't be effective).
392398
"""
393-
env = remove_python_envvars()
394-
env['PYTHONPATH'] = os.pathsep.join(sys.path)
399+
env = {'PYTHONPATH': os.pathsep.join(sys.path)}
395400
out, err = self.run_embedded_interpreter(
396401
"test_pre_initialization_sys_options", env=env)
397402
if support.verbose > 1:
@@ -686,10 +691,8 @@ def test_init_run_main_startup_exitcode(self):
686691
with open(filename, 'x') as fp:
687692
fp.write(CODE_EXITCODE_123)
688693

689-
env = dict(os.environ)
690-
env['PYTHONSTARTUP'] = filename
691694
self.check_program_exitcode("test_init_run_main_interactive_exitcode",
692-
env=env,
695+
env={'PYTHONSTARTUP': filename},
693696
check_stderr=False)
694697

695698
def test_init_run_main_module_exitcode(self):
@@ -699,10 +702,8 @@ def test_init_run_main_module_exitcode(self):
699702
with open(filename, 'x', encoding='utf8') as fp:
700703
fp.write(CODE_EXITCODE_123)
701704

702-
env = dict(os.environ)
703-
env['PYTHONPATH'] = tmpdir
704705
self.check_program_exitcode("test_init_run_main_module_exitcode",
705-
modname, env=env)
706+
modname, env={'PYTHONPATH': tmpdir})
706707

707708

708709
def config_dev_mode(preconfig, config):
@@ -1061,11 +1062,6 @@ def check_all_configs(self, testname, expected_config=None,
10611062
modify_path_cb=None,
10621063
stderr=None, *, api, preconfig_api=None,
10631064
env=None, ignore_stderr=False, cwd=None):
1064-
new_env = remove_python_envvars()
1065-
if env is not None:
1066-
new_env.update(env)
1067-
env = new_env
1068-
10691065
if preconfig_api is None:
10701066
preconfig_api = api
10711067
if preconfig_api == API_ISOLATED:
@@ -1094,8 +1090,9 @@ def check_all_configs(self, testname, expected_config=None,
10941090
env,
10951091
api, modify_path_cb)
10961092

1097-
out, err = self.run_embedded_interpreter(testname,
1098-
env=env, cwd=cwd)
1093+
# Ignore runtime flags like PYTHON_GIL to get the build configuration.
1094+
out, err = self.run_embedded_interpreter(testname, env=env, cwd=cwd,
1095+
runtime_envvars=False)
10991096
if stderr is None and not expected_config['verbose']:
11001097
stderr = ""
11011098
if stderr is not None and not ignore_stderr:
@@ -1871,8 +1868,7 @@ def test_getpath_abspath_win32(self):
18711868
]
18721869
out, err = self.run_embedded_interpreter(
18731870
"test_init_initialize_config",
1874-
env={**remove_python_envvars(),
1875-
"PYTHONPATH": os.path.pathsep.join(c[0] for c in CASES)}
1871+
env={"PYTHONPATH": os.path.pathsep.join(c[0] for c in CASES)}
18761872
)
18771873
self.assertEqual(err, "")
18781874
try:
@@ -2085,7 +2081,7 @@ def test_audit_run_interactivehook(self):
20852081
print("import sys", file=f)
20862082
print("sys.__interactivehook__ = lambda: None", file=f)
20872083
try:
2088-
env = {**remove_python_envvars(), "PYTHONSTARTUP": startup}
2084+
env = {"PYTHONSTARTUP": startup}
20892085
self.run_embedded_interpreter("test_audit_run_interactivehook",
20902086
timeout=support.SHORT_TIMEOUT,
20912087
returncode=10, env=env)
@@ -2097,7 +2093,7 @@ def test_audit_run_startup(self):
20972093
with open(startup, "w", encoding="utf-8") as f:
20982094
print("pass", file=f)
20992095
try:
2100-
env = {**remove_python_envvars(), "PYTHONSTARTUP": startup}
2096+
env = {"PYTHONSTARTUP": startup}
21012097
self.run_embedded_interpreter("test_audit_run_startup",
21022098
timeout=support.SHORT_TIMEOUT,
21032099
returncode=10, env=env)
@@ -2143,8 +2139,7 @@ def test_unicode_id_init(self):
21432139
'Py_FrozenMain is not exported on Windows')
21442140
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
21452141
def test_frozenmain(self):
2146-
env = dict(os.environ)
2147-
env['PYTHONUNBUFFERED'] = '1'
2142+
env = {'PYTHONUNBUFFERED': '1'}
21482143
out, err = self.run_embedded_interpreter("test_frozenmain", env=env)
21492144
executable = os.path.realpath('./argv0')
21502145
expected = textwrap.dedent(f"""

0 commit comments

Comments
 (0)