From e02250488ca8ea33417bdfe65409beb9c203e324 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Dias=20Apolin=C3=A1rio?= <100954782+Gronoxx@users.noreply.github.com> Date: Thu, 27 Aug 2026 16:49:29 -0300 Subject: [PATCH 1/2] gh-156495: Don't warn about a non-normalized sys.prefix in a venv --- Lib/site.py | 7 ++- Lib/test/test_venv.py | 49 +++++++++++++++++++ ...-08-27-14-00-00.gh-issue-156495.Qk3vTz.rst | 4 ++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-27-14-00-00.gh-issue-156495.Qk3vTz.rst diff --git a/Lib/site.py b/Lib/site.py index 294a4d6f641c39..36f81d69c8faa7 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -1031,11 +1031,14 @@ def _venv(state): ) break - if sys.prefix != site_prefix: + # site_prefix is already normalised by the abspath() above, but sys.prefix + # isn't. Normalise before comparing, or two spellings of the same directory + # look like a mismatch. + if os.path.normpath(sys.prefix) != site_prefix: _warn( f'Unexpected value in sys.prefix, expected {site_prefix}, got {sys.prefix}', RuntimeWarning) - if sys.exec_prefix != site_prefix: + if os.path.normpath(sys.exec_prefix) != site_prefix: _warn( f'Unexpected value in sys.exec_prefix, expected {site_prefix}, got {sys.exec_prefix}', RuntimeWarning) diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index b4ad1bf3f41294..0144307b773d2c 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -302,6 +302,55 @@ def test_prefixes(self): self.assertEqual(pathlib.Path(out.strip().decode()), pathlib.Path(expected), prefix) + @requireVenvCreate + def test_prefixes_with_non_normalised_executable(self): + """ + Test that a non-normalised executable path isn't counted as a mismatch. + """ + # gh-156495: sys.prefix isn't normalised, but the prefix derived from + # sys.executable is, so a string comparison flagged two spellings of the + # same directory. + rmtree(self.env_dir) + self.run_with_capture(venv.create, self.env_dir) + # Run from a directory next to the env so argv[0] starts with '..', + # which normpath() can't collapse. + subdir = tempfile.mkdtemp(dir=os.path.dirname(self.env_dir)) + self.addCleanup(rmtree, subdir) + relative_exe = os.path.join( + os.pardir, os.path.basename(self.env_dir), self.bindir, self.exe) + p = subprocess.run( + [relative_exe, '-c', + 'import sys; print(sys.prefix); print(sys.exec_prefix)'], + cwd=subdir, capture_output=True, encoding='utf-8', + env={**os.environ, 'PYTHONHOME': ''}) + self.assertEqual(p.returncode, 0, p.stderr) + self.assertNotIn('Unexpected value in sys.prefix', p.stderr) + self.assertNotIn('Unexpected value in sys.exec_prefix', p.stderr) + prefix, exec_prefix = p.stdout.splitlines() + for name, value in (('prefix', prefix), ('exec_prefix', exec_prefix)): + self.assertEqual(os.path.realpath(value), + os.path.realpath(self.env_dir), name) + + @requireVenvCreate + def test_prefixes_mismatch_is_still_reported(self): + """ + Test that a genuine prefix mismatch is still reported. + """ + rmtree(self.env_dir) + self.run_with_capture(venv.create, self.env_dir) + # Move pyvenv.cfg next to the interpreter instead of leaving it in the + # prefix, so that sys.prefix and the prefix derived from sys.executable + # differ for real and not only in spelling. + os.rename(os.path.join(self.env_dir, 'pyvenv.cfg'), + os.path.join(self.env_dir, self.bindir, 'pyvenv.cfg')) + p = subprocess.run( + [self.envpy(), '-c', 'import sys; print(sys.prefix)'], + capture_output=True, encoding='utf-8', + env={**os.environ, 'PYTHONHOME': ''}) + self.assertEqual(p.returncode, 0, p.stderr) + self.assertIn('Unexpected value in sys.prefix', p.stderr) + self.assertIn('Unexpected value in sys.exec_prefix', p.stderr) + @requireVenvCreate def test_sysconfig(self): """ diff --git a/Misc/NEWS.d/next/Library/2026-08-27-14-00-00.gh-issue-156495.Qk3vTz.rst b/Misc/NEWS.d/next/Library/2026-08-27-14-00-00.gh-issue-156495.Qk3vTz.rst new file mode 100644 index 00000000000000..8ff3d3f494537c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-27-14-00-00.gh-issue-156495.Qk3vTz.rst @@ -0,0 +1,4 @@ +Fix a false-positive :exc:`RuntimeWarning` from :mod:`site` when a virtual +environment interpreter is started through a non-normalised path such as +``../.venv/bin/python``. ``sys.prefix`` and the prefix derived from +:data:`sys.executable` are now compared in normalised form. From 4489fceb8e9c24ae392fdf041e05a8b2f8d2f7b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Dias=20Apolin=C3=A1rio?= <100954782+Gronoxx@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:24:02 -0300 Subject: [PATCH 2/2] Fix the new test on Windows subprocess only resolves a relative executable against its cwd argument on POSIX, so the test failed with FileNotFoundError on Windows. Change directory in the test process instead. --- Lib/test/test_venv.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 0144307b773d2c..7d5220708fe226 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -23,8 +23,8 @@ is_wasm32, requires_venv_with_pip, TEST_HOME_DIR, requires_resource, copy_python_src_ignore) -from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree, - TESTFN, FakePath) +from test.support.os_helper import (can_symlink, change_cwd, EnvironmentVarGuard, + rmtree, TESTFN, FakePath) import unittest import venv from unittest.mock import patch, Mock @@ -318,11 +318,14 @@ def test_prefixes_with_non_normalised_executable(self): self.addCleanup(rmtree, subdir) relative_exe = os.path.join( os.pardir, os.path.basename(self.env_dir), self.bindir, self.exe) - p = subprocess.run( - [relative_exe, '-c', - 'import sys; print(sys.prefix); print(sys.exec_prefix)'], - cwd=subdir, capture_output=True, encoding='utf-8', - env={**os.environ, 'PYTHONHOME': ''}) + # Windows does not resolve a relative executable against subprocess's + # cwd argument, so change directory in this process instead. + with change_cwd(subdir): + p = subprocess.run( + [relative_exe, '-c', + 'import sys; print(sys.prefix); print(sys.exec_prefix)'], + capture_output=True, encoding='utf-8', + env={**os.environ, 'PYTHONHOME': ''}) self.assertEqual(p.returncode, 0, p.stderr) self.assertNotIn('Unexpected value in sys.prefix', p.stderr) self.assertNotIn('Unexpected value in sys.exec_prefix', p.stderr)