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

pantsbuild / pants / 22285099215

22 Feb 2026 08:52PM UTC coverage: 75.854% (-17.1%) from 92.936%
22285099215

Pull #23121

github

web-flow
Merge c7299df9c into ba8359840
Pull Request #23121: fix issue with optional fields in dependency validator

28 of 29 new or added lines in 2 files covered. (96.55%)

11174 existing lines in 400 files now uncovered.

53694 of 70786 relevant lines covered (75.85%)

1.88 hits per line

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

83.58
/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
3✔
5

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

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

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

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

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

31

32
def has_python_version(version):
3✔
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
3✔
40

41

42
@lru_cache
3✔
43
def python_interpreter_path(version):
3✔
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:
3✔
51
        command = [f"python{version}", "-c", "import sys; print(sys.executable)"]
3✔
52
        py_path = subprocess.check_output(command).decode().strip()
3✔
53
        return os.path.realpath(py_path)
3✔
54
    except (subprocess.CalledProcessError, FileNotFoundError):
1✔
55
        return None
1✔
56

57

58
def skip_unless_all_pythons_present(*versions):
3✔
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)]
3✔
65
    if len(missing_versions) == 1:
3✔
66
        return skipIf(True, f"Could not find python {missing_versions[0]} on system. Skipping.")
1✔
67
    elif len(missing_versions) > 1:
3✔
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!")
3✔
76

77

78
def skip_unless_python27_present(func):
3✔
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):
3✔
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):
3✔
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):
3✔
94
    """A test skip decorator that only runs a test method if python3.7 is present."""
UNCOV
95
    return skip_unless_all_pythons_present(PY_37)(func)
×
96

97

98
def skip_unless_python38_present(func):
3✔
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)
1✔
101

102

103
def skip_unless_python39_present(func):
3✔
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)
3✔
106

107

108
def skip_unless_python310_present(func):
3✔
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):
3✔
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):
3✔
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)
2✔
121

122

123
def skip_unless_python27_and_python3_present(func):
3✔
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):
3✔
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):
3✔
134
    """A test skip decorator that only runs a test method if python3.8 and python3.9 are present."""
UNCOV
135
    return skip_unless_all_pythons_present(PY_38, PY_39)(func)
×
136

137

138
def skip_unless_python37_and_python39_present(func):
3✔
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(
3✔
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(
3✔
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(
3✔
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