Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Lib/test/test_venv.py
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,29 @@ def test_unoverwritable_fails(self):
self.assertRaises((ValueError, OSError), venv.create, self.env_dir)
self.clear_directory(self.env_dir)

@unittest.skipUnless(can_symlink(), 'Needs symlinks')
def test_symlinked_file_replaced(self):
"""
Test that venv creation replaces a symlink where it writes a file.
"""
outside_dir = tempfile.mkdtemp()
self.addCleanup(rmtree, outside_dir)
outside = os.path.join(outside_dir, 'outside')
with open(outside, 'w', encoding='utf-8') as f:
f.write('untouched\n')
links = ['pyvenv.cfg', '.gitignore',
os.path.join(self.bindir, 'activate')]
os.mkdir(os.path.join(self.env_dir, self.bindir))
for link in links:
os.symlink(outside, self.get_env_file(link))
venv.create(self.env_dir, scm_ignore_files={'git'})
for link in links:
with self.subTest(link=link):
self.assertFalse(os.path.islink(self.get_env_file(link)))
self.assertTrue(os.path.isfile(self.get_env_file(link)))
with open(outside, encoding='utf-8') as f:
self.assertEqual(f.read(), 'untouched\n')

def test_upgrade(self):
"""
Test upgrading an existing environment directory.
Expand Down
8 changes: 8 additions & 0 deletions Lib/venv/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
logger = logging.getLogger(__name__)


def _unlink_symlink(path):
if os.path.islink(path):
os.unlink(path)


class EnvBuilder:
"""
This class exists to allow virtual environment creation to be
Expand Down Expand Up @@ -223,6 +228,7 @@ def create_configuration(self, context):
being processed.
"""
context.cfg_path = path = os.path.join(context.env_dir, 'pyvenv.cfg')
_unlink_symlink(path)
with open(path, 'w', encoding='utf-8') as f:
f.write('home = %s\n' % context.python_dir)
if self.system_site_packages:
Expand Down Expand Up @@ -291,6 +297,7 @@ def create_git_ignore_file(self, context):
ignored by git.
"""
gitignore_path = os.path.join(context.env_dir, '.gitignore')
_unlink_symlink(gitignore_path)
with open(gitignore_path, 'w', encoding='utf-8') as file:
file.write('# Created by venv; '
'see https://docs.python.org/3/library/venv.html\n')
Expand Down Expand Up @@ -585,6 +592,7 @@ def skip_file(f):
if not os.path.exists(dstdir):
os.makedirs(dstdir)
dstfile = os.path.join(dstdir, f)
_unlink_symlink(dstfile)
if os.name == 'nt' and srcfile.endswith(('.exe', '.pdb')):
shutil.copy2(srcfile, dstfile)
continue
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:mod:`venv`: a symbolic link in place of ``pyvenv.cfg``, ``.gitignore`` or
an activation script is now replaced instead of written through.
Loading