Skip to content

Commit ce33758

Browse files
zangjiuchenggpshead
authored andcommitted
[3.14] gh-151626: Fix tests that fail when PYTHONPYCACHEPREFIX is set (GH-151952)
Fix tests in test_compileall, test_import, test_importlib, test_py_compile, and test_inspect to neutralize PYTHONPYCACHEPREFIX (cherry picked from commit 564c58c) Co-authored-by: Jiucheng(Oliver) <git.jiucheng@gmail.com>
1 parent b6ae32c commit ce33758

6 files changed

Lines changed: 108 additions & 38 deletions

File tree

Lib/test/test_compileall.py

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,24 @@ def temporary_pycache_prefix(self):
550550
finally:
551551
sys.pycache_prefix = old_prefix
552552

553+
@contextlib.contextmanager
554+
def no_pycache_prefix(self):
555+
"""Ignore any ambient pycache prefix for the duration of the test.
556+
557+
Some tests assume bytecode is written next to the source in a
558+
__pycache__ directory. When the test suite is run with
559+
PYTHONPYCACHEPREFIX set, neutralize it both in this process (used by
560+
cache_from_source) and in any spawned subprocesses.
561+
"""
562+
old_prefix = sys.pycache_prefix
563+
sys.pycache_prefix = None
564+
try:
565+
with os_helper.EnvironmentVarGuard() as env:
566+
env.unset('PYTHONPYCACHEPREFIX')
567+
yield
568+
finally:
569+
sys.pycache_prefix = old_prefix
570+
553571
def _get_run_args(self, args):
554572
return [*support.optim_args_from_interpreter_flags(),
555573
'-S', '-m', 'compileall',
@@ -646,15 +664,16 @@ def test_legacy_paths(self):
646664
def test_multiple_runs(self):
647665
# Bug 8527 reported that multiple calls produced empty
648666
# __pycache__/__pycache__ directories.
649-
self.assertRunOK('-q', self.pkgdir)
650-
# Verify the __pycache__ directory contents.
651-
self.assertTrue(os.path.exists(self.pkgdir_cachedir))
652-
cachecachedir = os.path.join(self.pkgdir_cachedir, '__pycache__')
653-
self.assertFalse(os.path.exists(cachecachedir))
654-
# Call compileall again.
655-
self.assertRunOK('-q', self.pkgdir)
656-
self.assertTrue(os.path.exists(self.pkgdir_cachedir))
657-
self.assertFalse(os.path.exists(cachecachedir))
667+
with self.no_pycache_prefix():
668+
self.assertRunOK('-q', self.pkgdir)
669+
# Verify the __pycache__ directory contents.
670+
self.assertTrue(os.path.exists(self.pkgdir_cachedir))
671+
cachecachedir = os.path.join(self.pkgdir_cachedir, '__pycache__')
672+
self.assertFalse(os.path.exists(cachecachedir))
673+
# Call compileall again.
674+
self.assertRunOK('-q', self.pkgdir)
675+
self.assertTrue(os.path.exists(self.pkgdir_cachedir))
676+
self.assertFalse(os.path.exists(cachecachedir))
658677

659678
@without_source_date_epoch # timestamp invalidation test
660679
def test_force(self):
@@ -727,10 +746,13 @@ def test_symlink_loop(self):
727746
script_helper.make_pkg(pkg)
728747
os.symlink('.', os.path.join(pkg, 'evil'))
729748
os.symlink('.', os.path.join(pkg, 'evil2'))
730-
self.assertRunOK('-q', self.pkgdir)
731-
self.assertCompiled(os.path.join(
732-
self.pkgdir, 'spam', 'evil', 'evil2', '__init__.py'
733-
))
749+
# This relies on the __pycache__ layout (shared across the symlinked
750+
# paths), so neutralize any ambient PYTHONPYCACHEPREFIX.
751+
with self.no_pycache_prefix():
752+
self.assertRunOK('-q', self.pkgdir)
753+
self.assertCompiled(os.path.join(
754+
self.pkgdir, 'spam', 'evil', 'evil2', '__init__.py'
755+
))
734756

735757
def test_quiet(self):
736758
noisy = self.assertRunOK(self.pkgdir)
@@ -817,13 +839,16 @@ def test_include_on_stdin(self):
817839
f2 = script_helper.make_script(self.pkgdir, 'f2', '')
818840
f3 = script_helper.make_script(self.pkgdir, 'f3', '')
819841
f4 = script_helper.make_script(self.pkgdir, 'f4', '')
820-
p = script_helper.spawn_python(*(self._get_run_args(()) + ['-i', '-']))
821-
p.stdin.write((f3+os.linesep).encode('ascii'))
822-
script_helper.kill_python(p)
823-
self.assertNotCompiled(f1)
824-
self.assertNotCompiled(f2)
825-
self.assertCompiled(f3)
826-
self.assertNotCompiled(f4)
842+
# spawn_python() runs with -E, ignoring PYTHONPYCACHEPREFIX, so make
843+
# cache_from_source() in this process agree by neutralizing it too.
844+
with self.no_pycache_prefix():
845+
p = script_helper.spawn_python(*(self._get_run_args(()) + ['-i', '-']))
846+
p.stdin.write((f3+os.linesep).encode('ascii'))
847+
script_helper.kill_python(p)
848+
self.assertNotCompiled(f1)
849+
self.assertNotCompiled(f2)
850+
self.assertCompiled(f3)
851+
self.assertNotCompiled(f4)
827852

828853
def test_compiles_as_much_as_possible(self):
829854
bingfn = script_helper.make_script(self.pkgdir, 'bing', 'syntax(error')

Lib/test/test_import/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,6 +1620,11 @@ def _clean(self):
16201620
unlink(self.source)
16211621

16221622
def setUp(self):
1623+
# These tests assume bytecode is written next to the source in a
1624+
# local __pycache__ directory, so neutralize any pycache prefix (e.g.
1625+
# when the test suite is run with PYTHONPYCACHEPREFIX set).
1626+
self._orig_pycache_prefix = sys.pycache_prefix
1627+
sys.pycache_prefix = None
16231628
self.source = TESTFN + '.py'
16241629
self._clean()
16251630
with open(self.source, 'w', encoding='utf-8') as fp:
@@ -1631,6 +1636,7 @@ def tearDown(self):
16311636
assert sys.path[0] == os.curdir, 'Unexpected sys.path[0]'
16321637
del sys.path[0]
16331638
self._clean()
1639+
sys.pycache_prefix = self._orig_pycache_prefix
16341640

16351641
@skip_if_dont_write_bytecode
16361642
def test_import_pyc_path(self):

Lib/test/test_importlib/test_util.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,17 @@ class PEP3147Tests:
334334

335335
tag = sys.implementation.cache_tag
336336

337+
def setUp(self):
338+
# Most of these tests assume the default (unset) pycache prefix, so
339+
# clear it for the duration of the test (e.g. when the test suite is
340+
# run with PYTHONPYCACHEPREFIX set). Tests that need a specific prefix
341+
# set their own via util.temporary_pycache_prefix().
342+
self._orig_pycache_prefix = sys.pycache_prefix
343+
sys.pycache_prefix = None
344+
345+
def tearDown(self):
346+
sys.pycache_prefix = self._orig_pycache_prefix
347+
337348
@unittest.skipIf(sys.implementation.cache_tag is None,
338349
'requires sys.implementation.cache_tag not be None')
339350
def test_cache_from_source(self):

Lib/test/test_inspect/test_inspect.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import functools
88
import gc
99
import importlib
10+
import importlib.util
1011
import inspect
1112
import io
1213
import linecache
@@ -6415,6 +6416,19 @@ def test_wrapped_descriptor(self):
64156416

64166417

64176418
class TestMain(unittest.TestCase):
6419+
@staticmethod
6420+
def _expected_cached(module):
6421+
# assert_python_ok() runs the subprocess in isolated mode (-I), which
6422+
# ignores PYTHONPYCACHEPREFIX, so compute the expected cached path the
6423+
# same way (i.e. without any pycache prefix) to stay independent of the
6424+
# environment the test suite is run in. Modules without a cached path
6425+
# (e.g. frozen modules such as ntpath/importlib.machinery on Windows)
6426+
# report None, so preserve that.
6427+
if module.__spec__.cached is None:
6428+
return None
6429+
with support.swap_attr(sys, 'pycache_prefix', None):
6430+
return importlib.util.cache_from_source(module.__spec__.origin)
6431+
64186432
def test_only_source(self):
64196433
module = importlib.import_module('unittest')
64206434
rc, out, err = assert_python_ok('-m', 'inspect',
@@ -6454,13 +6468,13 @@ def test_details(self):
64546468
rc, out, err = assert_python_ok(*args, '-m', 'inspect',
64556469
'unittest', '--details')
64566470
output = out.decode()
6471+
cached = self._expected_cached(module)
64576472
# Just a quick sanity check on the output
64586473
self.assertIn(module.__spec__.name, output)
64596474
self.assertIn(module.__name__, output)
64606475
self.assertIn(module.__spec__.origin, output)
64616476
self.assertIn(module.__file__, output)
6462-
self.assertIn(module.__spec__.cached, output)
6463-
self.assertIn(module.__cached__, output)
6477+
self.assertIn(cached, output)
64646478
self.assertEqual(err, b'')
64656479

64666480

Lib/test/test_py_compile.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -158,21 +158,24 @@ def test_source_date_epoch(self):
158158
def test_double_dot_no_clobber(self):
159159
# http://bugs.python.org/issue22966
160160
# py_compile foo.bar.py -> __pycache__/foo.cpython-34.pyc
161-
weird_path = os.path.join(self.directory, 'foo.bar.py')
162-
cache_path = importlib.util.cache_from_source(weird_path)
163-
pyc_path = weird_path + 'c'
164-
head, tail = os.path.split(cache_path)
165-
penultimate_tail = os.path.basename(head)
166-
self.assertEqual(
167-
os.path.join(penultimate_tail, tail),
168-
os.path.join(
169-
'__pycache__',
170-
'foo.bar.{}.pyc'.format(sys.implementation.cache_tag)))
171-
with open(weird_path, 'w') as file:
172-
file.write('x = 123\n')
173-
py_compile.compile(weird_path)
174-
self.assertTrue(os.path.exists(cache_path))
175-
self.assertFalse(os.path.exists(pyc_path))
161+
# This test asserts the default __pycache__ layout, so neutralize any
162+
# pycache prefix (e.g. when run with PYTHONPYCACHEPREFIX set).
163+
with support.swap_attr(sys, 'pycache_prefix', None):
164+
weird_path = os.path.join(self.directory, 'foo.bar.py')
165+
cache_path = importlib.util.cache_from_source(weird_path)
166+
pyc_path = weird_path + 'c'
167+
head, tail = os.path.split(cache_path)
168+
penultimate_tail = os.path.basename(head)
169+
self.assertEqual(
170+
os.path.join(penultimate_tail, tail),
171+
os.path.join(
172+
'__pycache__',
173+
'foo.bar.{}.pyc'.format(sys.implementation.cache_tag)))
174+
with open(weird_path, 'w') as file:
175+
file.write('x = 123\n')
176+
py_compile.compile(weird_path)
177+
self.assertTrue(os.path.exists(cache_path))
178+
self.assertFalse(os.path.exists(pyc_path))
176179

177180
def test_optimization_path(self):
178181
# Specifying optimized bytecode should lead to a path reflecting that.
@@ -271,7 +274,13 @@ def test_with_files(self):
271274
self.assertEqual(rc, 0)
272275
self.assertEqual(stdout, b'')
273276
self.assertEqual(stderr, b'')
274-
self.assertTrue(os.path.exists(self.cache_path))
277+
# pycompilecmd() runs the interpreter in isolated mode (-I), which
278+
# ignores PYTHONPYCACHEPREFIX, so the bytecode is written next to the
279+
# source. Compute the expected cache path the same way.
280+
with support.swap_attr(sys, 'pycache_prefix', None):
281+
cache_path = importlib.util.cache_from_source(
282+
self.source_path, optimization='' if __debug__ else 1)
283+
self.assertTrue(os.path.exists(cache_path))
275284

276285
def test_bad_syntax(self):
277286
bad_syntax = os.path.join(os.path.dirname(__file__),
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix several tests in ``test.test_inspect``, ``test.test_import``,
2+
``test.test_importlib``, ``test.test_py_compile`` and
3+
``test.test_compileall`` that failed when the test suite was run with
4+
:envvar:`PYTHONPYCACHEPREFIX` set. These tests now neutralize the pycache
5+
prefix where they assume the default ``__pycache__`` bytecode layout.

0 commit comments

Comments
 (0)