From 7751436b94d96ce0978b301681b851edd6efed63 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Mon, 18 Dec 2023 06:17:00 -0500 Subject: [PATCH] Extract venv management from test_installation - Create and use a test.lib.helper.VirtualEnvironment class. - Import and use venv module instead of running "python -m venv". These changes make no significant difference in speed or clarity for the existing test in test_installation. The reason for this change is instead to support the use of new per-test virtual environments in at least one other test. --- test/lib/helper.py | 41 ++++++++++++++++++++++++++++++++++++++ test/test_installation.py | 42 +++++++++++++++++++-------------------- 2 files changed, 61 insertions(+), 22 deletions(-) diff --git a/test/lib/helper.py b/test/lib/helper.py index bfc8cdc43..2fb8990dd 100644 --- a/test/lib/helper.py +++ b/test/lib/helper.py @@ -14,6 +14,7 @@ import textwrap import time import unittest +import venv import gitdb @@ -36,6 +37,7 @@ "with_rw_repo", "with_rw_and_rw_remote_repo", "TestBase", + "VirtualEnvironment", "TestCase", "SkipTest", "skipIf", @@ -390,3 +392,42 @@ def _make_file(self, rela_path, data, repo=None): with open(abs_path, "w") as fp: fp.write(data) return abs_path + + +class VirtualEnvironment: + """A newly created Python virtual environment for use in a test.""" + + __slots__ = ("_env_dir",) + + def __init__(self, env_dir, *, with_pip): + self._env_dir = env_dir + venv.create(env_dir, symlinks=(os.name != "nt"), with_pip=with_pip) + + @property + def env_dir(self): + """The top-level directory of the environment.""" + return self._env_dir + + @property + def python(self): + """Path to the Python executable in the environment.""" + return self._executable("python") + + @property + def pip(self): + """Path to the pip executable in the environment, or RuntimeError if absent.""" + return self._executable("pip") + + @property + def sources(self): + """Path to a src directory in the environment, which may not exist yet.""" + return os.path.join(self.env_dir, "src") + + def _executable(self, basename): + if os.name == "nt": + path = osp.join(self.env_dir, "Scripts", basename + ".exe") + else: + path = osp.join(self.env_dir, "bin", basename) + if osp.isfile(path) or osp.islink(path): + return path + raise RuntimeError(f"no regular file or symlink {path!r}") diff --git a/test/test_installation.py b/test/test_installation.py index 0a200415b..15ed5b13b 100644 --- a/test/test_installation.py +++ b/test/test_installation.py @@ -4,31 +4,19 @@ import ast import os import subprocess -import sys -from test.lib import TestBase -from test.lib.helper import with_rw_directory +from test.lib import TestBase, VirtualEnvironment, with_rw_directory class TestInstallation(TestBase): - def setUp_venv(self, rw_dir): - self.venv = rw_dir - subprocess.run([sys.executable, "-m", "venv", self.venv], stdout=subprocess.PIPE) - bin_name = "Scripts" if os.name == "nt" else "bin" - self.python = os.path.join(self.venv, bin_name, "python") - self.pip = os.path.join(self.venv, bin_name, "pip") - self.sources = os.path.join(self.venv, "src") - self.cwd = os.path.dirname(os.path.dirname(__file__)) - os.symlink(self.cwd, self.sources, target_is_directory=True) - @with_rw_directory def test_installation(self, rw_dir): - self.setUp_venv(rw_dir) + venv = self._set_up_venv(rw_dir) result = subprocess.run( - [self.pip, "install", "."], + [venv.pip, "install", "."], stdout=subprocess.PIPE, - cwd=self.sources, + cwd=venv.sources, ) self.assertEqual( 0, @@ -37,9 +25,9 @@ def test_installation(self, rw_dir): ) result = subprocess.run( - [self.python, "-c", "import git"], + [venv.python, "-c", "import git"], stdout=subprocess.PIPE, - cwd=self.sources, + cwd=venv.sources, ) self.assertEqual( 0, @@ -48,9 +36,9 @@ def test_installation(self, rw_dir): ) result = subprocess.run( - [self.python, "-c", "import gitdb; import smmap"], + [venv.python, "-c", "import gitdb; import smmap"], stdout=subprocess.PIPE, - cwd=self.sources, + cwd=venv.sources, ) self.assertEqual( 0, @@ -62,9 +50,9 @@ def test_installation(self, rw_dir): # by inserting its location into PYTHONPATH or otherwise patched into # sys.path, make sure it is not wrongly inserted as the *first* entry. result = subprocess.run( - [self.python, "-c", "import sys; import git; print(sys.path)"], + [venv.python, "-c", "import sys; import git; print(sys.path)"], stdout=subprocess.PIPE, - cwd=self.sources, + cwd=venv.sources, ) syspath = result.stdout.decode("utf-8").splitlines()[0] syspath = ast.literal_eval(syspath) @@ -73,3 +61,13 @@ def test_installation(self, rw_dir): syspath[0], msg="Failed to follow the conventions for https://docs.python.org/3/library/sys.html#sys.path", ) + + @staticmethod + def _set_up_venv(rw_dir): + venv = VirtualEnvironment(rw_dir, with_pip=True) + os.symlink( + os.path.dirname(os.path.dirname(__file__)), + venv.sources, + target_is_directory=True, + ) + return venv