mirror of
https://github.com/python/cpython.git
synced 2026-06-28 03:41:13 +00:00
gh-127727: Warn when running a virtual environment created for a different minor Python version (#149715)
This commit is contained in:
parent
b8786dc33b
commit
afda368fd6
3 changed files with 253 additions and 0 deletions
30
Lib/site.py
30
Lib/site.py
|
|
@ -982,6 +982,7 @@ def _venv(state):
|
|||
if candidate_conf:
|
||||
virtual_conf = candidate_conf
|
||||
system_site = "true"
|
||||
version, version_info = None, None
|
||||
# Issue 25185: Use UTF-8, as that's what the venv module uses when
|
||||
# writing the file.
|
||||
with open(virtual_conf, encoding='utf-8') as f:
|
||||
|
|
@ -994,6 +995,35 @@ def _venv(state):
|
|||
system_site = value.lower()
|
||||
elif key == 'home':
|
||||
sys._home = value
|
||||
elif key == 'version':
|
||||
version = value
|
||||
elif key == 'version_info':
|
||||
version_info = value
|
||||
|
||||
for field_name, field_value in [
|
||||
('version',version), ('version_info',version_info)
|
||||
]:
|
||||
if field_value is not None:
|
||||
try:
|
||||
major, minor = map(int, field_value.split(".")[:2])
|
||||
except (ValueError, AttributeError):
|
||||
_warn(
|
||||
f"Malformed {field_name} string in pyvenv.cfg: {field_value!r}",
|
||||
RuntimeWarning,
|
||||
)
|
||||
else:
|
||||
if (
|
||||
major == sys.version_info.major
|
||||
and minor != sys.version_info.minor
|
||||
):
|
||||
_warn(
|
||||
f"This virtual environment was created for Python {major}.{minor}, "
|
||||
f"but the current interpreter is Python "
|
||||
f"{sys.version_info.major}.{sys.version_info.minor}. "
|
||||
"Consider running `python -m venv --upgrade` to update the environment.",
|
||||
RuntimeWarning,
|
||||
)
|
||||
break
|
||||
|
||||
if sys.prefix != site_prefix:
|
||||
_warn(
|
||||
|
|
|
|||
|
|
@ -310,6 +310,226 @@ def test_sysconfig(self):
|
|||
out, err = check_output(cmd, encoding='utf-8')
|
||||
self.assertEqual(out.strip(), expected, err)
|
||||
|
||||
@requireVenvCreate
|
||||
def test_version_mismatch_warning(self):
|
||||
"""
|
||||
Test that a warning is emitted when running a venv created for a
|
||||
different minor Python version.
|
||||
"""
|
||||
rmtree(self.env_dir)
|
||||
|
||||
wrong_minor = sys.version_info.minor + 1
|
||||
self.run_with_capture(venv.create, self.env_dir, with_pip=False)
|
||||
|
||||
cfg_path = self.get_env_file('pyvenv.cfg')
|
||||
with open(cfg_path, 'r', encoding='utf-8') as f:
|
||||
cfg_content = f.read()
|
||||
|
||||
new_version = f"{sys.version_info.major}.{wrong_minor}"
|
||||
if 'version =' in cfg_content:
|
||||
cfg_content = re.sub(r'version = \d+\.\d+', f'version = {new_version}', cfg_content)
|
||||
|
||||
cfg_content += f'\nversion_info = {new_version}\n'
|
||||
|
||||
with open(cfg_path, 'w', encoding='utf-8') as f:
|
||||
f.write(cfg_content)
|
||||
|
||||
envpy = self.envpy(real_env_dir=True)
|
||||
|
||||
proc = subprocess.run(
|
||||
[envpy, '-c', 'import sys; print("done")'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env={**os.environ, "PYTHONHOME": ""}
|
||||
)
|
||||
|
||||
self.assertIn(f"Python {sys.version_info.major}.{wrong_minor}", proc.stderr)
|
||||
self.assertIn("Consider running `python -m venv --upgrade`", proc.stderr)
|
||||
|
||||
@requireVenvCreate
|
||||
def test_version_info_mismatch_warning(self):
|
||||
"""
|
||||
Test that a warning is emitted when version_info (used by virtualenv)
|
||||
indicates a different minor version.
|
||||
"""
|
||||
rmtree(self.env_dir)
|
||||
wrong_minor = sys.version_info.minor + 1
|
||||
self.run_with_capture(venv.create, self.env_dir, with_pip=False)
|
||||
|
||||
cfg_path = self.get_env_file('pyvenv.cfg')
|
||||
with open(cfg_path, 'r', encoding='utf-8') as f:
|
||||
cfg_content = f.read()
|
||||
|
||||
# Add only version_info, don't modify version
|
||||
new_version = f"{sys.version_info.major}.{wrong_minor}"
|
||||
cfg_content += f'\nversion_info = {new_version}\n'
|
||||
|
||||
with open(cfg_path, 'w', encoding='utf-8') as f:
|
||||
f.write(cfg_content)
|
||||
|
||||
envpy = self.envpy(real_env_dir=True)
|
||||
proc = subprocess.run(
|
||||
[envpy, '-c', 'import sys; print("done")'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env={**os.environ, "PYTHONHOME": ""}
|
||||
)
|
||||
|
||||
self.assertIn(f"Python {sys.version_info.major}.{wrong_minor}", proc.stderr)
|
||||
self.assertIn("Consider running `python -m venv --upgrade`", proc.stderr)
|
||||
|
||||
@requireVenvCreate
|
||||
def test_version_match_no_warning(self):
|
||||
"""
|
||||
Test that no warning is emitted when the venv version matches.
|
||||
"""
|
||||
rmtree(self.env_dir)
|
||||
|
||||
self.run_with_capture(venv.create, self.env_dir, with_pip=False)
|
||||
cfg_path = self.get_env_file('pyvenv.cfg')
|
||||
with open(cfg_path, 'r', encoding='utf-8') as f:
|
||||
cfg_content = f.read()
|
||||
expected_version = f"{sys.version_info.major}.{sys.version_info.minor}"
|
||||
|
||||
with open(cfg_path, 'w', encoding='utf-8') as f:
|
||||
f.write(cfg_content)
|
||||
envpy = self.envpy(real_env_dir=True)
|
||||
proc = subprocess.run(
|
||||
[envpy, '-c', 'import sys; print("done")'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env={**os.environ, "PYTHONHOME": ""}
|
||||
)
|
||||
|
||||
self.assertNotIn("Consider running `python -m venv --upgrade`", proc.stderr)
|
||||
|
||||
@requireVenvCreate
|
||||
def test_malformed_version_warning(self):
|
||||
"""
|
||||
Test that a warning is emitted on malformed version string
|
||||
in pyenv.cfg
|
||||
"""
|
||||
rmtree(self.env_dir)
|
||||
|
||||
self.run_with_capture(venv.create, self.env_dir, with_pip=False)
|
||||
|
||||
cfg_path = self.get_env_file('pyvenv.cfg')
|
||||
with open(cfg_path, 'r', encoding='utf-8') as f:
|
||||
cfg_content = f.read()
|
||||
|
||||
malformed_version = "not.a.version"
|
||||
if 'version =' in cfg_content:
|
||||
cfg_content = re.sub(r'version = .+', f'version = {malformed_version}', cfg_content)
|
||||
|
||||
with open(cfg_path, 'w', encoding='utf-8') as f:
|
||||
f.write(cfg_content)
|
||||
|
||||
envpy = self.envpy(real_env_dir=True)
|
||||
proc = subprocess.run(
|
||||
[envpy, '-c', 'import sys; print("done")'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env={**os.environ, "PYTHONHOME": ""}
|
||||
)
|
||||
self.assertIn("Malformed version string", proc.stderr)
|
||||
self.assertIn(malformed_version, proc.stderr)
|
||||
|
||||
@requireVenvCreate
|
||||
def test_malformed_version_info_warning(self):
|
||||
"""
|
||||
Test that a warning is emitted on malformed version_info string
|
||||
in pyenv.cfg
|
||||
"""
|
||||
rmtree(self.env_dir)
|
||||
self.run_with_capture(venv.create, self.env_dir, with_pip=False)
|
||||
|
||||
cfg_path = self.get_env_file('pyvenv.cfg')
|
||||
with open(cfg_path, 'r', encoding='utf-8') as f:
|
||||
cfg_content = f.read()
|
||||
|
||||
malformed_version = "invalid.version"
|
||||
cfg_content += f'\nversion_info = {malformed_version}\n'
|
||||
|
||||
with open(cfg_path, 'w', encoding='utf-8') as f:
|
||||
f.write(cfg_content)
|
||||
|
||||
envpy = self.envpy(real_env_dir=True)
|
||||
proc = subprocess.run(
|
||||
[envpy, '-c', 'import sys; print("done")'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env={**os.environ, "PYTHONHOME": ""}
|
||||
)
|
||||
|
||||
self.assertIn("Malformed version_info string", proc.stderr)
|
||||
self.assertIn(malformed_version, proc.stderr)
|
||||
|
||||
@requireVenvCreate
|
||||
def test_conflicting_version_fields(self):
|
||||
"""
|
||||
Test behavior when both version and version_info are present
|
||||
but contain different values. Should warn based on first mismatch found.
|
||||
"""
|
||||
rmtree(self.env_dir)
|
||||
wrong_minor = sys.version_info.minor + 1
|
||||
self.run_with_capture(venv.create, self.env_dir, with_pip=False)
|
||||
|
||||
cfg_path = self.get_env_file('pyvenv.cfg')
|
||||
with open(cfg_path, 'r', encoding='utf-8') as f:
|
||||
cfg_content = f.read()
|
||||
|
||||
version_wrong = f"{sys.version_info.major}.{wrong_minor}"
|
||||
if 'version =' in cfg_content:
|
||||
cfg_content = re.sub(r'version = \d+\.\d+', f'version = {version_wrong}', cfg_content)
|
||||
|
||||
version_info_wrong = f"{sys.version_info.major}.{wrong_minor + 1}"
|
||||
cfg_content += f'\nversion_info = {version_info_wrong}\n'
|
||||
|
||||
with open(cfg_path, 'w', encoding='utf-8') as f:
|
||||
f.write(cfg_content)
|
||||
|
||||
envpy = self.envpy(real_env_dir=True)
|
||||
proc = subprocess.run(
|
||||
[envpy, '-c', 'import sys; print("done")'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env={**os.environ, "PYTHONHOME": ""}
|
||||
)
|
||||
|
||||
self.assertIn("Consider running `python -m venv --upgrade`", proc.stderr)
|
||||
self.assertEqual(proc.stderr.count("Consider running `python -m venv --upgrade`"), 1)
|
||||
|
||||
@requireVenvCreate
|
||||
def test_different_major_version_no_warning(self):
|
||||
"""
|
||||
Test that no warning is emitted when major version differs.
|
||||
The warning should only trigger for same major, different minor.
|
||||
"""
|
||||
rmtree(self.env_dir)
|
||||
self.run_with_capture(venv.create, self.env_dir, with_pip=False)
|
||||
|
||||
cfg_path = self.get_env_file('pyvenv.cfg')
|
||||
with open(cfg_path, 'r', encoding='utf-8') as f:
|
||||
cfg_content = f.read()
|
||||
|
||||
different_major = sys.version_info.major + 1
|
||||
new_version = f"{different_major}.{sys.version_info.minor}"
|
||||
|
||||
if 'version =' in cfg_content:
|
||||
cfg_content = re.sub(r'version = \d+\.\d+', f'version = {new_version}', cfg_content)
|
||||
with open(cfg_path, 'w', encoding='utf-8') as f:
|
||||
f.write(cfg_content)
|
||||
|
||||
envpy = self.envpy(real_env_dir=True)
|
||||
proc = subprocess.run(
|
||||
[envpy, '-c', 'import sys; print("done")'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env={**os.environ, "PYTHONHOME": ""}
|
||||
)
|
||||
|
||||
self.assertNotIn("Consider running `python -m venv --upgrade`", proc.stderr)
|
||||
|
||||
@requireVenvCreate
|
||||
@unittest.skipUnless(can_symlink(), 'Needs symlinks')
|
||||
def test_sysconfig_symlinks(self):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
Warn when running a virtual environment created for a different minor Python
|
||||
version than the current interpreter, and suggest using ``python -m venv
|
||||
--upgrade``.
|
||||
Loading…
Add table
Add a link
Reference in a new issue