diff options
Diffstat (limited to 'tests/lib/__init__.py')
-rw-r--r-- | tests/lib/__init__.py | 638 |
1 files changed, 375 insertions, 263 deletions
diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py index ddb4bfe4c..7410072f5 100644 --- a/tests/lib/__init__.py +++ b/tests/lib/__init__.py @@ -1,5 +1,6 @@ import json import os +import pathlib import re import shutil import site @@ -11,12 +12,25 @@ from contextlib import contextmanager from hashlib import sha256 from io import BytesIO from textwrap import dedent -from typing import List, Optional +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterable, + Iterator, + List, + Mapping, + Optional, + Tuple, + Union, + cast, +) from zipfile import ZipFile import pytest from pip._vendor.packaging.utils import canonicalize_name -from scripttest import FoundDir, TestFileEnvironment +from scripttest import FoundDir, FoundFile, ProcResult, TestFileEnvironment from pip._internal.index.collector import LinkCollector from pip._internal.index.package_finder import PackageFinder @@ -25,50 +39,33 @@ from pip._internal.models.search_scope import SearchScope from pip._internal.models.selection_prefs import SelectionPreferences from pip._internal.models.target_python import TargetPython from pip._internal.network.session import PipSession -from pip._internal.utils.deprecation import DEPRECATION_MSG_PREFIX -from tests.lib.path import Path, curdir +from tests.lib.venv import VirtualEnvironment from tests.lib.wheel import make_wheel -DATA_DIR = Path(__file__).parent.parent.joinpath("data").resolve() -SRC_DIR = Path(__file__).resolve().parent.parent.parent +if TYPE_CHECKING: + # Literal was introduced in Python 3.8. + from typing import Literal -pyversion = get_major_minor_version() + ResolverVariant = Literal["resolvelib", "legacy"] +else: + ResolverVariant = str -CURRENT_PY_VERSION_INFO = sys.version_info[:3] +DATA_DIR = pathlib.Path(__file__).parent.parent.joinpath("data").resolve() +SRC_DIR = pathlib.Path(__file__).resolve().parent.parent.parent +pyversion = get_major_minor_version() -def assert_paths_equal(actual, expected): - assert os.path.normpath(actual) == os.path.normpath(expected) +CURRENT_PY_VERSION_INFO = sys.version_info[:3] +_Test = Callable[..., None] +_FilesState = Dict[str, Union[FoundDir, FoundFile]] -def path_to_url(path): - """ - Convert a path to URI. The path will be made absolute and - will not have quoted path parts. - (adapted from pip.util) - """ - path = os.path.normpath(os.path.abspath(path)) - drive, path = os.path.splitdrive(path) - filepath = path.split(os.path.sep) - url = "/".join(filepath) - if drive: - # Note: match urllib.request.pathname2url's - # behavior: uppercase the drive letter. - return "file:///" + drive.upper() + url - return "file://" + url - - -def _test_path_to_file_url(path): - """ - Convert a test Path to a "file://" URL. - Args: - path: a tests.lib.path.Path object. - """ - return "file://" + path.resolve().replace("\\", "/") +def assert_paths_equal(actual: str, expected: str) -> None: + assert os.path.normpath(actual) == os.path.normpath(expected) -def create_file(path, contents=None): +def create_file(path: str, contents: Optional[str] = None) -> None: """Create a file on the path, with the given contents""" from pip._internal.utils.misc import ensure_dir @@ -81,23 +78,26 @@ def create_file(path, contents=None): def make_test_search_scope( - find_links=None, # type: Optional[List[str]] - index_urls=None, # type: Optional[List[str]] -): + find_links: Optional[List[str]] = None, + index_urls: Optional[List[str]] = None, +) -> SearchScope: if find_links is None: find_links = [] if index_urls is None: index_urls = [] - return SearchScope.create(find_links=find_links, index_urls=index_urls) + return SearchScope.create( + find_links=find_links, + index_urls=index_urls, + no_index=False, + ) def make_test_link_collector( - find_links=None, # type: Optional[List[str]] - index_urls=None, # type: Optional[List[str]] - session=None, # type: Optional[PipSession] -): - # type: (...) -> LinkCollector + find_links: Optional[List[str]] = None, + index_urls: Optional[List[str]] = None, + session: Optional[PipSession] = None, +) -> LinkCollector: """ Create a LinkCollector object for testing purposes. """ @@ -113,13 +113,12 @@ def make_test_link_collector( def make_test_finder( - find_links=None, # type: Optional[List[str]] - index_urls=None, # type: Optional[List[str]] - allow_all_prereleases=False, # type: bool - session=None, # type: Optional[PipSession] - target_python=None, # type: Optional[TargetPython] -): - # type: (...) -> PackageFinder + find_links: Optional[List[str]] = None, + index_urls: Optional[List[str]] = None, + allow_all_prereleases: bool = False, + session: Optional[PipSession] = None, + target_python: Optional[TargetPython] = None, +) -> PackageFinder: """ Create a PackageFinder for testing purposes. """ @@ -152,17 +151,23 @@ class TestData: data into a directory and operating on the copied data. """ - def __init__(self, root, source=None): + __test__ = False + + def __init__( + self, + root: pathlib.Path, + source: Optional[pathlib.Path] = None, + ) -> None: self.source = source or DATA_DIR - self.root = Path(root).resolve() + self.root = root.resolve() @classmethod - def copy(cls, root): + def copy(cls, root: pathlib.Path) -> "TestData": obj = cls(root) obj.reset() return obj - def reset(self): + def reset(self) -> None: # Check explicitly for the target directory to avoid overly-broad # try/except. if self.root.exists(): @@ -170,51 +175,51 @@ class TestData: shutil.copytree(self.source, self.root, symlinks=True) @property - def packages(self): + def packages(self) -> pathlib.Path: return self.root.joinpath("packages") @property - def packages2(self): + def packages2(self) -> pathlib.Path: return self.root.joinpath("packages2") @property - def packages3(self): + def packages3(self) -> pathlib.Path: return self.root.joinpath("packages3") @property - def src(self): + def src(self) -> pathlib.Path: return self.root.joinpath("src") @property - def indexes(self): + def indexes(self) -> pathlib.Path: return self.root.joinpath("indexes") @property - def reqfiles(self): + def reqfiles(self) -> pathlib.Path: return self.root.joinpath("reqfiles") @property - def completion_paths(self): + def completion_paths(self) -> pathlib.Path: return self.root.joinpath("completion_paths") @property - def find_links(self): - return path_to_url(self.packages) + def find_links(self) -> str: + return self.packages.as_uri() @property - def find_links2(self): - return path_to_url(self.packages2) + def find_links2(self) -> str: + return self.packages2.as_uri() @property - def find_links3(self): - return path_to_url(self.packages3) + def find_links3(self) -> str: + return self.packages3.as_uri() @property - def backends(self): - return path_to_url(self.root.joinpath("backends")) + def backends(self) -> str: + return self.root.joinpath("backends").as_uri() - def index_url(self, index="simple"): - return path_to_url(self.root.joinpath("indexes", index)) + def index_url(self, index: str = "simple") -> str: + return self.root.joinpath("indexes", index).as_uri() class TestFailure(AssertionError): @@ -222,11 +227,39 @@ class TestFailure(AssertionError): An "assertion" failed during testing. """ - pass + +StrPath = Union[str, pathlib.Path] + + +class FoundFiles(Mapping[StrPath, FoundFile]): + def __init__(self, paths: Mapping[str, FoundFile]) -> None: + self._paths = {pathlib.Path(k): v for k, v in paths.items()} + + def __contains__(self, o: object) -> bool: + if isinstance(o, pathlib.Path): + return o in self._paths + elif isinstance(o, str): + return pathlib.Path(o) in self._paths + return False + + def __len__(self) -> int: + return len(self._paths) + + def __getitem__(self, k: StrPath) -> FoundFile: + if isinstance(k, pathlib.Path): + return self._paths[k] + elif isinstance(k, str): + return self._paths[pathlib.Path(k)] + raise KeyError(k) + + def __iter__(self) -> Iterator[pathlib.Path]: + return iter(self._paths) class TestPipResult: - def __init__(self, impl, verbose=False): + __test__ = False + + def __init__(self, impl: ProcResult, verbose: bool = False) -> None: self._impl = impl if verbose: @@ -236,38 +269,50 @@ class TestPipResult: print(self.stderr) print("=======================") - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> Any: return getattr(self._impl, attr) if sys.platform == "win32": @property - def stdout(self): + def stdout(self) -> str: return self._impl.stdout.replace("\r\n", "\n") @property - def stderr(self): + def stderr(self) -> str: return self._impl.stderr.replace("\r\n", "\n") - def __str__(self): + def __str__(self) -> str: return str(self._impl).replace("\r\n", "\n") else: # Python doesn't automatically forward __str__ through __getattr__ - def __str__(self): + def __str__(self) -> str: return str(self._impl) + @property + def files_created(self) -> FoundFiles: + return FoundFiles(self._impl.files_created) + + @property + def files_updated(self) -> FoundFiles: + return FoundFiles(self._impl.files_updated) + + @property + def files_deleted(self) -> FoundFiles: + return FoundFiles(self._impl.files_deleted) + def assert_installed( self, - pkg_name, - editable=True, - with_files=None, - without_files=None, - without_egg_link=False, - use_user_site=False, - sub_dir=False, - ): + pkg_name: str, + editable: bool = True, + with_files: Optional[List[str]] = None, + without_files: Optional[List[str]] = None, + without_egg_link: bool = False, + use_user_site: bool = False, + sub_dir: Optional[str] = None, + ) -> None: with_files = with_files or [] without_files = without_files or [] e = self.test_env @@ -282,9 +327,9 @@ class TestPipResult: pkg_dir = e.site_packages / pkg_name if use_user_site: - egg_link_path = e.user_site / pkg_name + ".egg-link" + egg_link_path = e.user_site / f"{pkg_name}.egg-link" else: - egg_link_path = e.site_packages / pkg_name + ".egg-link" + egg_link_path = e.site_packages / f"{pkg_name}.egg-link" if without_egg_link: if egg_link_path in self.files_created: @@ -303,9 +348,9 @@ class TestPipResult: # FIXME: I don't understand why there's a trailing . here if not ( egg_link_contents.endswith("\n.") - and egg_link_contents[:-2].endswith(pkg_dir) + and egg_link_contents[:-2].endswith(os.fspath(pkg_dir)) ): - expected_ending = pkg_dir + "\n." + expected_ending = f"{pkg_dir}\n." raise TestFailure( textwrap.dedent( f""" @@ -327,9 +372,9 @@ class TestPipResult: maybe = "" if without_egg_link else "not " raise TestFailure(f"{pth_file} unexpectedly {maybe}updated by install") - if (pkg_dir in self.files_created) == (curdir in without_files): - maybe = "not " if curdir in without_files else "" - files = sorted(self.files_created) + if (pkg_dir in self.files_created) == (os.curdir in without_files): + maybe = "not " if os.curdir in without_files else "" + files = sorted(p.as_posix() for p in self.files_created) raise TestFailure( textwrap.dedent( f""" @@ -354,20 +399,20 @@ class TestPipResult: f"Package directory {pkg_dir!r} has unexpected content {f}" ) - def did_create(self, path, message=None): - assert str(path) in self.files_created, _one_or_both(message, self) + def did_create(self, path: StrPath, message: Optional[str] = None) -> None: + assert path in self.files_created, _one_or_both(message, self) - def did_not_create(self, path, message=None): - assert str(path) not in self.files_created, _one_or_both(message, self) + def did_not_create(self, p: StrPath, message: Optional[str] = None) -> None: + assert p not in self.files_created, _one_or_both(message, self) - def did_update(self, path, message=None): - assert str(path) in self.files_updated, _one_or_both(message, self) + def did_update(self, path: StrPath, message: Optional[str] = None) -> None: + assert path in self.files_updated, _one_or_both(message, self) - def did_not_update(self, path, message=None): - assert str(path) not in self.files_updated, _one_or_both(message, self) + def did_not_update(self, p: StrPath, message: Optional[str] = None) -> None: + assert p not in self.files_updated, _one_or_both(message, self) -def _one_or_both(a, b): +def _one_or_both(a: Optional[str], b: Any) -> str: """Returns f"{a}\n{b}" if a is truthy, else returns str(b).""" if not a: return str(b) @@ -375,7 +420,7 @@ def _one_or_both(a, b): return f"{a}\n{b}" -def make_check_stderr_message(stderr, line, reason): +def make_check_stderr_message(stderr: str, line: str, reason: str) -> str: """ Create an exception message to use inside check_stderr(). """ @@ -389,10 +434,10 @@ def make_check_stderr_message(stderr, line, reason): def _check_stderr( - stderr, - allow_stderr_warning, - allow_stderr_error, -): + stderr: str, + allow_stderr_warning: bool, + allow_stderr_error: bool, +) -> None: """ Check the given stderr for logged warnings and errors. @@ -405,6 +450,7 @@ def _check_stderr( lines = stderr.splitlines() for line in lines: + line = line.lstrip() # First check for logging errors, which we don't allow during # tests even if allow_stderr_error=True (since a logging error # would signal a bug in pip's code). @@ -431,7 +477,7 @@ def _check_stderr( if allow_stderr_warning: continue - if line.startswith("WARNING: ") or line.startswith(DEPRECATION_MSG_PREFIX): + if line.startswith("WARNING: "): reason = ( "stderr has an unexpected warning " "(pass allow_stderr_warning=True to permit this)" @@ -457,28 +503,31 @@ class PipTestEnvironment(TestFileEnvironment): exe = sys.platform == "win32" and ".exe" or "" verbose = False - def __init__(self, base_path, *args, virtualenv, pip_expect_warning=None, **kwargs): - # Make our base_path a test.lib.path.Path object - base_path = Path(base_path) - + def __init__( + self, + base_path: pathlib.Path, + *args: Any, + virtualenv: VirtualEnvironment, + pip_expect_warning: bool = False, + zipapp: Optional[str] = None, + **kwargs: Any, + ) -> None: # Store paths related to the virtual environment self.venv_path = virtualenv.location self.lib_path = virtualenv.lib self.site_packages_path = virtualenv.site self.bin_path = virtualenv.bin + assert site.USER_BASE is not None + assert site.USER_SITE is not None + self.user_base_path = self.venv_path.joinpath("user") self.user_site_path = self.venv_path.joinpath( "user", site.USER_SITE[len(site.USER_BASE) + 1 :], ) if sys.platform == "win32": - if sys.version_info >= (3, 5): - scripts_base = Path( - os.path.normpath(self.user_site_path.joinpath("..")) - ) - else: - scripts_base = self.user_base_path + scripts_base = self.user_site_path.joinpath("..").resolve() self.user_bin_path = scripts_base.joinpath("Scripts") else: self.user_bin_path = self.user_base_path.joinpath( @@ -494,8 +543,8 @@ class PipTestEnvironment(TestFileEnvironment): # Setup our environment environ = kwargs.setdefault("environ", os.environ.copy()) - environ["PATH"] = Path.pathsep.join( - [self.bin_path] + [environ.get("PATH", [])], + environ["PATH"] = os.pathsep.join( + [os.fspath(self.bin_path), environ.get("PATH", "")], ) environ["PYTHONUSERBASE"] = self.user_base_path # Writing bytecode can mess up updated file detection @@ -507,6 +556,9 @@ class PipTestEnvironment(TestFileEnvironment): # (useful for Python version deprecation) self.pip_expect_warning = pip_expect_warning + # The name of an (optional) zipapp to use when running pip + self.zipapp = zipapp + # Call the TestFileEnvironment __init__ super().__init__(base_path, *args, **kwargs) @@ -523,13 +575,13 @@ class PipTestEnvironment(TestFileEnvironment): "scratch", ]: real_name = f"{name}_path" - relative_path = Path( + relative_path = pathlib.Path( os.path.relpath(getattr(self, real_name), self.base_path) ) setattr(self, name, relative_path) # Make sure temp_path is a Path object - self.temp_path = Path(self.temp_path) + self.temp_path: pathlib.Path = pathlib.Path(self.temp_path) # Ensure the tmp dir exists, things break horribly if it doesn't self.temp_path.mkdir() @@ -538,14 +590,18 @@ class PipTestEnvironment(TestFileEnvironment): self.user_site_path.mkdir(parents=True) self.user_site_path.joinpath("easy-install.pth").touch() - def _ignore_file(self, fn): + def _ignore_file(self, fn: str) -> bool: if fn.endswith("__pycache__") or fn.endswith(".pyc"): result = True + elif self.zipapp and fn.endswith("cacert.pem"): + # Temporary copies of cacert.pem are extracted + # when running from a zipapp + result = True else: result = super()._ignore_file(fn) return result - def _find_traverse(self, path, result): + def _find_traverse(self, path: str, result: Dict[str, FoundDir]) -> None: # Ignore symlinked directories to avoid duplicates in `run()` # results because of venv `lib64 -> lib/` symlink on Linux. full = os.path.join(self.base_path, path) @@ -557,14 +613,13 @@ class PipTestEnvironment(TestFileEnvironment): def run( self, - *args, - cwd=None, - run_from=None, - allow_stderr_error=None, - allow_stderr_warning=None, - allow_error=None, - **kw, - ): + *args: str, + cwd: Optional[StrPath] = None, + allow_stderr_error: Optional[bool] = None, + allow_stderr_warning: Optional[bool] = None, + allow_error: bool = False, + **kw: Any, + ) -> TestPipResult: """ :param allow_stderr_error: whether a logged error is allowed in stderr. Passing True for this argument implies @@ -587,11 +642,10 @@ class PipTestEnvironment(TestFileEnvironment): if self.verbose: print(f">> running {args} {kw}") - assert not cwd or not run_from, "Don't use run_from; it's going away" - cwd = cwd or run_from or self.cwd + cwd = cwd or self.cwd if sys.platform == "win32": # Partial fix for ScriptTest.run using `shell=True` on Windows. - args = [str(a).replace("^", "^^").replace("&", "^&") for a in args] + args = tuple(str(a).replace("^", "^^").replace("&", "^&") for a in args) if allow_error: kw["expect_error"] = True @@ -635,7 +689,7 @@ class PipTestEnvironment(TestFileEnvironment): if expect_error and not allow_error: if result.returncode == 0: __tracebackhide__ = True - raise AssertionError("Script passed unexpectedly.") + raise AssertionError(f"Script passed unexpectedly:\n{result}") _check_stderr( result.stderr, @@ -645,32 +699,44 @@ class PipTestEnvironment(TestFileEnvironment): return TestPipResult(result, verbose=self.verbose) - def pip(self, *args, use_module=True, **kwargs): + def pip( + self, + *args: StrPath, + use_module: bool = True, + **kwargs: Any, + ) -> TestPipResult: __tracebackhide__ = True if self.pip_expect_warning: kwargs["allow_stderr_warning"] = True - if use_module: + if self.zipapp: + exe = "python" + args = (self.zipapp,) + args + elif use_module: exe = "python" args = ("-m", "pip") + args else: exe = "pip" - return self.run(exe, *args, **kwargs) + return self.run(exe, *(os.fspath(a) for a in args), **kwargs) - def pip_install_local(self, *args, **kwargs): + def pip_install_local( + self, + *args: StrPath, + **kwargs: Any, + ) -> TestPipResult: return self.pip( "install", "--no-index", "--find-links", - path_to_url(os.path.join(DATA_DIR, "packages")), + pathlib.Path(DATA_DIR, "packages").as_uri(), *args, **kwargs, ) - def easy_install(self, *args, **kwargs): + def easy_install(self, *args: str, **kwargs: Any) -> TestPipResult: args = ("-m", "easy_install") + args return self.run("python", *args, **kwargs) - def assert_installed(self, **kwargs): + def assert_installed(self, **kwargs: str) -> None: ret = self.pip("list", "--format=json") installed = set( (canonicalize_name(val["name"]), val["version"]) @@ -679,7 +745,7 @@ class PipTestEnvironment(TestFileEnvironment): expected = set((canonicalize_name(k), v) for k, v in kwargs.items()) assert expected <= installed, "{!r} not all in {!r}".format(expected, installed) - def assert_not_installed(self, *args): + def assert_not_installed(self, *args: str) -> None: ret = self.pip("list", "--format=json") installed = set( canonicalize_name(val["name"]) for val in json.loads(ret.stdout) @@ -695,7 +761,9 @@ class PipTestEnvironment(TestFileEnvironment): # FIXME ScriptTest does something similar, but only within a single # ProcResult; this generalizes it so states can be compared across # multiple commands. Maybe should be rolled into ScriptTest? -def diff_states(start, end, ignore=None): +def diff_states( + start: _FilesState, end: _FilesState, ignore: Iterable[StrPath] = () +) -> Dict[str, _FilesState]: """ Differences two "filesystem states" as represented by dictionaries of FoundFile and FoundDir objects. @@ -719,9 +787,9 @@ def diff_states(start, end, ignore=None): size are considered. """ - ignore = ignore or [] - def prefix_match(path, prefix): + def prefix_match(path: str, prefix_path: StrPath) -> bool: + prefix = os.fspath(prefix_path) if path == prefix: return True prefix = prefix.rstrip(os.path.sep) + os.path.sep @@ -740,7 +808,11 @@ def diff_states(start, end, ignore=None): return dict(deleted=deleted, created=created, updated=updated) -def assert_all_changes(start_state, end_state, expected_changes): +def assert_all_changes( + start_state: Union[_FilesState, TestPipResult], + end_state: Union[_FilesState, TestPipResult], + expected_changes: List[StrPath], +) -> Dict[str, _FilesState]: """ Fails if anything changed that isn't listed in the expected_changes. @@ -761,6 +833,8 @@ def assert_all_changes(start_state, end_state, expected_changes): start_files = start_state.files_before if isinstance(end_state, TestPipResult): end_files = end_state.files_after + start_files = cast(_FilesState, start_files) + end_files = cast(_FilesState, end_files) diff = diff_states(start_files, end_files, ignore=expected_changes) if list(diff.values()) != [{}, {}, {}]: @@ -773,7 +847,11 @@ def assert_all_changes(start_state, end_state, expected_changes): return diff -def _create_main_file(dir_path, name=None, output=None): +def _create_main_file( + dir_path: pathlib.Path, + name: Optional[str] = None, + output: Optional[str] = None, +) -> None: """ Create a module with a main() function that prints the given output. """ @@ -792,12 +870,12 @@ def _create_main_file(dir_path, name=None, output=None): def _git_commit( - env_or_script, - repo_dir, - message=None, - allow_empty=False, - stage_modified=False, -): + env_or_script: PipTestEnvironment, + repo_dir: StrPath, + message: Optional[str] = None, + allow_empty: bool = False, + stage_modified: bool = False, +) -> None: """ Run git-commit. @@ -829,57 +907,67 @@ def _git_commit( env_or_script.run(*new_args, cwd=repo_dir) -def _vcs_add(script, version_pkg_path, vcs="git"): +def _vcs_add( + location: pathlib.Path, + version_pkg_path: pathlib.Path, + vcs: str = "git", +) -> pathlib.Path: if vcs == "git": - script.run("git", "init", cwd=version_pkg_path) - script.run("git", "add", ".", cwd=version_pkg_path) - _git_commit(script, version_pkg_path, message="initial version") + subprocess.check_call(["git", "init"], cwd=os.fspath(version_pkg_path)) + subprocess.check_call(["git", "add", "."], cwd=os.fspath(version_pkg_path)) + subprocess.check_call( + ["git", "commit", "-m", "initial version"], cwd=os.fspath(version_pkg_path) + ) elif vcs == "hg": - script.run("hg", "init", cwd=version_pkg_path) - script.run("hg", "add", ".", cwd=version_pkg_path) - script.run( - "hg", - "commit", - "-q", - "--user", - "pip <distutils-sig@python.org>", - "-m", - "initial version", - cwd=version_pkg_path, + subprocess.check_call(["hg", "init"], cwd=os.fspath(version_pkg_path)) + subprocess.check_call(["hg", "add", "."], cwd=os.fspath(version_pkg_path)) + subprocess.check_call( + [ + "hg", + "commit", + "-q", + "--user", + "pip <distutils-sig@python.org>", + "-m", + "initial version", + ], + cwd=os.fspath(version_pkg_path), ) elif vcs == "svn": - repo_url = _create_svn_repo(script, version_pkg_path) - script.run( - "svn", "checkout", repo_url, "pip-test-package", cwd=script.scratch_path + repo_url = _create_svn_repo(location, version_pkg_path) + subprocess.check_call( + ["svn", "checkout", repo_url, "pip-test-package"], cwd=os.fspath(location) ) - checkout_path = script.scratch_path / "pip-test-package" - - # svn internally stores windows drives as uppercase; we'll match that. - checkout_path = checkout_path.replace("c:", "C:") + checkout_path = location / "pip-test-package" version_pkg_path = checkout_path elif vcs == "bazaar": - script.run("bzr", "init", cwd=version_pkg_path) - script.run("bzr", "add", ".", cwd=version_pkg_path) - script.run( - "bzr", "whoami", "pip <distutils-sig@python.org>", cwd=version_pkg_path + subprocess.check_call(["bzr", "init"], cwd=os.fspath(version_pkg_path)) + subprocess.check_call(["bzr", "add", "."], cwd=os.fspath(version_pkg_path)) + subprocess.check_call( + ["bzr", "whoami", "pip <distutils-sig@python.org>"], + cwd=os.fspath(version_pkg_path), ) - script.run( - "bzr", - "commit", - "-q", - "--author", - "pip <distutils-sig@python.org>", - "-m", - "initial version", - cwd=version_pkg_path, + subprocess.check_call( + [ + "bzr", + "commit", + "-q", + "--author", + "pip <distutils-sig@python.org>", + "-m", + "initial version", + ], + cwd=os.fspath(version_pkg_path), ) else: raise ValueError(f"Unknown vcs: {vcs}") return version_pkg_path -def _create_test_package_with_subdirectory(script, subdirectory): +def _create_test_package_with_subdirectory( + script: PipTestEnvironment, subdirectory: str +) -> pathlib.Path: script.scratch_path.joinpath("version_pkg").mkdir() version_pkg_path = script.scratch_path / "version_pkg" _create_main_file(version_pkg_path, name="version_pkg", output="0.1") @@ -926,9 +1014,11 @@ def _create_test_package_with_subdirectory(script, subdirectory): return version_pkg_path -def _create_test_package_with_srcdir(script, name="version_pkg", vcs="git"): - script.scratch_path.joinpath(name).mkdir() - version_pkg_path = script.scratch_path / name +def _create_test_package_with_srcdir( + dir_path: pathlib.Path, name: str = "version_pkg", vcs: str = "git" +) -> pathlib.Path: + dir_path.joinpath(name).mkdir() + version_pkg_path = dir_path / name subdir_path = version_pkg_path.joinpath("subdir") subdir_path.mkdir() src_path = subdir_path.joinpath("src") @@ -951,12 +1041,14 @@ def _create_test_package_with_srcdir(script, name="version_pkg", vcs="git"): ) ) ) - return _vcs_add(script, version_pkg_path, vcs) + return _vcs_add(dir_path, version_pkg_path, vcs) -def _create_test_package(script, name="version_pkg", vcs="git"): - script.scratch_path.joinpath(name).mkdir() - version_pkg_path = script.scratch_path / name +def _create_test_package( + dir_path: pathlib.Path, name: str = "version_pkg", vcs: str = "git" +) -> pathlib.Path: + dir_path.joinpath(name).mkdir() + version_pkg_path = dir_path / name _create_main_file(version_pkg_path, name=name, output="0.1") version_pkg_path.joinpath("setup.py").write_text( textwrap.dedent( @@ -974,25 +1066,31 @@ def _create_test_package(script, name="version_pkg", vcs="git"): ) ) ) - return _vcs_add(script, version_pkg_path, vcs) - - -def _create_svn_repo(script, version_pkg_path): - repo_url = path_to_url(script.scratch_path / "pip-test-package-repo" / "trunk") - script.run("svnadmin", "create", "pip-test-package-repo", cwd=script.scratch_path) - script.run( - "svn", - "import", - version_pkg_path, - repo_url, - "-m", - "Initial import of pip-test-package", - cwd=script.scratch_path, + return _vcs_add(dir_path, version_pkg_path, vcs) + + +def _create_svn_repo(repo_path: pathlib.Path, version_pkg_path: StrPath) -> str: + repo_url = repo_path.joinpath("pip-test-package-repo", "trunk").as_uri() + subprocess.check_call( + "svnadmin create pip-test-package-repo".split(), cwd=repo_path + ) + subprocess.check_call( + [ + "svn", + "import", + os.fspath(version_pkg_path), + repo_url, + "-m", + "Initial import of pip-test-package", + ], + cwd=os.fspath(repo_path), ) return repo_url -def _change_test_package_version(script, version_pkg_path): +def _change_test_package_version( + script: PipTestEnvironment, version_pkg_path: pathlib.Path +) -> None: _create_main_file( version_pkg_path, name="version_pkg", output="some different version" ) @@ -1000,21 +1098,8 @@ def _change_test_package_version(script, version_pkg_path): _git_commit(script, version_pkg_path, message="messed version", stage_modified=True) -def assert_raises_regexp(exception, reg, run, *args, **kwargs): - """Like assertRaisesRegexp in unittest""" - __tracebackhide__ = True - - try: - run(*args, **kwargs) - assert False, f"{exception} should have been thrown" - except exception: - e = sys.exc_info()[1] - p = re.compile(reg) - assert p.search(str(e)), str(e) - - @contextmanager -def requirements_file(contents, tmpdir): +def requirements_file(contents: str, tmpdir: pathlib.Path) -> Iterator[pathlib.Path]: """Return a Path to a requirements file of given contents. As long as the context manager is open, the requirements file will exist. @@ -1028,7 +1113,9 @@ def requirements_file(contents, tmpdir): path.unlink() -def create_test_package_with_setup(script, **setup_kwargs): +def create_test_package_with_setup( + script: PipTestEnvironment, **setup_kwargs: Any +) -> pathlib.Path: assert "name" in setup_kwargs, setup_kwargs pkg_path = script.scratch_path / setup_kwargs["name"] pkg_path.mkdir() @@ -1044,17 +1131,15 @@ def create_test_package_with_setup(script, **setup_kwargs): return pkg_path -def urlsafe_b64encode_nopad(data): - # type: (bytes) -> str +def urlsafe_b64encode_nopad(data: bytes) -> str: return urlsafe_b64encode(data).rstrip(b"=").decode("ascii") -def create_really_basic_wheel(name, version): - # type: (str, str) -> bytes - def digest(contents): +def create_really_basic_wheel(name: str, version: str) -> bytes: + def digest(contents: bytes) -> str: return "sha256={}".format(urlsafe_b64encode_nopad(sha256(contents).digest())) - def add_file(path, text): + def add_file(path: str, text: str) -> None: contents = text.encode("utf-8") z.writestr(path, contents) records.append((path, digest(contents), str(len(contents)))) @@ -1083,14 +1168,14 @@ def create_really_basic_wheel(name, version): def create_basic_wheel_for_package( - script, - name, - version, - depends=None, - extras=None, - requires_python=None, - extra_files=None, -): + script: PipTestEnvironment, + name: str, + version: str, + depends: Optional[List[str]] = None, + extras: Optional[Dict[str, List[str]]] = None, + requires_python: Optional[str] = None, + extra_files: Optional[Dict[str, Union[bytes, str]]] = None, +) -> pathlib.Path: if depends is None: depends = [] if extras is None: @@ -1120,7 +1205,7 @@ def create_basic_wheel_for_package( for package in packages ] - metadata_updates = { + metadata_updates: Dict[str, Any] = { "Provides-Extra": list(extras), "Requires-Dist": requires_dist, } @@ -1142,22 +1227,49 @@ def create_basic_wheel_for_package( return archive_path -def create_basic_sdist_for_package(script, name, version, extra_files=None): +def create_basic_sdist_for_package( + script: PipTestEnvironment, + name: str, + version: str, + extra_files: Optional[Dict[str, str]] = None, + *, + fails_egg_info: bool = False, + fails_bdist_wheel: bool = False, + depends: Optional[List[str]] = None, + setup_py_prelude: str = "", +) -> pathlib.Path: files = { - "setup.py": """ + "setup.py": textwrap.dedent( + """\ + import sys from setuptools import find_packages, setup - setup(name={name!r}, version={version!r}) - """, + + {setup_py_prelude} + + fails_bdist_wheel = {fails_bdist_wheel!r} + fails_egg_info = {fails_egg_info!r} + + if fails_egg_info and "egg_info" in sys.argv: + raise Exception("Simulated failure for generating metadata.") + + if fails_bdist_wheel and "bdist_wheel" in sys.argv: + raise Exception("Simulated failure for building a wheel.") + + setup(name={name!r}, version={version!r}, + install_requires={depends!r}) + """ + ).format( + name=name, + version=version, + depends=depends or [], + setup_py_prelude=setup_py_prelude, + fails_bdist_wheel=fails_bdist_wheel, + fails_egg_info=fails_egg_info, + ), } # Some useful shorthands - archive_name = "{name}-{version}.tar.gz".format(name=name, version=version) - - # Replace key-values with formatted values - for key, value in list(files.items()): - del files[key] - key = key.format(name=name) - files[key] = textwrap.dedent(value).format(name=name, version=version).strip() + archive_name = f"{name}-{version}.tar.gz" # Add new files after formatting if extra_files: @@ -1170,7 +1282,7 @@ def create_basic_sdist_for_package(script, name, version, extra_files=None): retval = script.scratch_path / archive_name generated = shutil.make_archive( - retval, + os.fspath(retval), "gztar", root_dir=script.temp_path, base_dir=os.curdir, @@ -1183,8 +1295,8 @@ def create_basic_sdist_for_package(script, name, version, extra_files=None): return retval -def need_executable(name, check_cmd): - def wrapper(fn): +def need_executable(name: str, check_cmd: Tuple[str, ...]) -> Callable[[_Test], _Test]: + def wrapper(fn: _Test) -> _Test: try: subprocess.check_output(check_cmd) except (OSError, subprocess.CalledProcessError): @@ -1194,7 +1306,7 @@ def need_executable(name, check_cmd): return wrapper -def is_bzr_installed(): +def is_bzr_installed() -> bool: try: subprocess.check_output(("bzr", "version", "--short")) except OSError: @@ -1202,7 +1314,7 @@ def is_bzr_installed(): return True -def is_svn_installed(): +def is_svn_installed() -> bool: try: subprocess.check_output(("svn", "--version")) except OSError: @@ -1210,11 +1322,11 @@ def is_svn_installed(): return True -def need_bzr(fn): +def need_bzr(fn: _Test) -> _Test: return pytest.mark.bzr(need_executable("Bazaar", ("bzr", "version", "--short"))(fn)) -def need_svn(fn): +def need_svn(fn: _Test) -> _Test: return pytest.mark.svn( need_executable("Subversion", ("svn", "--version"))( need_executable("Subversion Admin", ("svnadmin", "--version"))(fn) @@ -1222,5 +1334,5 @@ def need_svn(fn): ) -def need_mercurial(fn): +def need_mercurial(fn: _Test) -> _Test: return pytest.mark.mercurial(need_executable("Mercurial", ("hg", "version"))(fn)) |