Skip to content

Commit

Permalink
Fix issues for real.
Browse files Browse the repository at this point in the history
  • Loading branch information
jsirois committed Oct 22, 2024
1 parent 74a3708 commit dc6a570
Show file tree
Hide file tree
Showing 26 changed files with 453 additions and 67 deletions.
8 changes: 8 additions & 0 deletions pex/pip/installation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)):
Expand Down
20 changes: 13 additions & 7 deletions pex/third_party/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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():
Expand All @@ -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():
Expand Down
2 changes: 1 addition & 1 deletion testing/bin/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand Down
8 changes: 8 additions & 0 deletions testing/build_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions testing/pytest/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Copyright 2024 Pex project contributors.
# Licensed under the Apache License, Version 2.0 (see LICENSE).
File renamed without changes.
120 changes: 120 additions & 0 deletions testing/pytest/tmp.py
Original file line number Diff line number Diff line change
@@ -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)
72 changes: 72 additions & 0 deletions testing/pytest/track_status_hook.py
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions testing/pytest/track_status_hook_py2.py
Original file line number Diff line number Diff line change
@@ -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
33 changes: 33 additions & 0 deletions testing/pytest/track_status_hook_py3.py
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion tests/build_system/test_pep_517.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit dc6a570

Please sign in to comment.