• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 27118289758

08 Jun 2026 05:40AM UTC coverage: 92.786% (-0.005%) from 92.791%
27118289758

push

github

web-flow
Fix uv support under remote caching/execution. (#23403)

As explained in #23344, a remote cache hit on the process that
creates the uv venv will cause venv creation to be skipped,
and pex will not find that venv locally.

With this change uv venv creation and the pex invocation that
uses it run under the same `Process`, ensuring that they are
always run together or cached together.

This fixes not only the remote caching issue but also a
similar issue with remote execution, when we aren't
guaranteed to run the two processes on the same machine.

13 of 13 new or added lines in 2 files covered. (100.0%)

8 existing lines in 4 files now uncovered.

93104 of 100343 relevant lines covered (92.79%)

3.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.09
/src/python/pants/testutil/python_interpreter_selection.py
1
# Copyright 2019 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
10✔
5

6
import os
10✔
7
import subprocess
10✔
8
from collections.abc import Iterable
10✔
9
from functools import lru_cache
10✔
10
from unittest import skipIf
10✔
11

12
import _pytest.mark.structures
10✔
13
import pytest
10✔
14

15
from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints
10✔
16

17
PY_2 = "2"
10✔
18
PY_3 = "3"
10✔
19

20
PY_27 = "2.7"
10✔
21
PY_36 = "3.6"
10✔
22
PY_37 = "3.7"
10✔
23
PY_38 = "3.8"
10✔
24
PY_39 = "3.9"
10✔
25
PY_310 = "3.10"
10✔
26
PY_311 = "3.11"
10✔
27
PY_312 = "3.12"
10✔
28
PY_313 = "3.13"
10✔
29
PY_314 = "3.14"
10✔
30

31

32
def has_python_version(version):
10✔
33
    """Returns `True` if the current system has the specified version of python.
34

35
    :param string version: A python version string, such as 2.7, 3.
36
    """
37
    # TODO: Tests that skip unless a python interpreter is present often need the path to that
38
    # interpreter, and so end up calling python_interpreter_path again. Find a way to streamline this.
39
    return python_interpreter_path(version) is not None
10✔
40

41

42
@lru_cache
10✔
43
def python_interpreter_path(version):
10✔
44
    """Returns the interpreter path if the current system has the specified version of python.
45

46
    :param string version: A python version string, such as 2.7, 3.
47
    :returns: the normalized path to the interpreter binary if found; otherwise `None`
48
    :rtype: string
49
    """
50
    try:
10✔
51
        command = [f"python{version}", "-c", "import sys; print(sys.executable)"]
10✔
52
        py_path = subprocess.check_output(command).decode().strip()
10✔
53
        return os.path.realpath(py_path)
10✔
UNCOV
54
    except (subprocess.CalledProcessError, FileNotFoundError):
×
UNCOV
55
        return None
×
56

57

58
def skip_unless_all_pythons_present(*versions):
10✔
59
    """A decorator that only runs the decorated test method if all of the specified pythons are
60
    present.
61

62
    :param string *versions: Python version strings, such as 2.7, 3.
63
    """
64
    missing_versions = [v for v in versions if not has_python_version(v)]
9✔
65
    if len(missing_versions) == 1:
9✔
UNCOV
66
        return skipIf(True, f"Could not find python {missing_versions[0]} on system. Skipping.")
×
67
    elif len(missing_versions) > 1:
9✔
68
        return skipIf(
×
69
            True,
70
            "Skipping due to the following missing required pythons: {}".format(
71
                ", ".join(missing_versions)
72
            ),
73
        )
74
    else:
75
        return skipIf(False, "All required pythons present, continuing with test!")
9✔
76

77

78
def skip_unless_python27_present(func):
10✔
79
    """A test skip decorator that only runs a test method if python2.7 is present."""
80
    return skip_unless_all_pythons_present(PY_27)(func)
×
81

82

83
def skip_unless_python3_present(func):
10✔
84
    """A test skip decorator that only runs a test method if python3 is present."""
85
    return skip_unless_all_pythons_present(PY_3)(func)
×
86

87

88
def skip_unless_python36_present(func):
10✔
89
    """A test skip decorator that only runs a test method if python3.6 is present."""
90
    return skip_unless_all_pythons_present(PY_36)(func)
×
91

92

93
def skip_unless_python37_present(func):
10✔
94
    """A test skip decorator that only runs a test method if python3.7 is present."""
95
    return skip_unless_all_pythons_present(PY_37)(func)
1✔
96

97

98
def skip_unless_python38_present(func):
10✔
99
    """A test skip decorator that only runs a test method if python3.8 is present."""
100
    return skip_unless_all_pythons_present(PY_38)(func)
7✔
101

102

103
def skip_unless_python39_present(func):
10✔
104
    """A test skip decorator that only runs a test method if python3.9 is present."""
105
    return skip_unless_all_pythons_present(PY_39)(func)
8✔
106

107

108
def skip_unless_python310_present(func):
10✔
109
    """A test skip decorator that only runs a test method if python3.10 is present."""
110
    return skip_unless_all_pythons_present(PY_310)(func)
×
111

112

113
def skip_unless_python311_present(func):
10✔
114
    """A test skip decorator that only runs a test method if python3.11 is present."""
115
    return skip_unless_all_pythons_present(PY_311)(func)
×
116

117

118
def skip_unless_python310_and_python311_present(func):
10✔
119
    """A test skip decorator that only runs a test method if python3.8 and python3.9 are present."""
120
    return skip_unless_all_pythons_present(PY_310, PY_311)(func)
3✔
121

122

123
def skip_unless_python27_and_python3_present(func):
10✔
124
    """A test skip decorator that only runs a test method if python2.7 and python3 are present."""
125
    return skip_unless_all_pythons_present(PY_27, PY_3)(func)
×
126

127

128
def skip_unless_python27_and_python36_present(func):
10✔
129
    """A test skip decorator that only runs a test method if python2.7 and python3.6 are present."""
130
    return skip_unless_all_pythons_present(PY_27, PY_36)(func)
×
131

132

133
def skip_unless_python38_and_python39_present(func):
10✔
134
    """A test skip decorator that only runs a test method if python3.8 and python3.9 are present."""
135
    return skip_unless_all_pythons_present(PY_38, PY_39)(func)
1✔
136

137

138
def skip_unless_python37_and_python39_present(func):
10✔
139
    """A test skip decorator that only runs a test method if python3.7 and python3.9 are present."""
140
    return skip_unless_all_pythons_present(PY_37, PY_39)(func)
×
141

142

143
def all_major_minor_python_versions(
10✔
144
    constraints: Iterable[str],
145
) -> tuple[_pytest.mark.structures.ParameterSet, ...]:
146
    """All major.minor Python versions used by the interpreter constraints.
147

148
    This is intended to be used with `@pytest.mark.parametrize()` to run a test with every relevant
149
    Python interpreter.
150
    """
151
    versions = InterpreterConstraints(constraints).partition_into_major_minor_versions(
10✔
152
        # Please update this when new stable Python versions are released to CI.
153
        interpreter_universe=[
154
            "2.7",
155
            "3.6",
156
            "3.7",
157
            "3.8",
158
            "3.9",
159
            "3.10",
160
            "3.11",
161
            "3.12",
162
            "3.13",
163
            "3.14",
164
        ]
165
    )
166
    return tuple(
10✔
167
        pytest.param(
168
            version,
169
            marks=pytest.mark.skipif(
170
                not has_python_version(version),
171
                reason=f"Could not find python {version} on system. Skipping.",
172
            ),
173
        )
174
        for version in versions
175
    )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc