Skip to content

Commit

Permalink
Migrate tests to use pathlib.Path
Browse files Browse the repository at this point in the history
The pip-specific Path implementation has been removed, and all its
usages replaced by pathlib.Path. The tmpdir and tmpdir_factory fixtures
are also removed, and all usages are replaced by tmp_path and
tmp_path_factory, which use pathlib.Path.

The pip() function now also accepts pathlib.Path so we don't need to put
str() everywhere. Path arguments are coerced with os.fspath() into str.
  • Loading branch information
uranusjr committed Jun 8, 2022
1 parent e58a8a5 commit eaa2503
Show file tree
Hide file tree
Showing 74 changed files with 1,423 additions and 1,481 deletions.
145 changes: 80 additions & 65 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,25 @@
import fnmatch
import io
import os
import pathlib
import re
import shutil
import subprocess
import sys
import time
from contextlib import ExitStack, contextmanager
from typing import TYPE_CHECKING, Callable, Dict, Iterable, Iterator, List, Optional
from typing import (
TYPE_CHECKING,
Callable,
Dict,
Iterable,
Iterator,
List,
Optional,
Union,
)
from unittest.mock import patch

import py.path
import pytest

# Config will be available from the public API in pytest >= 7.0.0:
Expand All @@ -27,7 +36,6 @@
from pip._internal.locations import _USE_SYSCONFIG
from pip._internal.utils.temp_dir import global_tempdir_manager
from tests.lib import DATA_DIR, SRC_DIR, PipTestEnvironment, TestData
from tests.lib.path import Path
from tests.lib.server import MockServer as _MockServer
from tests.lib.server import make_mock_server, server_running
from tests.lib.venv import VirtualEnvironment, VirtualEnvironmentType
Expand All @@ -37,7 +45,7 @@
if TYPE_CHECKING:
from typing import Protocol

from wsgi import WSGIApplication
from _typeshed.wsgi import WSGIApplication
else:
# TODO: Protocol was introduced in Python 3.8. Remove this branch when
# dropping support for Python 3.7.
Expand Down Expand Up @@ -146,42 +154,44 @@ def resolver_variant(request: pytest.FixtureRequest) -> Iterator[str]:


@pytest.fixture(scope="session")
def tmpdir_factory(
request: pytest.FixtureRequest, tmpdir_factory: pytest.TempdirFactory
) -> Iterator[pytest.TempdirFactory]:
def tmp_path_factory(
request: pytest.FixtureRequest, tmp_path_factory: pytest.TempPathFactory
) -> Iterator[pytest.TempPathFactory]:
"""Modified `tmpdir_factory` session fixture
that will automatically cleanup after itself.
"""
yield tmpdir_factory
yield tmp_path_factory
if not request.config.getoption("--keep-tmpdir"):
shutil.rmtree(
tmpdir_factory.getbasetemp(),
tmp_path_factory.getbasetemp(),
ignore_errors=True,
)


@pytest.fixture
def tmpdir(request: pytest.FixtureRequest, tmpdir: py.path.local) -> Iterator[Path]:
def tmp_path(
request: pytest.FixtureRequest,
tmp_path: pathlib.Path,
) -> Iterator[pathlib.Path]:
"""
Return a temporary directory path object which is unique to each test
function invocation, created as a sub directory of the base temporary
directory. The returned object is a ``tests.lib.path.Path`` object.
directory. The returned object is a ``pathlib.Path`` object.
This uses the built-in tmpdir fixture from pytest itself but modified
to return our typical path object instead of py.path.local as well as
deleting the temporary directories at the end of each test case.
This uses the built-in tmp_path fixture from pytest itself, but deletes the
temporary directories at the end of each test case.
"""
assert tmpdir.isdir()
yield Path(str(tmpdir))
assert tmp_path.is_dir()
yield tmp_path
# Clear out the temporary directory after the test has finished using it.
# This should prevent us from needing a multiple gigabyte temporary
# directory while running the tests.
if not request.config.getoption("--keep-tmpdir"):
tmpdir.remove(ignore_errors=True)
shutil.rmtree(tmp_path, ignore_errors=True)


@pytest.fixture(autouse=True)
def isolate(tmpdir: Path, monkeypatch: pytest.MonkeyPatch) -> None:
def isolate(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""
Isolate our tests so that things like global configuration files and the
like do not affect our test results.
Expand All @@ -194,11 +204,11 @@ def isolate(tmpdir: Path, monkeypatch: pytest.MonkeyPatch) -> None:
# as well as user level configuration files.

# Create a directory to use as our home location.
home_dir = os.path.join(str(tmpdir), "home")
home_dir = os.path.join(tmp_path, "home")
os.makedirs(home_dir)

# Create a directory to use as a fake root
fake_root = os.path.join(str(tmpdir), "fake-root")
fake_root = os.path.join(tmp_path, "fake-root")
os.makedirs(fake_root)

if sys.platform == "win32":
Expand Down Expand Up @@ -296,7 +306,7 @@ def scoped_global_tempdir_manager(request: pytest.FixtureRequest) -> Iterator[No


@pytest.fixture(scope="session")
def pip_src(tmpdir_factory: pytest.TempdirFactory) -> Path:
def pip_src(tmp_path_factory: pytest.TempPathFactory) -> pathlib.Path:
def not_code_files_and_folders(path: str, names: List[str]) -> Iterable[str]:
# In the root directory...
if path == SRC_DIR:
Expand All @@ -317,7 +327,7 @@ def not_code_files_and_folders(path: str, names: List[str]) -> Iterable[str]:
ignored.update(fnmatch.filter(names, pattern))
return ignored

pip_src = Path(str(tmpdir_factory.mktemp("pip_src"))).joinpath("pip_src")
pip_src = tmp_path_factory.mktemp("pip_src").joinpath("pip_src")
# Copy over our source tree so that each use is self contained
shutil.copytree(
SRC_DIR,
Expand All @@ -328,11 +338,11 @@ def not_code_files_and_folders(path: str, names: List[str]) -> Iterable[str]:


def _common_wheel_editable_install(
tmpdir_factory: pytest.TempdirFactory, common_wheels: Path, package: str
) -> Path:
tmp_path_factory: pytest.TempPathFactory, common_wheels: pathlib.Path, package: str
) -> pathlib.Path:
wheel_candidates = list(common_wheels.glob(f"{package}-*.whl"))
assert len(wheel_candidates) == 1, wheel_candidates
install_dir = Path(str(tmpdir_factory.mktemp(package))) / "install"
install_dir = tmp_path_factory.mktemp(package) / "install"
Wheel(wheel_candidates[0]).install_as_egg(install_dir)
(install_dir / "EGG-INFO").rename(install_dir / f"{package}.egg-info")
assert compileall.compile_dir(str(install_dir), quiet=1)
Expand All @@ -341,25 +351,28 @@ def _common_wheel_editable_install(

@pytest.fixture(scope="session")
def setuptools_install(
tmpdir_factory: pytest.TempdirFactory, common_wheels: Path
) -> Path:
return _common_wheel_editable_install(tmpdir_factory, common_wheels, "setuptools")
tmp_path_factory: pytest.TempPathFactory, common_wheels: pathlib.Path
) -> pathlib.Path:
return _common_wheel_editable_install(tmp_path_factory, common_wheels, "setuptools")


@pytest.fixture(scope="session")
def wheel_install(tmpdir_factory: pytest.TempdirFactory, common_wheels: Path) -> Path:
return _common_wheel_editable_install(tmpdir_factory, common_wheels, "wheel")
def wheel_install(
tmp_path_factory: pytest.TempPathFactory,
common_wheels: pathlib.Path,
) -> pathlib.Path:
return _common_wheel_editable_install(tmp_path_factory, common_wheels, "wheel")


@pytest.fixture(scope="session")
def coverage_install(
tmpdir_factory: pytest.TempdirFactory, common_wheels: Path
) -> Path:
return _common_wheel_editable_install(tmpdir_factory, common_wheels, "coverage")
tmp_path_factory: pytest.TempPathFactory, common_wheels: pathlib.Path
) -> pathlib.Path:
return _common_wheel_editable_install(tmp_path_factory, common_wheels, "coverage")


def install_egg_link(
venv: VirtualEnvironment, project_name: str, egg_info_dir: Path
venv: VirtualEnvironment, project_name: str, egg_info_dir: pathlib.Path
) -> None:
with open(venv.site / "easy-install.pth", "a") as fp:
fp.write(str(egg_info_dir.resolve()) + "\n")
Expand All @@ -370,10 +383,10 @@ def install_egg_link(
@pytest.fixture(scope="session")
def virtualenv_template(
request: pytest.FixtureRequest,
tmpdir_factory: pytest.TempdirFactory,
pip_src: Path,
setuptools_install: Path,
coverage_install: Path,
tmp_path_factory: pytest.TempPathFactory,
pip_src: pathlib.Path,
setuptools_install: pathlib.Path,
coverage_install: pathlib.Path,
) -> Iterator[VirtualEnvironment]:

venv_type: VirtualEnvironmentType
Expand All @@ -383,12 +396,12 @@ def virtualenv_template(
venv_type = "virtualenv"

# Create the virtual environment
tmpdir = Path(str(tmpdir_factory.mktemp("virtualenv")))
venv = VirtualEnvironment(tmpdir.joinpath("venv_orig"), venv_type=venv_type)
tmp_path = tmp_path_factory.mktemp("virtualenv")
venv = VirtualEnvironment(tmp_path.joinpath("venv_orig"), venv_type=venv_type)

# Install setuptools and pip.
install_egg_link(venv, "setuptools", setuptools_install)
pip_editable = Path(str(tmpdir_factory.mktemp("pip"))) / "pip"
pip_editable = tmp_path_factory.mktemp("pip") / "pip"
shutil.copytree(pip_src, pip_editable, symlinks=True)
# noxfile.py is Python 3 only
assert compileall.compile_dir(
Expand Down Expand Up @@ -420,58 +433,60 @@ def virtualenv_template(

# Rename original virtualenv directory to make sure
# it's not reused by mistake from one of the copies.
venv_template = tmpdir / "venv_template"
venv_template = tmp_path / "venv_template"
venv.move(venv_template)
yield venv


@pytest.fixture(scope="session")
def virtualenv_factory(
virtualenv_template: VirtualEnvironment,
) -> Callable[[Path], VirtualEnvironment]:
def factory(tmpdir: Path) -> VirtualEnvironment:
return VirtualEnvironment(tmpdir, virtualenv_template)
) -> Callable[[pathlib.Path], VirtualEnvironment]:
def factory(tmp_path: pathlib.Path) -> VirtualEnvironment:
return VirtualEnvironment(tmp_path, virtualenv_template)

return factory


@pytest.fixture
def virtualenv(
virtualenv_factory: Callable[[Path], VirtualEnvironment], tmpdir: Path
virtualenv_factory: Callable[[pathlib.Path], VirtualEnvironment],
tmp_path: pathlib.Path,
) -> Iterator[VirtualEnvironment]:
"""
Return a virtual environment which is unique to each test function
invocation created inside of a sub directory of the test function's
temporary directory. The returned object is a
``tests.lib.venv.VirtualEnvironment`` object.
"""
yield virtualenv_factory(tmpdir.joinpath("workspace", "venv"))
yield virtualenv_factory(tmp_path.joinpath("workspace", "venv"))


@pytest.fixture
def with_wheel(virtualenv: VirtualEnvironment, wheel_install: Path) -> None:
def with_wheel(virtualenv: VirtualEnvironment, wheel_install: pathlib.Path) -> None:
install_egg_link(virtualenv, "wheel", wheel_install)


class ScriptFactory(Protocol):
def __call__(
self, tmpdir: Path, virtualenv: Optional[VirtualEnvironment] = None
self, tmp_path: pathlib.Path, virtualenv: Optional[VirtualEnvironment] = None
) -> PipTestEnvironment:
...


@pytest.fixture(scope="session")
def script_factory(
virtualenv_factory: Callable[[Path], VirtualEnvironment], deprecated_python: bool
virtualenv_factory: Callable[[pathlib.Path], VirtualEnvironment],
deprecated_python: bool,
) -> ScriptFactory:
def factory(
tmpdir: Path, virtualenv: Optional[VirtualEnvironment] = None
tmp_path: pathlib.Path, virtualenv: Optional[VirtualEnvironment] = None
) -> PipTestEnvironment:
if virtualenv is None:
virtualenv = virtualenv_factory(tmpdir.joinpath("venv"))
virtualenv = virtualenv_factory(tmp_path.joinpath("venv"))
return PipTestEnvironment(
# The base location for our test environment
tmpdir,
tmp_path,
# Tell the Test Environment where our virtualenv is located
virtualenv=virtualenv,
# Do not ignore hidden files, they need to be checked as well
Expand All @@ -491,33 +506,33 @@ def factory(

@pytest.fixture
def script(
tmpdir: Path,
tmp_path: pathlib.Path,
virtualenv: VirtualEnvironment,
script_factory: Callable[[Path, Optional[VirtualEnvironment]], PipTestEnvironment],
script_factory: ScriptFactory,
) -> PipTestEnvironment:
"""
Return a PipTestEnvironment which is unique to each test function and
will execute all commands inside of the unique virtual environment for this
test function. The returned object is a
``tests.lib.PipTestEnvironment``.
"""
return script_factory(tmpdir.joinpath("workspace"), virtualenv)
return script_factory(tmp_path.joinpath("workspace"), virtualenv)


@pytest.fixture(scope="session")
def common_wheels() -> Path:
def common_wheels() -> pathlib.Path:
"""Provide a directory with latest setuptools and wheel wheels"""
return DATA_DIR.joinpath("common_wheels")


@pytest.fixture(scope="session")
def shared_data(tmpdir_factory: pytest.TempdirFactory) -> TestData:
return TestData.copy(Path(str(tmpdir_factory.mktemp("data"))))
def shared_data(tmp_path_factory: pytest.TempPathFactory) -> TestData:
return TestData.copy(tmp_path_factory.mktemp("data"))


@pytest.fixture
def data(tmpdir: Path) -> TestData:
return TestData.copy(tmpdir.joinpath("data"))
def data(tmp_path: pathlib.Path) -> TestData:
return TestData.copy(tmp_path.joinpath("data"))


class InMemoryPipResult:
Expand All @@ -527,12 +542,12 @@ def __init__(self, returncode: int, stdout: str) -> None:


class InMemoryPip:
def pip(self, *args: str) -> InMemoryPipResult:
def pip(self, *args: Union[str, pathlib.Path]) -> InMemoryPipResult:
orig_stdout = sys.stdout
stdout = io.StringIO()
sys.stdout = stdout
try:
returncode = pip_entry_point(list(args))
returncode = pip_entry_point([os.fspath(a) for a in args])
except SystemExit as e:
returncode = e.code or 0
finally:
Expand All @@ -555,15 +570,15 @@ def deprecated_python() -> bool:


@pytest.fixture(scope="session")
def cert_factory(tmpdir_factory: pytest.TempdirFactory) -> CertFactory:
def cert_factory(tmp_path_factory: pytest.TempPathFactory) -> CertFactory:
# Delay the import requiring cryptography in order to make it possible
# to deselect relevant tests on systems where cryptography cannot
# be installed.
from tests.lib.certs import make_tls_cert, serialize_cert, serialize_key

def factory() -> str:
"""Returns path to cert/key file."""
output_path = Path(str(tmpdir_factory.mktemp("certs"))) / "cert.pem"
output_path = tmp_path_factory.mktemp("certs") / "cert.pem"
# Must be Text on PY2.
cert, key = make_tls_cert("localhost")
with open(str(output_path), "wb") as f:
Expand Down
10 changes: 6 additions & 4 deletions tests/functional/test_broken_stdout.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import os
import pathlib
import subprocess
from typing import List, Tuple

from tests.lib.path import Path

_BROKEN_STDOUT_RETURN_CODE = 120


Expand Down Expand Up @@ -47,11 +46,14 @@ def test_broken_stdout_pipe(deprecated_python: bool) -> None:
assert returncode == _BROKEN_STDOUT_RETURN_CODE


def test_broken_stdout_pipe__log_option(deprecated_python: bool, tmpdir: Path) -> None:
def test_broken_stdout_pipe__log_option(
deprecated_python: bool,
tmp_path: pathlib.Path,
) -> None:
"""
Test a broken pipe to stdout when --log is passed.
"""
log_path = os.path.join(str(tmpdir), "log.txt")
log_path = os.path.join(tmp_path, "log.txt")
stderr, returncode = setup_broken_stdout_test(
["pip", "--log", log_path, "list"],
deprecated_python=deprecated_python,
Expand Down
Loading

0 comments on commit eaa2503

Please sign in to comment.