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

pantsbuild / pants / 25441711719

06 May 2026 02:31PM UTC coverage: 92.915%. Remained the same
25441711719

push

github

web-flow
use sha pin (with comment) format for generated actions (#23312)

Per the GitHub Action best practices we recently enabled at #23249, we
should pin each action to a SHA so that the reference is actually
immutable.

This will -- I hope -- knock out a large chunk of the 421 alerts we
currently get from zizmor. The next followup would then be upgrades and
harmonizing the generated and none-generated pins.

Notice: This idea was suggested by Claude while going over pinact output
and I was surprised to see that post processing the yaml wasn't too
gross.

92206 of 99237 relevant lines covered (92.91%)

4.04 hits per line

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

86.57
/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
11✔
5

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

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

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

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

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

31

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

41

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

57

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

77

78
def skip_unless_python27_present(func):
11✔
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):
11✔
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):
11✔
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):
11✔
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):
11✔
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)
8✔
101

102

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

107

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

122

123
def skip_unless_python27_and_python3_present(func):
11✔
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):
11✔
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):
11✔
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):
11✔
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(
11✔
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(
11✔
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(
11✔
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