diff --git a/pex/pip/installation.py b/pex/pip/installation.py index ecd147309..709c39276 100644 --- a/pex/pip/installation.py +++ b/pex/pip/installation.py @@ -30,6 +30,7 @@ from pex.tracer import TRACER from pex.typing import TYPE_CHECKING from pex.util import CacheHelper +from pex.variables import ENV from pex.venv.virtualenv import InstallationChoice, Virtualenv if TYPE_CHECKING: @@ -337,6 +338,13 @@ class PipInstallation(object): extra_requirements = attr.ib() # type: Tuple[Requirement, ...] use_system_time = attr.ib() # type: bool + # We use this to isolate installations by PEX_ROOT for tests. In production, there will only + # ever be 1 PEX_ROOT per Pex process lifetime. + pex_root = attr.ib(init=False) # type: str + + def __attrs_post_init__(self): + object.__setattr__(self, "pex_root", ENV.PEX_ROOT) + def check_python_applies(self): # type: () -> None if not self.version.requires_python_applies(LocalInterpreter.create(self.interpreter)): diff --git a/pex/third_party/__init__.py b/pex/third_party/__init__.py index ff9fe3baa..3c1bea0ea 100644 --- a/pex/third_party/__init__.py +++ b/pex/third_party/__init__.py @@ -22,7 +22,7 @@ from pex.util import CacheHelper if TYPE_CHECKING: - from typing import Container, Iterable, Iterator, List, Optional, Tuple + from typing import Container, Dict, Iterable, Iterator, List, Optional, Tuple from pex.interpreter import PythonInterpreter @@ -403,7 +403,9 @@ class IsolationResult(namedtuple("IsolatedPex", ["pex_hash", "chroot_path"])): """The result of isolating the current pex distribution to a filesystem chroot.""" -_ISOLATED = None # type: Optional[IsolationResult] +# We use this to isolate Pex installations by PEX_ROOT for tests. In production, there will only +# ever be 1 PEX_ROOT per Pex process lifetime. +_ISOLATED = {} # type: Dict[str, Optional[IsolationResult]] def _isolate_pex_from_dir( @@ -464,8 +466,11 @@ def isolated(interpreter=None): :return: An isolation result. """ - global _ISOLATED - if _ISOLATED is None: + from pex.variables import ENV + + pex_root = ENV.PEX_ROOT + isolation_result = _ISOLATED.get(pex_root) + if isolation_result is None: from pex import layout, vendor from pex.atomic_directory import atomic_directory from pex.cache.dirs import CacheDir @@ -509,7 +514,7 @@ def isolated(interpreter=None): pex_zip_paths = (zip_path, pex_package_relpath) pex_hash = CacheHelper.zip_hash(zip_path, relpath=pex_package_relpath) - isolated_dir = CacheDir.ISOLATED.path(pex_hash) + isolated_dir = CacheDir.ISOLATED.path(pex_hash, pex_root=pex_root) with _tracer().timed("Isolating pex"): with atomic_directory(isolated_dir) as chroot: if not chroot.is_finalized(): @@ -529,8 +534,9 @@ def isolated(interpreter=None): exclude_files=vendor_lockfiles, ) - _ISOLATED = IsolationResult(pex_hash=pex_hash, chroot_path=isolated_dir) - return _ISOLATED + isolation_result = IsolationResult(pex_hash=pex_hash, chroot_path=isolated_dir) + _ISOLATED[pex_root] = isolation_result + return isolation_result def uninstall(): diff --git a/testing/bin/run_tests.py b/testing/bin/run_tests.py index d6a41852f..d76262074 100755 --- a/testing/bin/run_tests.py +++ b/testing/bin/run_tests.py @@ -153,7 +153,7 @@ def main(): args = [sys.executable, "-m", "pytest", "-n", "auto"] if options.it: - args.extend(["tests/integration", "-p", "testing.pytest_shard"]) + args.extend(["tests/integration", "-p", "testing.pytest.shard"]) else: args.extend(["tests", "--ignore", "tests/integration"]) args.extend(passthrough_args or ["-vvs"]) diff --git a/testing/build_system.py b/testing/build_system.py index cdf7477e0..4277dcc54 100644 --- a/testing/build_system.py +++ b/testing/build_system.py @@ -5,6 +5,9 @@ import glob import os +import sys + +import pytest from pex.build_system.pep_517 import build_sdist from pex.dist_metadata import Distribution @@ -22,6 +25,11 @@ from typing import Any +hatchling_only_supports_37_and_greater = pytest.mark.skipif( + sys.version_info[:2] <= (3, 7), reason="Our current build system only works under Python>=3.7" +) + + def assert_build_sdist( project_dir, # type: str project_name, # type: str diff --git a/testing/pytest/__init__.py b/testing/pytest/__init__.py new file mode 100644 index 000000000..87fb2ed9a --- /dev/null +++ b/testing/pytest/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2024 Pex project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). diff --git a/testing/pytest_shard.py b/testing/pytest/shard.py similarity index 100% rename from testing/pytest_shard.py rename to testing/pytest/shard.py diff --git a/testing/pytest/tmp.py b/testing/pytest/tmp.py new file mode 100644 index 000000000..67c2610ca --- /dev/null +++ b/testing/pytest/tmp.py @@ -0,0 +1,120 @@ +# Copyright 2024 Pex project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import absolute_import + +import errno +import hashlib +import os.path +import re +import warnings + +import attr + +from pex.common import safe_delete, safe_mkdir, safe_rmtree +from pex.enum import Enum +from pex.typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Optional + +_MAX_ATTEMPTS = 50 + + +class RetentionPolicy(Enum["RetentionPolicy.Value"]): + class Value(Enum.Value): + pass + + ALL = Value("all") + FAILED = Value("failed") + NONE = Value("none") + + +@attr.s(frozen=True) +class Tempdir(object): + path = attr.ib() # type: str + symlink = attr.ib(default=None) # type: Optional[str] + + def join(self, *components): + # type: (*str) -> str + return os.path.join(self.path, *components) + + def safe_remove(self): + # type: () -> None + if self.symlink: + safe_delete(self.symlink) + safe_rmtree(self.path) + + def __str__(self): + # type: () -> str + return self.path + + +@attr.s(frozen=True) +class TempdirFactory(object): + path = attr.ib() # type: str + retention_policy = attr.ib() # type: RetentionPolicy.Value + + def getbasetemp(self): + # type: () -> str + return self.path + + def mktemp( + self, + name, # type: str + request=None, # type: Optional[Any] + ): + # type: (...) -> Tempdir + + long_name = None # type: Optional[str] + name = "{name}-{node}".format(name=name, node=request.node.name) if request else name + normalized_name = re.sub(r"\W", "_", name) + if len(normalized_name) > 30: + # The pytest implementation simply truncates at 30 which leads to collisions and this + # causes issues when tmpdir teardown is active - 1 test with the same 30 character + # prefix in its test name as another test can have its directories torn down out from + # underneath it! Here we ~ensure unique tmpdir names while preserving a filename length + # limit to play well with various file systems. + long_name = normalized_name + + # This is yields a ~1 in a million (5 hex chars at 4 bits a piece -> 2^20) chance of + # collision if the 1st 24 characters of the test name match. + prefix = normalized_name[:24] + fingerprint = hashlib.sha1(normalized_name.encode("utf-8")).hexdigest()[:5] + normalized_name = "{prefix}-{hash}".format(prefix=prefix, hash=fingerprint) + for index in range(_MAX_ATTEMPTS): + tempdir_name = "{name}{index}".format(name=normalized_name, index=index) + tempdir = os.path.join(self.path, tempdir_name) + try: + os.makedirs(tempdir) + symlink = None # type: Optional[str] + if long_name: + symlink = os.path.join(self.path, long_name) + safe_delete(symlink) + os.symlink(tempdir_name, symlink) + return Tempdir(tempdir, symlink=symlink) + except OSError as e: + if e.errno == errno.EEXIST: + continue + raise OSError( + "Could not create numbered dir with prefix {prefix} in {root} after {max} tries".format( + prefix=normalized_name, root=self.path, max=_MAX_ATTEMPTS + ) + ) + + +def tmpdir_factory( + basetemp, # type: str + retention_count, # type: int + retention_policy, # type: RetentionPolicy.Value +): + # type: (...) -> TempdirFactory + + safe_rmtree(basetemp) + if retention_count > 1: + warnings.warn( + "Ignoring temp dir retention count of {count}: only temp dirs from the current run " + "will be retained.".format(count=retention_count) + ) + safe_mkdir(basetemp) + return TempdirFactory(path=basetemp, retention_policy=retention_policy) diff --git a/testing/pytest/track_status_hook.py b/testing/pytest/track_status_hook.py new file mode 100644 index 000000000..289ae5f10 --- /dev/null +++ b/testing/pytest/track_status_hook.py @@ -0,0 +1,72 @@ +# Copyright 2024 Pex project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import absolute_import + +import sys + +from pex.compatibility import PY2 +from pex.typing import TYPE_CHECKING +from testing.pytest.tmp import RetentionPolicy + +if TYPE_CHECKING: + from typing import Dict + + from _pytest.config.argparsing import Parser # type: ignore[import] + from _pytest.fixtures import FixtureRequest # type: ignore[import] + from _pytest.nodes import Item # type: ignore[import] + from _pytest.reports import TestReport # type: ignore[import] + + +_PASSED_STATUS = {} # type: Dict[str, bool] + + +def mark_passed(node): + # type: (Item) -> None + _PASSED_STATUS[node.nodeid] = True + + +def passed(node): + # type: (Item) -> bool + return _PASSED_STATUS.pop(node.nodeid, False) + + +if PY2: + from testing.pytest.track_status_hook_py2 import track_status_hook as _track_status_hook +else: + from testing.pytest.track_status_hook_py3 import track_status_hook as _track_status_hook + +hook = _track_status_hook + + +if sys.version_info[:2] < (3, 7): + + def pytest_addoption(parser): + # type: (Parser) -> None + parser.addini( + "tmp_path_retention_count", + help=( + "How many sessions should we keep the `tmpdir` directories, according to" + "`tmp_path_retention_policy`." + ), + default=3, + ) + + parser.addini( + "tmp_path_retention_policy", + help=( + "Controls which directories created by the `tmpdir` fixture are kept around, based " + "on test outcome. ({values})".format( + values="/".join(map(str, RetentionPolicy.values())) + ) + ), + default="all", + ) + +else: + + def pytest_addoption(parser): + # type: (Parser) -> None + # The `tmp_path_retention_count` and `tmp_path_retention_policy` options are already setup + # under the newer pytests used by our Python>=3.7 test environments. + pass diff --git a/testing/pytest/track_status_hook_py2.py b/testing/pytest/track_status_hook_py2.py new file mode 100644 index 000000000..f84f9e8ae --- /dev/null +++ b/testing/pytest/track_status_hook_py2.py @@ -0,0 +1,30 @@ +# Copyright 2024 Pex project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import absolute_import, print_function + +from _pytest.config import hookimpl # type: ignore[import] + +from pex.typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Generator + + from _pytest.nodes import Item # type: ignore[import] + from pluggy.callers import _Result # type: ignore[import] + + +@hookimpl(hookwrapper=True, tryfirst=True) +def track_status_hook( + item, # type: Item + call, # type: Any +): + # type: (...) -> Generator[None, _Result, None] + + from testing.pytest.track_status_hook import mark_passed + + report = yield + result = report.get_result() + if result.when == "call" and result.passed: + mark_passed(item) + return diff --git a/testing/pytest/track_status_hook_py3.py b/testing/pytest/track_status_hook_py3.py new file mode 100644 index 000000000..aa8db0b82 --- /dev/null +++ b/testing/pytest/track_status_hook_py3.py @@ -0,0 +1,33 @@ +# Copyright 2024 Pex project contributors. +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import absolute_import + +import sys + +from _pytest.config import hookimpl # type: ignore[import] + +from pex.typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Any, Generator + + from _pytest.nodes import Item # type: ignore[import] + from _pytest.reports import TestReport # type: ignore[import] + + +@hookimpl(tryfirst=True, **{"wrapper" if sys.version_info[:2] >= (3, 7) else "hookwrapper": True}) +def track_status_hook( + item, # type: Item + call, # type: Any +): + # type: (...) -> Generator[None, TestReport, TestReport] + + from testing.pytest.track_status_hook import mark_passed + + report = yield + if sys.version_info[:2] < (3, 7): + report = report.get_result() + if report.when == "call" and report.passed: + mark_passed(item) + return report diff --git a/tests/build_system/test_pep_517.py b/tests/build_system/test_pep_517.py index 4c23e2b21..78760c7c9 100644 --- a/tests/build_system/test_pep_517.py +++ b/tests/build_system/test_pep_517.py @@ -11,7 +11,7 @@ from pex.typing import TYPE_CHECKING from pex.version import __version__ from testing import make_project -from testing.build_system import assert_build_sdist +from testing.build_system import assert_build_sdist, hatchling_only_supports_37_and_greater if TYPE_CHECKING: from typing import Any @@ -59,6 +59,7 @@ def test_build_sdist_setup_py(tmpdir): assert_build_sdist(project_dir, "foo", "42", tmpdir) +@hatchling_only_supports_37_and_greater def test_build_sdist_pyproject_toml( tmpdir, # type: Any pex_project_dir, # type: str diff --git a/tests/build_system/test_pep_518.py b/tests/build_system/test_pep_518.py index 502f7b2e6..a0b2971dc 100644 --- a/tests/build_system/test_pep_518.py +++ b/tests/build_system/test_pep_518.py @@ -15,6 +15,7 @@ from pex.typing import TYPE_CHECKING from pex.variables import ENV from pex.venv.virtualenv import Virtualenv +from testing.build_system import hatchling_only_supports_37_and_greater if TYPE_CHECKING: from typing import Any, Optional, Union @@ -60,6 +61,7 @@ def test_load_build_system_pyproject_but_not_for_build(tmpdir): assert load_build_system(project_dir) is None +@hatchling_only_supports_37_and_greater def test_load_build_system_pyproject( tmpdir, # type: Any pex_project_dir, # type: str @@ -80,6 +82,7 @@ def test_load_build_system_pyproject( ) +@hatchling_only_supports_37_and_greater def test_load_build_system_env_strip_issue_1872( tmpdir, # type: Any pex_project_dir, # type: str diff --git a/tests/conftest.py b/tests/conftest.py index b2a9d8aab..d824cbe3d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,12 +3,26 @@ from __future__ import absolute_import +import getpass +import os.path +import tempfile + import pytest +from _pytest.config import hookimpl # type: ignore[import] import testing from pex.interpreter import PythonInterpreter from pex.platforms import Platform +from pex.typing import TYPE_CHECKING from testing import PY27, PY38, PY39, PY310, ensure_python_interpreter +from testing.pytest import tmp, track_status_hook + +if TYPE_CHECKING: + from typing import Iterator + + from _pytest.fixtures import FixtureRequest # type: ignore[import] + from _pytest.nodes import Item # type: ignore[import] + from _pytest.reports import TestReport # type: ignore[import] @pytest.fixture(scope="session") @@ -17,6 +31,57 @@ def pex_project_dir(): return testing.pex_project_dir() +@pytest.fixture(scope="session") +def tmpdir_factory(request): + # type: (FixtureRequest) -> tmp.TempdirFactory + + # We use existing pytest configuration sources and values for tmpdir here to be drop-in + # ~compatible. + + basetemp = request.config.option.basetemp or os.path.join( + tempfile.gettempdir(), "pytest-of-{user}".format(user=getpass.getuser() or "unknown") + ) + + retention_count = int(request.config.getini("tmp_path_retention_count")) + if retention_count < 0: + raise ValueError( + "The `tmp_path_retention_count` value must be >= 0. Given: {count}.".format( + count=retention_count + ) + ) + + retention_policy = tmp.RetentionPolicy.for_value( + request.config.getini("tmp_path_retention_policy") + ) + + return tmp.tmpdir_factory( + basetemp=basetemp, retention_count=retention_count, retention_policy=retention_policy + ) + + +# This exposes the proper hooks for Python 2 or Python 3 as the case may be - the names on the LHS +# are key. +pytest_addoption = track_status_hook.pytest_addoption +pytest_runtest_makereport = track_status_hook.hook + + +@pytest.fixture +def tmpdir( + tmpdir_factory, # type: tmp.TempdirFactory + request, # type: FixtureRequest +): + # type: (...) -> Iterator[tmp.Tempdir] + temp_directory = tmpdir_factory.mktemp(name=request.node.name) + try: + yield temp_directory + finally: + if ( + tmpdir_factory.retention_policy is tmp.RetentionPolicy.FAILED + and track_status_hook.passed(request.node) + ) or tmpdir_factory.retention_policy is tmp.RetentionPolicy.NONE: + temp_directory.safe_remove() + + @pytest.fixture def current_interpreter(): # type: () -> PythonInterpreter diff --git a/tests/integration/cli/commands/test_issue_1413.py b/tests/integration/cli/commands/test_issue_1413.py index 054181783..3f7d44ddf 100644 --- a/tests/integration/cli/commands/test_issue_1413.py +++ b/tests/integration/cli/commands/test_issue_1413.py @@ -17,15 +17,19 @@ from pex.typing import TYPE_CHECKING from testing import make_env, run_pex_command from testing.cli import run_pex3 +from testing.pytest.tmp import Tempdir, TempdirFactory if TYPE_CHECKING: from typing import Any @pytest.fixture(scope="module") -def td(tmpdir_factory): - # type: (Any) -> Any - return tmpdir_factory.mktemp("td") +def td( + tmpdir_factory, # type: TempdirFactory + request, # type: Any +): + # type: (...) -> Tempdir + return tmpdir_factory.mktemp("td", request=request) @pytest.fixture(scope="module") diff --git a/tests/integration/cli/commands/test_lock_resolve_auth.py b/tests/integration/cli/commands/test_lock_resolve_auth.py index a1bd7bf15..6160a5fd3 100644 --- a/tests/integration/cli/commands/test_lock_resolve_auth.py +++ b/tests/integration/cli/commands/test_lock_resolve_auth.py @@ -16,6 +16,7 @@ from pex.typing import TYPE_CHECKING from testing import IntegResults, make_env, run_pex_command from testing.cli import run_pex3 +from testing.pytest.tmp import TempdirFactory if TYPE_CHECKING: from typing import Any, Iterator @@ -72,9 +73,12 @@ def do_GET(self): @pytest.fixture(scope="module") -def ansicolors_find_links_directory(tmpdir_factory): - # type: (Any) -> str - find_links = str(tmpdir_factory.mktemp("find_links")) +def ansicolors_find_links_directory( + tmpdir_factory, # type: TempdirFactory + request, # type: Any +): + # type: (...) -> str + find_links = str(tmpdir_factory.mktemp("find_links", request=request)) run_pex_command( args=[ "ansicolors==1.1.8", diff --git a/tests/integration/cli/commands/test_lock_sync.py b/tests/integration/cli/commands/test_lock_sync.py index 5bc5d8665..6c6eb4d65 100644 --- a/tests/integration/cli/commands/test_lock_sync.py +++ b/tests/integration/cli/commands/test_lock_sync.py @@ -1034,7 +1034,7 @@ def test_sync_venv_run_retain_pip_preinstalled( ): # type: (...) -> None - venv_dir = os.path.join(str(tmpdir), "venv-pip-preinstalled") + venv_dir = os.path.join(str(tmpdir), "venv") venv = Virtualenv.create(venv_dir, install_pip=InstallationChoice.YES) pip = find_distribution("pip", search_path=venv.sys_path) assert pip is not None @@ -1079,7 +1079,7 @@ def test_sync_venv_run_retain_pip_no_pip_preinstalled( ): # type: (...) -> None - venv_dir = os.path.join(str(tmpdir), "venv-no-pip-preinstalled") + venv_dir = os.path.join(str(tmpdir), "venv") lock = os.path.join(str(tmpdir), "lock.json") run_sync( *( diff --git a/tests/integration/cli/commands/test_venv_create.py b/tests/integration/cli/commands/test_venv_create.py index e79268dc1..4d7e76e27 100644 --- a/tests/integration/cli/commands/test_venv_create.py +++ b/tests/integration/cli/commands/test_venv_create.py @@ -27,16 +27,20 @@ from pex.venv.virtualenv import Virtualenv from testing import IS_MAC, PY39, PY310, ensure_python_interpreter, make_env, run_pex_command from testing.cli import run_pex3 +from testing.pytest.tmp import Tempdir, TempdirFactory if TYPE_CHECKING: from typing import Any @pytest.fixture(scope="module") -def td(tmpdir_factory): - # type: (Any) -> Any +def td( + tmpdir_factory, # type: TempdirFactory + request, # type: Any +): + # type: (...) -> Tempdir - return tmpdir_factory.mktemp("td") + return tmpdir_factory.mktemp("td", request=request) @pytest.fixture(scope="module") diff --git a/tests/integration/test_excludes.py b/tests/integration/test_excludes.py index 80064be19..d0c480f1a 100644 --- a/tests/integration/test_excludes.py +++ b/tests/integration/test_excludes.py @@ -31,6 +31,7 @@ from testing import PY_VER, data, make_env, run_pex_command from testing.cli import run_pex3 from testing.lock import extract_lock_option_args, index_lock_artifacts +from testing.pytest.tmp import TempdirFactory if TYPE_CHECKING: from typing import Any @@ -84,11 +85,15 @@ def requests_certifi_excluded_pex(tmpdir): @pytest.fixture(scope="module") -def certifi_venv(tmpdir_factory): - # type: (Any) -> Virtualenv +def certifi_venv( + tmpdir_factory, # type: TempdirFactory + request, # type: Any +): + # type: (...) -> Virtualenv venv = Virtualenv.create( - venv_dir=str(tmpdir_factory.mktemp("venv")), install_pip=InstallationChoice.YES + venv_dir=str(tmpdir_factory.mktemp("venv", request=request)), + install_pip=InstallationChoice.YES, ) pip = venv.bin_path("pip") diff --git a/tests/integration/test_issue_1656.py b/tests/integration/test_issue_1656.py index 2d285c1e7..5524788ec 100644 --- a/tests/integration/test_issue_1656.py +++ b/tests/integration/test_issue_1656.py @@ -11,6 +11,7 @@ from pex.pex_info import PexInfo from pex.typing import TYPE_CHECKING from testing import IS_PYPY, PY_VER, make_env, run_pex_command +from testing.pytest.tmp import TempdirFactory if TYPE_CHECKING: from typing import Any, List, Text @@ -28,13 +29,16 @@ def create_pex_pex( @pytest.fixture(scope="module") -def old_pex(tmpdir_factory): - # type: (Any) -> str +def old_pex( + tmpdir_factory, # type: TempdirFactory + request, # type: Any +): + # type: (...) -> str # This was the first version to support Python 3.10, and it did not rely upon the RECORD file to # build venvs. pex_version = "2.1.55" - pex_file = os.path.join(str(tmpdir_factory.mktemp(pex_version)), "tool.pex") + pex_file = tmpdir_factory.mktemp(pex_version, request=request).join("tool.pex") create_pex_pex(pex_version=pex_version, pex_file=pex_file) return pex_file diff --git a/tests/integration/test_lock_resolver.py b/tests/integration/test_lock_resolver.py index dfe51ad74..210036ef3 100644 --- a/tests/integration/test_lock_resolver.py +++ b/tests/integration/test_lock_resolver.py @@ -25,6 +25,7 @@ from testing import IS_PYPY, PY_VER, built_wheel, make_env, run_pex_command from testing.cli import run_pex3 from testing.lock import index_lock_artifacts +from testing.pytest.tmp import TempdirFactory if TYPE_CHECKING: from typing import Any, Mapping, Tuple @@ -51,10 +52,13 @@ def index_pex_distributions(pex_file): @pytest.fixture(scope="module") -def requests_lock_strict(tmpdir_factory): - # type: (Any) -> str +def requests_lock_strict( + tmpdir_factory, # type: TempdirFactory + request, # type: Any +): + # type: (...) -> str - lock = os.path.join(str(tmpdir_factory.mktemp("locks")), "requests.lock") + lock = tmpdir_factory.mktemp("locks", request=request).join("requests.lock") # N.B.: requests 2.25.1 is known to work with all versions of Python Pex supports. run_pex3("lock", "create", "--style", "strict", "requests==2.25.1", "-o", lock).assert_success() return lock @@ -140,12 +144,13 @@ class LockAndRepo(object): @pytest.fixture(scope="module") def requests_tool_pex( - tmpdir_factory, # type: Any + tmpdir_factory, # type: TempdirFactory + request, # type: Any requests_lock_strict, # type: str ): # type: (...) -> str - requests_pex = os.path.join(str(tmpdir_factory.mktemp("tool")), "requests.pex") + requests_pex = tmpdir_factory.mktemp("tool", request=request).join("requests.pex") run_pex_command( args=["--lock", requests_lock_strict, "--include-tools", "requests", "-o", requests_pex] ).assert_success() @@ -154,17 +159,18 @@ def requests_tool_pex( @pytest.fixture def requests_lock_findlinks( - tmpdir_factory, # type: Any + tmpdir_factory, # type: TempdirFactory + request, # type: Any requests_tool_pex, # type: str ): # type: (...) -> LockAndRepo - find_links_repo = str(tmpdir_factory.mktemp("repo")) + find_links_repo = str(tmpdir_factory.mktemp("repo", request=request)) subprocess.check_call( args=[requests_tool_pex, "repository", "extract", "-f", find_links_repo], env=make_env(PEX_TOOLS=1), ) - lock = os.path.join(str(tmpdir_factory.mktemp("locks")), "requests-find-links.lock") + lock = tmpdir_factory.mktemp("locks", request=request).join("requests-find-links.lock") run_pex3( "lock", "create", @@ -275,10 +281,13 @@ def test_unavailable_artifacts( @pytest.fixture(scope="module") -def requests_lock_universal(tmpdir_factory): - # type: (Any) -> str +def requests_lock_universal( + tmpdir_factory, # type: TempdirFactory + request, # type: Any +): + # type: (...) -> str - lock = os.path.join(str(tmpdir_factory.mktemp("locks")), "requests-universal.lock") + lock = tmpdir_factory.mktemp("locks", request=request).join("requests-universal.lock") run_pex3( "lock", "create", "--style", "universal", "requests[security]==2.25.1", "-o", lock ).assert_success() diff --git a/tests/integration/test_shebang_length_limit.py b/tests/integration/test_shebang_length_limit.py index 593115ba5..363ae5c06 100644 --- a/tests/integration/test_shebang_length_limit.py +++ b/tests/integration/test_shebang_length_limit.py @@ -18,6 +18,7 @@ from pex.typing import TYPE_CHECKING from testing import IS_PYPY, make_project, run_pex_command from testing.cli import run_pex3 +from testing.pytest.tmp import TempdirFactory if TYPE_CHECKING: from typing import Any, Callable, List @@ -52,12 +53,15 @@ def find_max_length( @pytest.fixture(scope="module") -def file_path_length_limit(tmpdir_factory): - # type: (Any) -> int +def file_path_length_limit( + tmpdir_factory, # type: TempdirFactory + request, # type: Any +): + # type: (...) -> int def file_path_too_long(length): # type: (int) -> bool - path = str(tmpdir_factory.mktemp("td")) + path = str(tmpdir_factory.mktemp("td", request=request)) while len(path) < length - len(os.path.join("directory", "x")): path = os.path.join(path, "directory") try: @@ -85,14 +89,15 @@ def file_path_too_long(length): @pytest.fixture(scope="module") def shebang_length_limit( - tmpdir_factory, # type: Any + tmpdir_factory, # type: TempdirFactory + request, # type: Any file_path_length_limit, # type: int ): # type: (...) -> int def shebang_too_long(length): # type: (int) -> bool - path = str(tmpdir_factory.mktemp("td")) + path = str(tmpdir_factory.mktemp("td", request=request)) while len(path) < length - len("#!\n" + os.path.join("directory", "x")): path = os.path.join(path, "directory") try: diff --git a/tests/integration/tools/commands/test_issue_2105.py b/tests/integration/tools/commands/test_issue_2105.py index 29caf7583..cc70e360a 100644 --- a/tests/integration/tools/commands/test_issue_2105.py +++ b/tests/integration/tools/commands/test_issue_2105.py @@ -14,15 +14,19 @@ from pex.typing import TYPE_CHECKING from pex.venv.virtualenv import InstallationChoice, Virtualenv from testing import make_env, run_pex_command +from testing.pytest.tmp import Tempdir, TempdirFactory if TYPE_CHECKING: from typing import Any, Iterable, Mapping, Optional @pytest.fixture(scope="module") -def td(tmpdir_factory): - # type: (Any) -> Any - return tmpdir_factory.mktemp("td") +def td( + tmpdir_factory, # type: TempdirFactory + request, # type: Any +): + # type: (...) -> Tempdir + return tmpdir_factory.mktemp("td", request=request) PIP_PROJECT_NAME = ProjectName("pip") diff --git a/tests/integration/venv_ITs/test_issue_2160.py b/tests/integration/venv_ITs/test_issue_2160.py index e7eb60583..7f3bc433d 100644 --- a/tests/integration/venv_ITs/test_issue_2160.py +++ b/tests/integration/venv_ITs/test_issue_2160.py @@ -20,9 +20,9 @@ @pytest.fixture -def top_level_wheel(tmpdir_factory): +def top_level_wheel(tmpdir): # type: (Any) -> str - top_level_project = str(tmpdir_factory.mktemp("project")) + top_level_project = os.path.join(str(tmpdir), "project") with safe_open(os.path.join(top_level_project, "top_level", "__init__.py"), "w") as fp: fp.write("__path__ = __import__('pkgutil').extend_path(__path__, __name__)") with safe_open(os.path.join(top_level_project, "top_level", "lib.py"), "w") as fp: @@ -53,7 +53,7 @@ def top_level_wheel(tmpdir_factory): ) ) - wheel_dir = str(tmpdir_factory.mktemp("wheels")) + wheel_dir = os.path.join(str(tmpdir), "wheels") return WheelBuilder(source_dir=top_level_project, wheel_dir=wheel_dir).bdist() @@ -69,14 +69,14 @@ def top_level_wheel(tmpdir_factory): "layout", [pytest.param(layout, id=layout.value) for layout in Layout.values()] ) def test_ns_package_split_across_sources_and_deps( - tmpdir_factory, # type: Any + tmpdir, # type: Any top_level_wheel, # type: str style_args, # type: List[str] layout, # type: Layout.Value ): # type: (...) -> None - sources = str(tmpdir_factory.mktemp("sources")) + sources = os.path.join(str(tmpdir), "sources") with safe_open(os.path.join(sources, "top_level", "__init__.py"), "w") as fp: fp.write("__path__ = __import__('pkgutil').extend_path(__path__, __name__)") with safe_open(os.path.join(sources, "top_level", "mymain.py"), "w") as fp: @@ -91,8 +91,8 @@ def test_ns_package_split_across_sources_and_deps( ) ) - pex_root = str(tmpdir_factory.mktemp("pex_root")) - pex = str(tmpdir_factory.mktemp("bin").join("binary.pex")) + pex_root = os.path.join(str(tmpdir), "pex_root") + pex = os.path.join(str(tmpdir), "binary.pex") run_pex_command( args=[ "--pex-root", diff --git a/tests/test_interpreter.py b/tests/test_interpreter.py index c3acc6a44..2a228d5af 100644 --- a/tests/test_interpreter.py +++ b/tests/test_interpreter.py @@ -32,6 +32,7 @@ environment_as, pushd, ) +from testing.pytest.tmp import TempdirFactory try: from unittest.mock import Mock, patch # type: ignore[import] @@ -434,9 +435,12 @@ def test_identify_cwd_isolation_issues_1231(tmpdir): @pytest.fixture(scope="module") -def macos_monterey_interpeter(tmpdir_factory): - # type: (Any) -> str - pythonwrapper = os.path.join(str(tmpdir_factory.mktemp("bin")), "pythonwrapper") +def macos_monterey_interpeter( + tmpdir_factory, # type: TempdirFactory + request, # type: Any +): + # type: (...) -> str + pythonwrapper = tmpdir_factory.mktemp("bin", request=request).join("pythonwrapper") with open(pythonwrapper, "w") as fp: fp.write( dedent( @@ -450,7 +454,7 @@ def macos_monterey_interpeter(tmpdir_factory): ) chmod_plus_x(pythonwrapper) - python = os.path.join(str(tmpdir_factory.mktemp("bin")), "python") + python = tmpdir_factory.mktemp("bin", request=request).join("python") os.symlink(pythonwrapper, python) return python diff --git a/tests/test_pip.py b/tests/test_pip.py index a26132812..0db2a3e75 100644 --- a/tests/test_pip.py +++ b/tests/test_pip.py @@ -30,6 +30,7 @@ from pex.variables import ENV from pex.venv.virtualenv import Virtualenv from testing import IS_LINUX, PY310, ensure_python_interpreter, environment_as +from testing.pytest.tmp import Tempdir if TYPE_CHECKING: from typing import Any, Iterable, Iterator, Optional, Protocol @@ -54,14 +55,13 @@ def current_interpreter(): @pytest.fixture def pex_root(tmpdir): - # type: (Any) -> str - return os.path.join(str(tmpdir), "pex_root") + # type: (Tempdir) -> str + return tmpdir.join("pex_root") @pytest.fixture -def create_pip(tmpdir_factory): - # type: (Any) -> Iterator[CreatePip] - pex_root = str(tmpdir_factory.mktemp("pex_root")) +def create_pip(pex_root): + # type: (str) -> Iterator[CreatePip] def create_pip( interpreter, # type: Optional[PythonInterpreter] diff --git a/tests/test_third_party.py b/tests/test_third_party.py index e138bda4f..2fa5da167 100644 --- a/tests/test_third_party.py +++ b/tests/test_third_party.py @@ -20,12 +20,7 @@ def temporary_pex_root(): # type: () -> Iterator[Tuple[str, Dict[str, str]]] with temporary_dir() as pex_root, ENV.patch(PEX_ROOT=os.path.realpath(pex_root)) as env: - original_isolated = third_party._ISOLATED - try: - third_party._ISOLATED = None - yield os.path.realpath(pex_root), env - finally: - third_party._ISOLATED = original_isolated + yield os.path.realpath(pex_root), env def test_isolated_pex_root():