https://github.com/python/cpython/commit/7087839a4ed55c760eae91fe44035105dfb22ead
commit: 7087839a4ed55c760eae91fe44035105dfb22ead
branch: 3.14
author: Gregory P. Smith <[email protected]>
committer: gpshead <[email protected]>
date: 2026-07-01T00:54:26-07:00
summary:

[3.14] gh-151626: Fix tests that fail when PYTHONPYCACHEPREFIX is set 
(GH-151952) (#152744)

Fix tests in test_compileall, test_import, test_importlib, test_py_compile, and 
test_inspect to neutralize PYTHONPYCACHEPREFIX
(cherry picked from commit 564c58c718bc3de9cf3d9bc20cdc06317411c1cd)

Co-authored-by: Jiucheng(Oliver) <[email protected]>

files:
A Misc/NEWS.d/next/Tests/2026-06-22-19-45-00.gh-issue-151626.K9pZ2x.rst
M Lib/test/test_compileall.py
M Lib/test/test_import/__init__.py
M Lib/test/test_importlib/test_util.py
M Lib/test/test_inspect/test_inspect.py
M Lib/test/test_py_compile.py

diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py
index 8384c183dd92ddd..e9caa82a700d2fe 100644
--- a/Lib/test/test_compileall.py
+++ b/Lib/test/test_compileall.py
@@ -550,6 +550,24 @@ def temporary_pycache_prefix(self):
         finally:
             sys.pycache_prefix = old_prefix
 
+    @contextlib.contextmanager
+    def no_pycache_prefix(self):
+        """Ignore any ambient pycache prefix for the duration of the test.
+
+        Some tests assume bytecode is written next to the source in a
+        __pycache__ directory.  When the test suite is run with
+        PYTHONPYCACHEPREFIX set, neutralize it both in this process (used by
+        cache_from_source) and in any spawned subprocesses.
+        """
+        old_prefix = sys.pycache_prefix
+        sys.pycache_prefix = None
+        try:
+            with os_helper.EnvironmentVarGuard() as env:
+                env.unset('PYTHONPYCACHEPREFIX')
+                yield
+        finally:
+            sys.pycache_prefix = old_prefix
+
     def _get_run_args(self, args):
         return [*support.optim_args_from_interpreter_flags(),
                 '-S', '-m', 'compileall',
@@ -646,15 +664,16 @@ def test_legacy_paths(self):
     def test_multiple_runs(self):
         # Bug 8527 reported that multiple calls produced empty
         # __pycache__/__pycache__ directories.
-        self.assertRunOK('-q', self.pkgdir)
-        # Verify the __pycache__ directory contents.
-        self.assertTrue(os.path.exists(self.pkgdir_cachedir))
-        cachecachedir = os.path.join(self.pkgdir_cachedir, '__pycache__')
-        self.assertFalse(os.path.exists(cachecachedir))
-        # Call compileall again.
-        self.assertRunOK('-q', self.pkgdir)
-        self.assertTrue(os.path.exists(self.pkgdir_cachedir))
-        self.assertFalse(os.path.exists(cachecachedir))
+        with self.no_pycache_prefix():
+            self.assertRunOK('-q', self.pkgdir)
+            # Verify the __pycache__ directory contents.
+            self.assertTrue(os.path.exists(self.pkgdir_cachedir))
+            cachecachedir = os.path.join(self.pkgdir_cachedir, '__pycache__')
+            self.assertFalse(os.path.exists(cachecachedir))
+            # Call compileall again.
+            self.assertRunOK('-q', self.pkgdir)
+            self.assertTrue(os.path.exists(self.pkgdir_cachedir))
+            self.assertFalse(os.path.exists(cachecachedir))
 
     @without_source_date_epoch  # timestamp invalidation test
     def test_force(self):
@@ -727,10 +746,13 @@ def test_symlink_loop(self):
         script_helper.make_pkg(pkg)
         os.symlink('.', os.path.join(pkg, 'evil'))
         os.symlink('.', os.path.join(pkg, 'evil2'))
-        self.assertRunOK('-q', self.pkgdir)
-        self.assertCompiled(os.path.join(
-            self.pkgdir, 'spam', 'evil', 'evil2', '__init__.py'
-        ))
+        # This relies on the __pycache__ layout (shared across the symlinked
+        # paths), so neutralize any ambient PYTHONPYCACHEPREFIX.
+        with self.no_pycache_prefix():
+            self.assertRunOK('-q', self.pkgdir)
+            self.assertCompiled(os.path.join(
+                self.pkgdir, 'spam', 'evil', 'evil2', '__init__.py'
+            ))
 
     def test_quiet(self):
         noisy = self.assertRunOK(self.pkgdir)
@@ -817,13 +839,16 @@ def test_include_on_stdin(self):
         f2 = script_helper.make_script(self.pkgdir, 'f2', '')
         f3 = script_helper.make_script(self.pkgdir, 'f3', '')
         f4 = script_helper.make_script(self.pkgdir, 'f4', '')
-        p = script_helper.spawn_python(*(self._get_run_args(()) + ['-i', '-']))
-        p.stdin.write((f3+os.linesep).encode('ascii'))
-        script_helper.kill_python(p)
-        self.assertNotCompiled(f1)
-        self.assertNotCompiled(f2)
-        self.assertCompiled(f3)
-        self.assertNotCompiled(f4)
+        # spawn_python() runs with -E, ignoring PYTHONPYCACHEPREFIX, so make
+        # cache_from_source() in this process agree by neutralizing it too.
+        with self.no_pycache_prefix():
+            p = script_helper.spawn_python(*(self._get_run_args(()) + ['-i', 
'-']))
+            p.stdin.write((f3+os.linesep).encode('ascii'))
+            script_helper.kill_python(p)
+            self.assertNotCompiled(f1)
+            self.assertNotCompiled(f2)
+            self.assertCompiled(f3)
+            self.assertNotCompiled(f4)
 
     def test_compiles_as_much_as_possible(self):
         bingfn = script_helper.make_script(self.pkgdir, 'bing', 'syntax(error')
diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
index a6030d7b563834b..b06096700d1332a 100644
--- a/Lib/test/test_import/__init__.py
+++ b/Lib/test/test_import/__init__.py
@@ -1620,6 +1620,11 @@ def _clean(self):
         unlink(self.source)
 
     def setUp(self):
+        # These tests assume bytecode is written next to the source in a
+        # local __pycache__ directory, so neutralize any pycache prefix (e.g.
+        # when the test suite is run with PYTHONPYCACHEPREFIX set).
+        self._orig_pycache_prefix = sys.pycache_prefix
+        sys.pycache_prefix = None
         self.source = TESTFN + '.py'
         self._clean()
         with open(self.source, 'w', encoding='utf-8') as fp:
@@ -1631,6 +1636,7 @@ def tearDown(self):
         assert sys.path[0] == os.curdir, 'Unexpected sys.path[0]'
         del sys.path[0]
         self._clean()
+        sys.pycache_prefix = self._orig_pycache_prefix
 
     @skip_if_dont_write_bytecode
     def test_import_pyc_path(self):
diff --git a/Lib/test/test_importlib/test_util.py 
b/Lib/test/test_importlib/test_util.py
index 8c14b96271ad3d5..f7df5c3251edb49 100644
--- a/Lib/test/test_importlib/test_util.py
+++ b/Lib/test/test_importlib/test_util.py
@@ -334,6 +334,17 @@ class PEP3147Tests:
 
     tag = sys.implementation.cache_tag
 
+    def setUp(self):
+        # Most of these tests assume the default (unset) pycache prefix, so
+        # clear it for the duration of the test (e.g. when the test suite is
+        # run with PYTHONPYCACHEPREFIX set).  Tests that need a specific prefix
+        # set their own via util.temporary_pycache_prefix().
+        self._orig_pycache_prefix = sys.pycache_prefix
+        sys.pycache_prefix = None
+
+    def tearDown(self):
+        sys.pycache_prefix = self._orig_pycache_prefix
+
     @unittest.skipIf(sys.implementation.cache_tag is None,
                      'requires sys.implementation.cache_tag not be None')
     def test_cache_from_source(self):
diff --git a/Lib/test/test_inspect/test_inspect.py 
b/Lib/test/test_inspect/test_inspect.py
index 28acb2a45a31b75..f3b04285b0d1d7c 100644
--- a/Lib/test/test_inspect/test_inspect.py
+++ b/Lib/test/test_inspect/test_inspect.py
@@ -7,6 +7,7 @@
 import functools
 import gc
 import importlib
+import importlib.util
 import inspect
 import io
 import linecache
@@ -6415,6 +6416,19 @@ def test_wrapped_descriptor(self):
 
 
 class TestMain(unittest.TestCase):
+    @staticmethod
+    def _expected_cached(module):
+        # assert_python_ok() runs the subprocess in isolated mode (-I), which
+        # ignores PYTHONPYCACHEPREFIX, so compute the expected cached path the
+        # same way (i.e. without any pycache prefix) to stay independent of the
+        # environment the test suite is run in.  Modules without a cached path
+        # (e.g. frozen modules such as ntpath/importlib.machinery on Windows)
+        # report None, so preserve that.
+        if module.__spec__.cached is None:
+            return None
+        with support.swap_attr(sys, 'pycache_prefix', None):
+            return importlib.util.cache_from_source(module.__spec__.origin)
+
     def test_only_source(self):
         module = importlib.import_module('unittest')
         rc, out, err = assert_python_ok('-m', 'inspect',
@@ -6454,13 +6468,13 @@ def test_details(self):
         rc, out, err = assert_python_ok(*args, '-m', 'inspect',
                                         'unittest', '--details')
         output = out.decode()
+        cached = self._expected_cached(module)
         # Just a quick sanity check on the output
         self.assertIn(module.__spec__.name, output)
         self.assertIn(module.__name__, output)
         self.assertIn(module.__spec__.origin, output)
         self.assertIn(module.__file__, output)
-        self.assertIn(module.__spec__.cached, output)
-        self.assertIn(module.__cached__, output)
+        self.assertIn(cached, output)
         self.assertEqual(err, b'')
 
 
diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py
index 749a877d013ce40..28141be31cb7340 100644
--- a/Lib/test/test_py_compile.py
+++ b/Lib/test/test_py_compile.py
@@ -158,21 +158,24 @@ def test_source_date_epoch(self):
     def test_double_dot_no_clobber(self):
         # http://bugs.python.org/issue22966
         # py_compile foo.bar.py -> __pycache__/foo.cpython-34.pyc
-        weird_path = os.path.join(self.directory, 'foo.bar.py')
-        cache_path = importlib.util.cache_from_source(weird_path)
-        pyc_path = weird_path + 'c'
-        head, tail = os.path.split(cache_path)
-        penultimate_tail = os.path.basename(head)
-        self.assertEqual(
-            os.path.join(penultimate_tail, tail),
-            os.path.join(
-                '__pycache__',
-                'foo.bar.{}.pyc'.format(sys.implementation.cache_tag)))
-        with open(weird_path, 'w') as file:
-            file.write('x = 123\n')
-        py_compile.compile(weird_path)
-        self.assertTrue(os.path.exists(cache_path))
-        self.assertFalse(os.path.exists(pyc_path))
+        # This test asserts the default __pycache__ layout, so neutralize any
+        # pycache prefix (e.g. when run with PYTHONPYCACHEPREFIX set).
+        with support.swap_attr(sys, 'pycache_prefix', None):
+            weird_path = os.path.join(self.directory, 'foo.bar.py')
+            cache_path = importlib.util.cache_from_source(weird_path)
+            pyc_path = weird_path + 'c'
+            head, tail = os.path.split(cache_path)
+            penultimate_tail = os.path.basename(head)
+            self.assertEqual(
+                os.path.join(penultimate_tail, tail),
+                os.path.join(
+                    '__pycache__',
+                    'foo.bar.{}.pyc'.format(sys.implementation.cache_tag)))
+            with open(weird_path, 'w') as file:
+                file.write('x = 123\n')
+            py_compile.compile(weird_path)
+            self.assertTrue(os.path.exists(cache_path))
+            self.assertFalse(os.path.exists(pyc_path))
 
     def test_optimization_path(self):
         # Specifying optimized bytecode should lead to a path reflecting that.
@@ -271,7 +274,13 @@ def test_with_files(self):
         self.assertEqual(rc, 0)
         self.assertEqual(stdout, b'')
         self.assertEqual(stderr, b'')
-        self.assertTrue(os.path.exists(self.cache_path))
+        # pycompilecmd() runs the interpreter in isolated mode (-I), which
+        # ignores PYTHONPYCACHEPREFIX, so the bytecode is written next to the
+        # source.  Compute the expected cache path the same way.
+        with support.swap_attr(sys, 'pycache_prefix', None):
+            cache_path = importlib.util.cache_from_source(
+                self.source_path, optimization='' if __debug__ else 1)
+        self.assertTrue(os.path.exists(cache_path))
 
     def test_bad_syntax(self):
         bad_syntax = os.path.join(os.path.dirname(__file__),
diff --git 
a/Misc/NEWS.d/next/Tests/2026-06-22-19-45-00.gh-issue-151626.K9pZ2x.rst 
b/Misc/NEWS.d/next/Tests/2026-06-22-19-45-00.gh-issue-151626.K9pZ2x.rst
new file mode 100644
index 000000000000000..9858415018ae1ba
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2026-06-22-19-45-00.gh-issue-151626.K9pZ2x.rst
@@ -0,0 +1,5 @@
+Fix several tests in ``test.test_inspect``, ``test.test_import``,
+``test.test_importlib``, ``test.test_py_compile`` and
+``test.test_compileall`` that failed when the test suite was run with
+:envvar:`PYTHONPYCACHEPREFIX` set.  These tests now neutralize the pycache
+prefix where they assume the default ``__pycache__`` bytecode layout.

_______________________________________________
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