Skip to content

Commit 031f637

Browse files
committed
gh-143768: Replace a dangling interpreter symlink when creating a venv
1 parent 6544bf4 commit 031f637

3 files changed

Lines changed: 32 additions & 0 deletions

File tree

Lib/test/test_venv.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,33 @@ def test_failed_symlink(self):
608608
filepath_regex = r"'[A-Z]:\\\\(?:[^\\\\]+\\\\)*[^\\\\]+'"
609609
self.assertRegex(err, rf"Unable to symlink {filepath_regex} to {filepath_regex}")
610610

611+
@requireVenvCreate
612+
@unittest.skipIf(os.name == 'nt', 'not relevant on Windows')
613+
@unittest.skipUnless(can_symlink(), 'Needs symlinks')
614+
def test_broken_symlink_in_existing_venv(self):
615+
"""
616+
Test creating a venv when a stale venv with broken symlinks exists.
617+
"""
618+
bindir = os.path.join(self.env_dir, self.bindir)
619+
os.makedirs(bindir)
620+
python = os.path.join(bindir, 'python3')
621+
os.symlink('/path/to/deleted/conda/env/bin/python3', python)
622+
self.assertTrue(os.path.islink(python))
623+
self.assertFalse(os.path.exists(python))
624+
625+
builder = venv.EnvBuilder(with_pip=False, symlinks=True)
626+
self.run_with_capture(builder.create, self.env_dir)
627+
self.assertTrue(os.path.islink(python))
628+
self.assertTrue(os.path.exists(python))
629+
630+
rmtree(self.env_dir)
631+
os.makedirs(bindir)
632+
os.symlink('/path/to/deleted/conda/env/bin/python3', python)
633+
builder = venv.EnvBuilder(with_pip=False, symlinks=False)
634+
self.run_with_capture(builder.create, self.env_dir)
635+
self.assertFalse(os.path.islink(python))
636+
self.assertTrue(os.path.exists(python))
637+
611638
@requireVenvCreate
612639
def test_multiprocessing(self):
613640
"""

Lib/venv/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):
267267
switch to a different set of files instead.)
268268
"""
269269
assert os.name != 'nt'
270+
if os.path.islink(dst) and not os.path.exists(dst):
271+
os.unlink(dst)
270272
force_copy = not self.symlinks
271273
if not force_copy:
272274
try:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:mod:`venv`: Replace a dangling interpreter symlink in an existing
2+
environment instead of failing or silently leaving it broken. Fix by
3+
Clay Dugo.

0 commit comments

Comments
 (0)