https://github.com/python/cpython/commit/9bd44aaa4eae33679c8e2bb6adbc9f2c7b19759f
commit: 9bd44aaa4eae33679c8e2bb6adbc9f2c7b19759f
branch: main
author: Cody Maloney <[email protected]>
committer: cmaloney <[email protected]>
date: 2026-11-03T12:50:49-08:00
summary:

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.

files:
M Lib/test/test_embed.py

diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py
index 6c51b79bc44a0f..b5a441b7e94fb4 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -48,6 +48,9 @@
 INIT_LOOPS = 4
 MAX_HASH_SEED = 4294967295
 
+# Environment variables that change runtime but should not break embedding.
+RUNTIME_ENVVARS = ('PYTHON_GIL', 'PYTHON_JIT_STRESS', 'PYTHON_UOPS_OPTIMIZE')
+
 ABI_THREAD = 't' if support.Py_GIL_DISABLED else ''
 # PLATSTDLIB_LANDMARK copied from Modules/getpath.py
 if os.name == 'nt':
@@ -133,21 +136,24 @@ def tearDown(self):
 
     def run_embedded_interpreter(self, *args, env=None,
                                  timeout=None, returncode=0, input=None,
-                                 cwd=None):
+                                 cwd=None, runtime_envvars=True):
         """Runs a test in the embedded interpreter"""
         cmd = [self.test_exe]
         cmd.extend(args)
-        if env is not None and MS_WINDOWS:
-            # Windows requires at least the SYSTEMROOT environment variable to
-            # start Python.
-            env = env.copy()
-            env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
+        safe_env = remove_python_envvars()
+        # Copy across runtime-modifying variables.
+        if runtime_envvars:
+            for name in RUNTIME_ENVVARS:
+                if name in os.environ:
+                    safe_env[name] = os.environ[name]
+        if env:
+            safe_env.update(env)
 
         kwargs = dict(
             stdout=subprocess.PIPE,
             stderr=subprocess.PIPE,
             universal_newlines=True,
-            env=env,
+            env=safe_env,
             cwd=cwd,
         )
         if input is not None:
@@ -323,7 +329,7 @@ def test_inittab_submodule_singlephase(self):
 
     def test_forced_io_encoding(self):
         # Checks forced configuration of embedded interpreter IO streams
-        env = dict(os.environ, PYTHONIOENCODING="utf-8:surrogateescape")
+        env = {'PYTHONIOENCODING': 'utf-8:surrogateescape'}
         out, err = self.run_embedded_interpreter("test_forced_io_encoding", 
env=env)
         if support.verbose > 1:
             print()
@@ -369,7 +375,7 @@ def test_pre_initialization_api(self):
         Checks some key parts of the C-API that need to work before the runtime
         is initialized (via Py_Initialize()).
         """
-        env = dict(os.environ, PYTHONPATH=os.pathsep.join(sys.path))
+        env = {'PYTHONPATH': os.pathsep.join(sys.path)}
         out, err = 
self.run_embedded_interpreter("test_pre_initialization_api", env=env)
         if support.verbose > 1:
             print()
@@ -390,8 +396,7 @@ def test_pre_initialization_sys_options(self):
         Checks that sys.warnoptions and sys._xoptions can be set before the
         runtime is initialized (otherwise they won't be effective).
         """
-        env = remove_python_envvars()
-        env['PYTHONPATH'] = os.pathsep.join(sys.path)
+        env = {'PYTHONPATH': os.pathsep.join(sys.path)}
         out, err = self.run_embedded_interpreter(
                         "test_pre_initialization_sys_options", env=env)
         if support.verbose > 1:
@@ -686,10 +691,8 @@ def test_init_run_main_startup_exitcode(self):
             with open(filename, 'x') as fp:
                 fp.write(CODE_EXITCODE_123)
 
-            env = dict(os.environ)
-            env['PYTHONSTARTUP'] = filename
             
self.check_program_exitcode("test_init_run_main_interactive_exitcode",
-                                        env=env,
+                                        env={'PYTHONSTARTUP': filename},
                                         check_stderr=False)
 
     def test_init_run_main_module_exitcode(self):
@@ -699,10 +702,8 @@ def test_init_run_main_module_exitcode(self):
             with open(filename, 'x', encoding='utf8') as fp:
                 fp.write(CODE_EXITCODE_123)
 
-            env = dict(os.environ)
-            env['PYTHONPATH'] = tmpdir
             self.check_program_exitcode("test_init_run_main_module_exitcode",
-                                        modname, env=env)
+                                        modname, env={'PYTHONPATH': tmpdir})
 
 
 def config_dev_mode(preconfig, config):
@@ -1061,11 +1062,6 @@ def check_all_configs(self, testname, 
expected_config=None,
                           modify_path_cb=None,
                           stderr=None, *, api, preconfig_api=None,
                           env=None, ignore_stderr=False, cwd=None):
-        new_env = remove_python_envvars()
-        if env is not None:
-            new_env.update(env)
-        env = new_env
-
         if preconfig_api is None:
             preconfig_api = api
         if preconfig_api == API_ISOLATED:
@@ -1094,8 +1090,9 @@ def check_all_configs(self, testname, 
expected_config=None,
                                  env,
                                  api, modify_path_cb)
 
-        out, err = self.run_embedded_interpreter(testname,
-                                                 env=env, cwd=cwd)
+        # Ignore runtime flags like PYTHON_GIL to get the build configuration.
+        out, err = self.run_embedded_interpreter(testname, env=env, cwd=cwd,
+                                                 runtime_envvars=False)
         if stderr is None and not expected_config['verbose']:
             stderr = ""
         if stderr is not None and not ignore_stderr:
@@ -1871,8 +1868,7 @@ def test_getpath_abspath_win32(self):
         ]
         out, err = self.run_embedded_interpreter(
             "test_init_initialize_config",
-            env={**remove_python_envvars(),
-                 "PYTHONPATH": os.path.pathsep.join(c[0] for c in CASES)}
+            env={"PYTHONPATH": os.path.pathsep.join(c[0] for c in CASES)}
         )
         self.assertEqual(err, "")
         try:
@@ -2085,7 +2081,7 @@ def test_audit_run_interactivehook(self):
             print("import sys", file=f)
             print("sys.__interactivehook__ = lambda: None", file=f)
         try:
-            env = {**remove_python_envvars(), "PYTHONSTARTUP": startup}
+            env = {"PYTHONSTARTUP": startup}
             self.run_embedded_interpreter("test_audit_run_interactivehook",
                                           timeout=support.SHORT_TIMEOUT,
                                           returncode=10, env=env)
@@ -2097,7 +2093,7 @@ def test_audit_run_startup(self):
         with open(startup, "w", encoding="utf-8") as f:
             print("pass", file=f)
         try:
-            env = {**remove_python_envvars(), "PYTHONSTARTUP": startup}
+            env = {"PYTHONSTARTUP": startup}
             self.run_embedded_interpreter("test_audit_run_startup",
                                           timeout=support.SHORT_TIMEOUT,
                                           returncode=10, env=env)
@@ -2143,8 +2139,7 @@ def test_unicode_id_init(self):
                      'Py_FrozenMain is not exported on Windows')
     @unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
     def test_frozenmain(self):
-        env = dict(os.environ)
-        env['PYTHONUNBUFFERED'] = '1'
+        env = {'PYTHONUNBUFFERED': '1'}
         out, err = self.run_embedded_interpreter("test_frozenmain", env=env)
         executable = os.path.realpath('./argv0')
         expected = textwrap.dedent(f"""

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to