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

pantsbuild / pants / 19250292619

11 Nov 2025 12:09AM UTC coverage: 77.865% (-2.4%) from 80.298%
19250292619

push

github

web-flow
flag non-runnable targets used with `code_quality_tool` (#22875)

2 of 5 new or added lines in 2 files covered. (40.0%)

1487 existing lines in 72 files now uncovered.

71448 of 91759 relevant lines covered (77.86%)

3.22 hits per line

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

49.5
/src/python/pants/backend/python/providers/python_build_standalone/rules_integration_test.py
1
# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
2✔
5

6
import shutil
2✔
7
from collections.abc import Iterable
2✔
8
from textwrap import dedent
2✔
9

10
import pytest
2✔
11

12
from pants.backend.python import target_types_rules
2✔
13
from pants.backend.python.dependency_inference import rules as dependency_inference_rules
2✔
14
from pants.backend.python.goals.run_python_source import PythonSourceFieldSet
2✔
15
from pants.backend.python.goals.run_python_source import rules as run_rules
2✔
16
from pants.backend.python.providers.python_build_standalone import rules as pbs
2✔
17
from pants.backend.python.providers.python_build_standalone.constraints import ConstraintsList
2✔
18
from pants.backend.python.target_types import PythonSourcesGeneratorTarget
2✔
19
from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints
2✔
20
from pants.build_graph.address import Address
2✔
21
from pants.core.goals.run import RunRequest
2✔
22
from pants.core.util_rules.external_tool import ExternalToolError
2✔
23
from pants.engine.platform import Platform
2✔
24
from pants.engine.process import InteractiveProcess
2✔
25
from pants.engine.rules import QueryRule
2✔
26
from pants.engine.target import Target
2✔
27
from pants.testutil.option_util import create_subsystem
2✔
28
from pants.testutil.rule_runner import RuleRunner, mock_console
2✔
29
from pants.version import Version
2✔
30

31

32
@pytest.fixture
2✔
33
def rule_runner() -> RuleRunner:
2✔
34
    return RuleRunner(
2✔
35
        rules=[
36
            *run_rules(),
37
            *pbs.rules(),
38
            *dependency_inference_rules.rules(),
39
            *target_types_rules.rules(),
40
            QueryRule(RunRequest, (PythonSourceFieldSet,)),
41
            QueryRule(Platform, ()),
42
            QueryRule(pbs.PBSPythonProviderSubsystem, ()),
43
        ],
44
        target_types=[
45
            PythonSourcesGeneratorTarget,
46
        ],
47
    )
48

49

50
@pytest.fixture
2✔
51
def mock_empty_versions_resource():
2✔
UNCOV
52
    before = pbs.load_pbs_pythons
×
UNCOV
53
    pbs.load_pbs_pythons = lambda: {}
×
UNCOV
54
    yield
×
UNCOV
55
    pbs.load_pbs_pythons = before
×
56

57

58
def run_run_request(
2✔
59
    rule_runner: RuleRunner,
60
    target: Target,
61
    additional_args: Iterable[str] = (),
62
) -> str:
63
    args = [
2✔
64
        "--backend-packages=['pants.backend.python', 'pants.backend.python.providers.experimental.python_build_standalone']",
65
        "--source-root-patterns=['src']",
66
        *additional_args,
67
    ]
68
    rule_runner.set_options(args, env_inherit={"PATH", "PYENV_ROOT", "HOME"})
2✔
69
    run_request = rule_runner.request(RunRequest, [PythonSourceFieldSet.create(target)])
2✔
70
    run_process = InteractiveProcess(
2✔
71
        argv=run_request.args,
72
        env=run_request.extra_env,
73
        input_digest=run_request.digest,
74
        run_in_workspace=True,
75
        immutable_input_digests=run_request.immutable_input_digests,
76
        append_only_caches=run_request.append_only_caches,
77
    )
78
    with mock_console(rule_runner.options_bootstrapper) as mocked_console:
2✔
79
        rule_runner.run_interactive_process(run_process)
2✔
80
        return mocked_console[1].get_stdout().strip()
2✔
81

82

83
@pytest.mark.platform_specific_behavior
2✔
84
@pytest.mark.parametrize("py_version", ["3.8.*", "3.9.*", "3.9.10"])
2✔
85
def test_using_pbs(rule_runner, py_version):
2✔
86
    rule_runner.write_files(
2✔
87
        {
88
            "src/app.py": dedent(
89
                """\
90
                import sys
91

92
                print(sys.version.replace("\\n", " "))
93
                """
94
            ),
95
            "src/BUILD": f"python_sources(interpreter_constraints=['=={py_version}'])",
96
        }
97
    )
98

99
    target = rule_runner.get_target(Address("src", relative_file_path="app.py"))
2✔
100
    stdout = run_run_request(rule_runner, target)
2✔
101
    version = stdout.splitlines()[0]
2✔
102
    assert py_version.rstrip("*") in version
2✔
103
    # NB: The earliest 3.9 we support is 3.9.6, but that should never get chosen unless explicitly
104
    # requested because we should prefer latest-patch if possible.
105
    assert not version.startswith("3.9.6")
2✔
106

107

108
def test_useful_error(rule_runner):
2✔
UNCOV
109
    rule_runner.write_files(
×
110
        {
111
            "src/app.py": "",
112
            "src/BUILD": "python_sources(interpreter_constraints=['==2.7'])",
113
        }
114
    )
115

UNCOV
116
    target = rule_runner.get_target(Address("src", relative_file_path="app.py"))
×
UNCOV
117
    with pytest.raises(Exception, match="Supported versions are currently"):
×
UNCOV
118
        run_run_request(rule_runner, target)
×
119

120

121
def test_additional_versions(rule_runner, mock_empty_versions_resource):
2✔
UNCOV
122
    rule_runner.write_files(
×
123
        {
124
            "src/app.py": dedent(
125
                """\
126
                import sys
127

128
                print(sys.version.replace("\\n", " "))
129
                """
130
            ),
131
            "src/BUILD": "python_sources(interpreter_constraints=['==3.9.*'])",
132
        }
133
    )
134

UNCOV
135
    target = rule_runner.get_target(Address("src", relative_file_path="app.py"))
×
UNCOV
136
    with pytest.raises(Exception, match=r"Supported versions are currently: \[\]"):
×
UNCOV
137
        run_run_request(rule_runner, target)
×
138

UNCOV
139
    stdout = run_run_request(
×
140
        rule_runner,
141
        target,
142
        additional_args=[
143
            "--python-build-standalone-python-provider-known-python-versions=["
144
            + "'3.9.16|linux_arm64|75f3d10ae8933e17bf27e8572466ff8a1e7792f521d33acba578cc8a25d82e0b|24540128|https://github.com/astral-sh/python-build-standalone/releases/download/20221220/cpython-3.9.16%2B20221220-aarch64-unknown-linux-gnu-install_only.tar.gz',"
145
            + "'3.9.16|macos_arm64|73bad3a610a0ff14166fbd5045cd186084bd2ce99edd2c6327054509e790b9ab|16765350|https://github.com/astral-sh/python-build-standalone/releases/download/20221220/cpython-3.9.16%2B20221220-aarch64-apple-darwin-install_only.tar.gz',"
146
            + "'f885f3d011ab08e4d9521a7ae2662e9e0073acc0305a1178984b5a1cf057309a|26767987|https://github.com/astral-sh/python-build-standalone/releases/download/20221220/cpython-3.9.16%2B20221220-x86_64-unknown-linux-gnu-install_only.tar.gz',"
147
            + "'69331e93656b179fcbfec0d506dfca11d899fe5dced990b28915e41755ce215c|17151321|https://github.com/astral-sh/python-build-standalone/releases/download/20221220/cpython-3.9.16%2B20221220-x86_64-apple-darwin-install_only.tar.gz',"
148
            + "]"
149
        ],
150
    )
UNCOV
151
    version = stdout.splitlines()[0]
×
UNCOV
152
    assert version.startswith("3.9.16")
×
153

154

155
# Confirm whether the PBS tag data can be inferred from a URL.
156
def test_tag_inference_from_url() -> None:
2✔
UNCOV
157
    subsystem = create_subsystem(
×
158
        pbs.PBSPythonProviderSubsystem,
159
        known_python_versions=[
160
            "3.10.13|linux_arm64|e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855|123|https://github.com/astral-sh/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz",
161
        ],
162
    )
163

UNCOV
164
    user_supplied_pbs_versions = subsystem.get_user_supplied_pbs_pythons()
×
UNCOV
165
    assert user_supplied_pbs_versions["3.10.13"]["20240224"]["linux_arm64"] == pbs.PBSPythonInfo(
×
166
        url="https://github.com/astral-sh/python-build-standalone/releases/download/20240224/cpython-3.10.13%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz",
167
        sha256="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
168
        size=123,
169
    )
170

171
    # Confirm whether requiring tag inference results in an error.
UNCOV
172
    subsystem = create_subsystem(
×
173
        pbs.PBSPythonProviderSubsystem,
174
        known_python_versions=[
175
            "3.10.13|linux_arm64|e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855|123|file:///releases/20240224/cpython.tar.gz",
176
        ],
177
    )
UNCOV
178
    with pytest.raises(
×
179
        ExternalToolError, match="no PBS release tag could be inferred from the URL"
180
    ):
UNCOV
181
        _ = subsystem.get_user_supplied_pbs_pythons()
×
182

183

184
def test_venv_pex_reconstruction(rule_runner):
2✔
185
    """A VenvPex refers to the location of the venv so it doesn't have to re-construct if it exists.
186

187
    Part of this location is a hash of the interpreter. Without careful consideration it can be easy
188
    for this hash to drift from build-time to run-time. This test validates the assumption that the
189
    venv could be reconstructed exactly if the underlying directory was wiped clean.
190
    """
UNCOV
191
    rule_runner.write_files(
×
192
        {
193
            "src/app.py": dedent(
194
                """\
195
                import pathlib
196
                import sys
197

198
                in_venv_python_path = pathlib.Path(sys.executable)
199
                venv_link = in_venv_python_path.parent.parent
200
                venv_location = venv_link.resolve()
201
                print(venv_location)
202
                """
203
            ),
204
            "src/BUILD": "python_sources(interpreter_constraints=['==3.9.*'])",
205
        }
206
    )
207

UNCOV
208
    target = rule_runner.get_target(Address("src", relative_file_path="app.py"))
×
UNCOV
209
    stdout1 = run_run_request(rule_runner, target)
×
UNCOV
210
    assert "pex_root/venvs/" in stdout1
×
UNCOV
211
    venv_location = stdout1
×
UNCOV
212
    shutil.rmtree(venv_location)
×
UNCOV
213
    stdout2 = run_run_request(rule_runner, target)
×
UNCOV
214
    assert stdout1 == stdout2
×
215

216

217
def test_release_constraint_evaluation(rule_runner: RuleRunner) -> None:
2✔
UNCOV
218
    ics = InterpreterConstraints(["cpython==3.9.*"])
×
UNCOV
219
    universe = ["3.9"]
×
220

UNCOV
221
    def make_platform_metadata():
×
UNCOV
222
        return {
×
223
            "linux_arm64": {
224
                "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
225
                "size": 1,
226
                "url": "foobar",
227
            },
228
            "linux_x86_64": {
229
                "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
230
                "size": 1,
231
                "url": "https://example.com/foo.zip",
232
            },
233
            "macos_arm64": {
234
                "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
235
                "size": 1,
236
                "url": "https://example.com/foo.zip",
237
            },
238
            "macos_x86_64": {
239
                "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
240
                "size": 1,
241
                "url": "https://example.com/foo.zip",
242
            },
243
        }
244

UNCOV
245
    pbs_versions = {
×
246
        "3.9.18": {"20241001": make_platform_metadata()},
247
        "3.9.19": {"20241101": make_platform_metadata()},
248
        "3.9.20": {"20241201": make_platform_metadata()},
249
    }
250

UNCOV
251
    platform = rule_runner.request(Platform, [])
×
252

UNCOV
253
    rc = ConstraintsList.parse(">=20241001,<20241201")
×
UNCOV
254
    version, pbs_version, _info = pbs._choose_python(ics, universe, pbs_versions, platform, rc)
×
UNCOV
255
    assert version == "3.9.19"
×
UNCOV
256
    assert pbs_version == Version("20241101")
×
257

258
    # Ensure that exception occurs if no version matches.
UNCOV
259
    rc = ConstraintsList.parse("==20250101")
×
UNCOV
260
    with pytest.raises(
×
261
        Exception,
262
        match="Failed to find a supported Python Build Standalone for Interpreter Constraint",
263
    ):
UNCOV
264
        _version, _pbs_version, _info = pbs._choose_python(
×
265
            ics, universe, pbs_versions, platform, rc
266
        )
267

268
    # Ensure that PBS versions with no tag metadata are filtered out so there is no "match".
UNCOV
269
    actual_pbs_versions = pbs.load_pbs_pythons()
×
UNCOV
270
    rc = ConstraintsList.parse("==19700101")
×
UNCOV
271
    with pytest.raises(
×
272
        Exception,
273
        match="Failed to find a supported Python Build Standalone for Interpreter Constraint",
274
    ):
UNCOV
275
        _version, _pbs_version, _info = pbs._choose_python(
×
276
            ics, universe, actual_pbs_versions, platform, rc
277
        )
278

279
    # Ensure that the highest release for a particualr version is chosen.
UNCOV
280
    pbs_versions = {
×
281
        "3.9.18": {"20241001": make_platform_metadata()},
282
        "3.9.19": {"20241101": make_platform_metadata(), "20241115": make_platform_metadata()},
283
        "3.10.15": {"20241115": make_platform_metadata()},
284
    }
UNCOV
285
    rc = ConstraintsList.parse(">=20241001,<=20241201")
×
UNCOV
286
    version, pbs_version, _info = pbs._choose_python(ics, universe, pbs_versions, platform, rc)
×
UNCOV
287
    assert version == "3.9.19"
×
UNCOV
288
    assert pbs_version == Version("20241115")
×
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

© 2025 Coveralls, Inc