Skip to content

Commit 5541061

Browse files
committed
Add tests for venv and getpath in the case where Python is symlinked
1 parent 16ecabb commit 5541061

File tree

2 files changed

+94
-0
lines changed

2 files changed

+94
-0
lines changed

Lib/test/test_getpath.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,56 @@ def test_PYTHONHOME_in_venv(self):
864864
actual = getpath(ns, expected)
865865
self.assertEqual(expected, actual)
866866

867+
def test_venv_w_symlinked_base_executable(self):
868+
"""
869+
If we symlink the base executable, and point to it via home in pyvenv.cfg,
870+
we should have it as sys.executable (and sys.prefix should be the resolved location)
871+
"""
872+
ns = MockPosixNamespace(
873+
argv0="/venv/bin/python3",
874+
PREFIX="/some/_internal/prefix",
875+
base_exec_prefix="/foo/bar"
876+
)
877+
# Setup venv
878+
ns.add_known_xfile("/venv/bin/python3")
879+
ns.add_known_xfile("/usr/local/bin/python3")
880+
ns.add_known_xfile("/some/_internal/prefix/bin/python3")
881+
882+
ns.add_known_file("/venv/pyvenv.cfg", [
883+
# The published based executable location is /usr/local/bin - we don't want to
884+
# expose /some/internal/directory (this location can change under our feet)
885+
r"home = /usr/local/bin"
886+
])
887+
ns.add_known_link("/venv/bin/python3", "/usr/local/bin/python3")
888+
ns.add_known_link("/usr/local/bin/python3", "/some/_internal/prefix/bin/python3")
889+
890+
ns.add_known_file("/some/_internal/prefix/lib/python9.8/os.py")
891+
ns.add_known_dir("/some/_internal/prefix/lib/python9.8/lib-dynload")
892+
893+
# Put a file completely outside of /usr/local to validate that the issue
894+
# in https://github.com/python/cpython/issues/106045 is resolved.
895+
ns.add_known_dir("/usr/lib/python9.8/lib-dynload")
896+
897+
expected = dict(
898+
executable="/venv/bin/python3",
899+
prefix="/venv",
900+
exec_prefix="/venv",
901+
base_prefix="/some/_internal/prefix",
902+
base_exec_prefix="/some/_internal/prefix",
903+
# It is important to maintain the link to the original executable, as this
904+
# is used when creating a new virtual environment (which should also have home
905+
# set to /usr/local/bin to avoid bleeding the internal path to the venv)
906+
base_executable="/usr/local/bin/python3",
907+
module_search_paths_set=1,
908+
module_search_paths=[
909+
"/some/_internal/prefix/lib/python98.zip",
910+
"/some/_internal/prefix/lib/python9.8",
911+
"/some/_internal/prefix/lib/python9.8/lib-dynload",
912+
],
913+
)
914+
actual = getpath(ns, expected)
915+
self.assertEqual(expected, actual)
916+
867917
# ******************************************************************************
868918

869919
DEFAULT_NAMESPACE = dict(

Lib/test/test_venv.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,50 @@ def test_zippath_from_non_installed_posix(self):
752752
out, err = check_output(cmd)
753753
self.assertTrue(zip_landmark.encode() in out)
754754

755+
@unittest.skipIf(os.name == 'nt', 'not relevant on Windows')
756+
@requireVenvCreate
757+
def test_venv_from_venv_with_symlink(self):
758+
"""
759+
Test that we can create a venv from a venv using a base Python that is
760+
a symlink, without exposing the location of the symlink in pyvenv.cfg.
761+
"""
762+
rmtree(self.env_dir)
763+
public_prefix = os.path.realpath(tempfile.mkdtemp())
764+
self.addCleanup(rmtree, public_prefix)
765+
public_bin_dir = os.path.join(public_prefix, 'bin')
766+
os.mkdir(public_bin_dir)
767+
public_exe = os.path.join(public_bin_dir, self.exe)
768+
os.symlink(sys.executable, public_exe)
769+
cmd = [public_exe,
770+
"-m",
771+
"venv",
772+
"--without-pip",
773+
"--without-scm-ignore-files",
774+
self.env_dir]
775+
776+
subprocess.check_call(cmd)
777+
778+
# Verify that we don't expose the internal prefix to the first venv config:
779+
contents = (pathlib.Path(self.env_dir) / 'pyvenv.cfg').read_text()
780+
self.assertIn(f'home = {public_bin_dir}\n', contents)
781+
782+
# Now use the venv to make another, and assert that the internal env is
783+
# also not exposed there.
784+
second_venv = os.path.realpath(tempfile.mkdtemp())
785+
self.addCleanup(rmtree, second_venv)
786+
787+
cmd = [os.path.join(self.env_dir, 'bin', 'python3'),
788+
"-m",
789+
"venv",
790+
"--without-pip",
791+
"--without-scm-ignore-files",
792+
second_venv]
793+
794+
subprocess.check_call(cmd)
795+
796+
contents = (pathlib.Path(second_venv) / 'pyvenv.cfg').read_text()
797+
self.assertIn(f'home = {public_bin_dir}\n', contents)
798+
755799
@requireVenvCreate
756800
def test_activate_shell_script_has_no_dos_newlines(self):
757801
"""

0 commit comments

Comments
 (0)