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

pantsbuild / pants / 24039158023

06 Apr 2026 03:58PM UTC coverage: 92.902% (-0.005%) from 92.907%
24039158023

Pull #23221

github

web-flow
Merge 453920c53 into 6b89a7982
Pull Request #23221: support plumbing --uploaded-prior to Pex/Pip

47 of 48 new or added lines in 6 files covered. (97.92%)

8 existing lines in 2 files now uncovered.

91579 of 98576 relevant lines covered (92.9%)

3.72 hits per line

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

94.7
/src/python/pants/core/goals/update_build_files_test.py
1
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
1✔
5

6
from collections.abc import Iterable
1✔
7
from pathlib import Path
1✔
8
from textwrap import dedent
1✔
9

10
import pytest
1✔
11

12
from pants.backend.build_files.fmt.buildifier.subsystem import Buildifier
1✔
13
from pants.backend.python.lint.black.subsystem import Black
1✔
14
from pants.backend.python.lint.ruff.subsystem import Ruff
1✔
15
from pants.backend.python.lint.yapf.subsystem import Yapf
1✔
16
from pants.backend.python.subsystems.python_tool_base import get_lockfile_interpreter_constraints
1✔
17
from pants.backend.python.util_rules import pex
1✔
18
from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints
1✔
19
from pants.backend.python.util_rules.lockfile_metadata import PythonLockfileMetadata
1✔
20
from pants.backend.python.util_rules.pex_requirements import LoadedLockfile, Lockfile
1✔
21
from pants.core.goals.update_build_files import (
1✔
22
    FormatWithBlackRequest,
23
    FormatWithBuildifierRequest,
24
    FormatWithRuffRequest,
25
    FormatWithYapfRequest,
26
    RewrittenBuildFile,
27
    RewrittenBuildFileRequest,
28
    UpdateBuildFilesGoal,
29
    UpdateBuildFilesSubsystem,
30
    format_build_file_with_black,
31
    format_build_file_with_buildifier,
32
    format_build_file_with_ruff,
33
    format_build_file_with_yapf,
34
    rewrite_build_file,
35
    update_build_files,
36
)
37
from pants.core.target_types import GenericTarget
1✔
38
from pants.core.util_rules import config_files
1✔
39
from pants.engine.fs import EMPTY_DIGEST
1✔
40
from pants.engine.rules import rule
1✔
41
from pants.engine.unions import UnionRule
1✔
42
from pants.option.ranked_value import Rank, RankedValue
1✔
43
from pants.testutil.option_util import create_subsystem
1✔
44
from pants.testutil.rule_runner import GoalRuleResult, RuleRunner, run_rule_with_mocks
1✔
45

46
# ------------------------------------------------------------------------------------------
47
# Generic goal
48
# ------------------------------------------------------------------------------------------
49

50

51
class MockRewriteAddLine(RewrittenBuildFileRequest):
1✔
52
    pass
1✔
53

54

55
class MockRewriteReverseLines(RewrittenBuildFileRequest):
1✔
56
    pass
1✔
57

58

59
@rule
1✔
60
async def add_line(request: MockRewriteAddLine) -> RewrittenBuildFile:
1✔
61
    return RewrittenBuildFile(
1✔
62
        request.path, (*request.lines, "# added line"), change_descriptions=("Add a new line",)
63
    )
64

65

66
@rule
1✔
67
async def reverse_lines(request: MockRewriteReverseLines) -> RewrittenBuildFile:
1✔
68
    return RewrittenBuildFile(
1✔
69
        request.path, tuple(reversed(request.lines)), change_descriptions=("Reverse lines",)
70
    )
71

72

73
@pytest.fixture
1✔
74
def generic_goal_rule_runner() -> RuleRunner:
1✔
75
    return RuleRunner(
1✔
76
        rules=(
77
            add_line,
78
            reverse_lines,
79
            rewrite_build_file,
80
            format_build_file_with_ruff,
81
            format_build_file_with_yapf,
82
            update_build_files,
83
            *config_files.rules(),
84
            *pex.rules(),
85
            # Ruff and Yapf are included, but Black isn't because
86
            # that's the formatter we enable in pants.toml.
87
            # These tests check that Ruff and Yapf are NOT invoked,
88
            # but the other rewrite targets are invoked.
89
            *Ruff.rules(),
90
            *Yapf.rules(),
91
            *UpdateBuildFilesSubsystem.rules(),
92
            UnionRule(RewrittenBuildFileRequest, MockRewriteAddLine),
93
            UnionRule(RewrittenBuildFileRequest, MockRewriteReverseLines),
94
            UnionRule(RewrittenBuildFileRequest, FormatWithRuffRequest),
95
            UnionRule(RewrittenBuildFileRequest, FormatWithYapfRequest),
96
        )
97
    )
98

99

100
def test_goal_rewrite_mode(generic_goal_rule_runner: RuleRunner) -> None:
1✔
101
    """Checks that we correctly write the changes and pipe fixers to each other."""
102
    generic_goal_rule_runner.write_files({"BUILD": "# line\n", "dir/BUILD": "# line 1\n# line 2\n"})
1✔
103
    result = generic_goal_rule_runner.run_goal_rule(UpdateBuildFilesGoal, args=["::"])
1✔
104
    assert result.exit_code == 0
1✔
105
    assert result.stdout == dedent(
1✔
106
        """\
107
        Updated BUILD:
108
          - Add a new line
109
          - Reverse lines
110
        Updated dir/BUILD:
111
          - Add a new line
112
          - Reverse lines
113
        """
114
    )
115
    assert (
1✔
116
        Path(generic_goal_rule_runner.build_root, "BUILD").read_text() == "# added line\n# line\n"
117
    )
118
    assert (
1✔
119
        Path(generic_goal_rule_runner.build_root, "dir/BUILD").read_text()
120
        == "# added line\n# line 2\n# line 1\n"
121
    )
122

123

124
def test_goal_check_mode(generic_goal_rule_runner: RuleRunner) -> None:
1✔
125
    """Checks that we correctly set the exit code and pipe fixers to each other."""
126
    generic_goal_rule_runner.write_files({"BUILD": "# line\n", "dir/BUILD": "# line 1\n# line 2\n"})
1✔
127
    result = generic_goal_rule_runner.run_goal_rule(
1✔
128
        UpdateBuildFilesGoal,
129
        global_args=["--pants-bin-name=./custom_pants"],
130
        args=["--check", "::"],
131
    )
132
    assert result.exit_code == 1
1✔
133
    assert result.stdout == dedent(
1✔
134
        """\
135
        Would update BUILD:
136
          - Add a new line
137
          - Reverse lines
138
        Would update dir/BUILD:
139
          - Add a new line
140
          - Reverse lines
141

142
        To fix `update-build-files` failures, run `./custom_pants update-build-files`.
143
        """
144
    )
145
    assert Path(generic_goal_rule_runner.build_root, "BUILD").read_text() == "# line\n"
1✔
146
    assert (
1✔
147
        Path(generic_goal_rule_runner.build_root, "dir/BUILD").read_text() == "# line 1\n# line 2\n"
148
    )
149

150

151
def test_get_lockfile_interpreter_constraints() -> None:
1✔
152
    default_metadata = PythonLockfileMetadata.new(
1✔
153
        valid_for_interpreter_constraints=InterpreterConstraints(["==2.7.*"]),
154
        requirements=set(),
155
        requirement_constraints=set(),
156
        only_binary=set(),
157
        no_binary=set(),
158
        manylinux=None,
159
        excludes=set(),
160
        overrides=set(),
161
        sources=set(),
162
        lock_style="universal",
163
        complete_platforms=(),
164
    )
165

UNCOV
166
    def assert_ics(
×
167
        lckfile: str,
168
        expected: Iterable[str],
169
        *,
170
        ics: RankedValue = RankedValue(Rank.HARDCODED, list(Black.default_interpreter_constraints)),
171
        metadata: PythonLockfileMetadata | None = default_metadata,
172
    ) -> None:
UNCOV
173
        black = create_subsystem(
×
174
            Black,
175
            lockfile=lckfile,
176
            interpreter_constraints=ics,
177
            version="v",
178
            requirements=["v"],
179
            install_from_resolve=None,
180
            extra_requirements=[],
181
        )
UNCOV
182
        loaded_lock = LoadedLockfile(
×
183
            EMPTY_DIGEST,
184
            "black.lock",
185
            metadata=metadata,
186
            requirement_estimate=1,
187
            is_pex_native=True,
188
            as_constraints_strings=None,
189
            original_lockfile=Lockfile(
190
                "black.lock", url_description_of_origin="foo", resolve_name="black"
191
            ),
192
        )
UNCOV
193
        result = run_rule_with_mocks(
×
194
            get_lockfile_interpreter_constraints,
195
            rule_args=[black],
196
            mock_calls={
197
                "pants.backend.python.util_rules.pex_requirements.load_lockfile": lambda _: loaded_lock,
198
            },
199
        )
UNCOV
200
        assert result == InterpreterConstraints(expected)
×
201

202
    # If ICs are set by user, always use those.
UNCOV
203
    assert_ics("black.lock", ["==3.8.*"], ics=RankedValue(Rank.CONFIG, ["==3.8.*"]))
×
204
    # Otherwise use what's in the lockfile metadata.
UNCOV
205
    assert_ics("black.lock", ["==2.7.*"])
×
206

207

208
# ------------------------------------------------------------------------------------------
209
# Black formatter fixer
210
# ------------------------------------------------------------------------------------------
211

212
# See black/rules_integration_test.py for why we set LANG and LC_ALL.
213
BLACK_ENV_INHERIT = {"PATH", "PYENV_ROOT", "HOME", "LANG", "LC_ALL"}
1✔
214

215

216
@pytest.fixture
1✔
217
def black_rule_runner() -> RuleRunner:
1✔
218
    return RuleRunner(
1✔
219
        rules=(
220
            rewrite_build_file,
221
            format_build_file_with_black,
222
            format_build_file_with_ruff,
223
            format_build_file_with_yapf,
224
            update_build_files,
225
            *config_files.rules(),
226
            *pex.rules(),
227
            *Black.rules(),
228
            # Even though Ruff and Yapf are included here,
229
            # only Black should be used for formatting.
230
            *Ruff.rules(),
231
            *Yapf.rules(),
232
            *UpdateBuildFilesSubsystem.rules(),
233
            UnionRule(RewrittenBuildFileRequest, FormatWithBlackRequest),
234
            UnionRule(RewrittenBuildFileRequest, FormatWithRuffRequest),
235
            UnionRule(RewrittenBuildFileRequest, FormatWithYapfRequest),
236
        ),
237
        target_types=[GenericTarget],
238
    )
239

240

241
def test_black_fixer_fixes(black_rule_runner: RuleRunner) -> None:
1✔
242
    black_rule_runner.write_files({"BUILD": "target( name =  't' )"})
1✔
243
    result = black_rule_runner.run_goal_rule(
1✔
244
        UpdateBuildFilesGoal, args=["::"], env_inherit=BLACK_ENV_INHERIT
245
    )
246
    assert result.exit_code == 0
1✔
247
    assert result.stdout == dedent(
1✔
248
        """\
249
        Updated BUILD:
250
          - Format with Black
251
        """
252
    )
253
    assert Path(black_rule_runner.build_root, "BUILD").read_text() == 'target(name="t")\n'
1✔
254

255

256
def test_black_fixer_noops(black_rule_runner: RuleRunner) -> None:
1✔
257
    black_rule_runner.write_files({"BUILD": 'target(name="t")\n'})
1✔
258
    result = black_rule_runner.run_goal_rule(
1✔
259
        UpdateBuildFilesGoal, args=["::"], env_inherit=BLACK_ENV_INHERIT
260
    )
261
    assert result.exit_code == 0
1✔
262
    assert Path(black_rule_runner.build_root, "BUILD").read_text() == 'target(name="t")\n'
1✔
263

264

265
def test_black_fixer_args(black_rule_runner: RuleRunner) -> None:
1✔
266
    black_rule_runner.write_files({"BUILD": "target(name='t')\n"})
1✔
267
    result = black_rule_runner.run_goal_rule(
1✔
268
        UpdateBuildFilesGoal,
269
        global_args=["--black-args='--skip-string-normalization'"],
270
        args=["::"],
271
        env_inherit=BLACK_ENV_INHERIT,
272
    )
273
    assert result.exit_code == 0
1✔
274
    assert Path(black_rule_runner.build_root, "BUILD").read_text() == "target(name='t')\n"
1✔
275

276

277
def test_black_config(black_rule_runner: RuleRunner) -> None:
1✔
278
    black_rule_runner.write_files(
1✔
279
        {
280
            "pyproject.toml": "[tool.black]\nskip-string-normalization = 'true'\n",
281
            "BUILD": "target(name='t')\n",
282
        },
283
    )
284
    result = black_rule_runner.run_goal_rule(
1✔
285
        UpdateBuildFilesGoal, args=["::"], env_inherit=BLACK_ENV_INHERIT
286
    )
287
    assert result.exit_code == 0
1✔
288
    assert Path(black_rule_runner.build_root, "BUILD").read_text() == "target(name='t')\n"
1✔
289

290

291
# ------------------------------------------------------------------------------------------
292
# Ruff formatter fixer
293
# ------------------------------------------------------------------------------------------
294

295

296
def run_ruff(
1✔
297
    build_content: str, *, extra_args: list[str] | None = None
298
) -> tuple[GoalRuleResult, str]:
299
    """Returns the Goal's result and contents of the BUILD file after execution."""
300
    rule_runner = RuleRunner(
1✔
301
        rules=(
302
            rewrite_build_file,
303
            format_build_file_with_ruff,
304
            update_build_files,
305
            *config_files.rules(),
306
            *pex.rules(),
307
            *Ruff.rules(),
308
            *UpdateBuildFilesSubsystem.rules(),
309
            UnionRule(RewrittenBuildFileRequest, FormatWithRuffRequest),
310
        ),
311
        target_types=[GenericTarget],
312
    )
313
    rule_runner.write_files({"BUILD": build_content})
1✔
314
    goal_result = rule_runner.run_goal_rule(
1✔
315
        UpdateBuildFilesGoal,
316
        args=["--update-build-files-formatter=ruff", "::"],
317
        global_args=extra_args or (),
318
        env_inherit=BLACK_ENV_INHERIT,
319
    )
320
    rewritten_build = Path(rule_runner.build_root, "BUILD").read_text()
1✔
321
    return goal_result, rewritten_build
1✔
322

323

324
def test_ruff_fixer_fixes() -> None:
1✔
325
    result, build = run_ruff("target( name =  't' )")
1✔
326
    assert result.exit_code == 0
1✔
327
    assert result.stdout == dedent(
1✔
328
        """\
329
        Updated BUILD:
330
          - Format with Ruff
331
        """
332
    )
333
    assert build == 'target(name="t")\n'
1✔
334

335

336
def test_ruff_fixer_noops() -> None:
1✔
337
    result, build = run_ruff('target(name="t")\n')
1✔
338
    assert result.exit_code == 0
1✔
339
    assert not result.stdout
1✔
340
    assert build == 'target(name="t")\n'
1✔
341

342

343
# ------------------------------------------------------------------------------------------
344
# Buildifier formatter fixer
345
# ------------------------------------------------------------------------------------------
346

347

348
def run_buildifier(
1✔
349
    build_content: str, *, extra_args: list[str] | None = None
350
) -> tuple[GoalRuleResult, str]:
351
    """Returns the Goal's result and contents of the BUILD file after execution."""
352
    rule_runner = RuleRunner(
1✔
353
        rules=(
354
            rewrite_build_file,
355
            format_build_file_with_buildifier,
356
            update_build_files,
357
            *config_files.rules(),
358
            *pex.rules(),
359
            *Buildifier.rules(),
360
            *UpdateBuildFilesSubsystem.rules(),
361
            UnionRule(RewrittenBuildFileRequest, FormatWithBuildifierRequest),
362
        ),
363
        target_types=[GenericTarget],
364
    )
365
    rule_runner.write_files({"BUILD": build_content})
1✔
366
    goal_result = rule_runner.run_goal_rule(
1✔
367
        UpdateBuildFilesGoal,
368
        args=[f"--update-build-files-formatter={Buildifier.options_scope}", "::"],
369
        global_args=extra_args or (),
370
        env_inherit=BLACK_ENV_INHERIT,
371
    )
372
    rewritten_build = Path(rule_runner.build_root, "BUILD").read_text()
1✔
373
    return goal_result, rewritten_build
1✔
374

375

376
def test_buildifier_fixer_fixes() -> None:
1✔
377
    result, build = run_buildifier("target(name='t')")
1✔
378
    assert result.exit_code == 0
1✔
379
    assert result.stdout == dedent(
1✔
380
        f"""\
381
        Updated BUILD:
382
          - Format with {Buildifier.name}
383
        """
384
    )
385
    assert build == 'target(name = "t")\n'
1✔
386

387

388
def test_buildifier_fixer_noops() -> None:
1✔
389
    result, build = run_buildifier('target(name = "t")\n')
1✔
390
    assert result.exit_code == 0
1✔
391
    assert not result.stdout
1✔
392
    assert build == 'target(name = "t")\n'
1✔
393

394

395
# ------------------------------------------------------------------------------------------
396
# Yapf formatter fixer
397
# ------------------------------------------------------------------------------------------
398

399

400
def run_yapf(
1✔
401
    build_content: str, *, extra_args: list[str] | None = None
402
) -> tuple[GoalRuleResult, str]:
403
    """Returns the Goal's result and contents of the BUILD file after execution."""
404
    rule_runner = RuleRunner(
1✔
405
        rules=(
406
            rewrite_build_file,
407
            format_build_file_with_yapf,
408
            update_build_files,
409
            *config_files.rules(),
410
            *pex.rules(),
411
            *Yapf.rules(),
412
            *UpdateBuildFilesSubsystem.rules(),
413
            UnionRule(RewrittenBuildFileRequest, FormatWithYapfRequest),
414
        ),
415
        target_types=[GenericTarget],
416
    )
417
    rule_runner.write_files({"BUILD": build_content})
1✔
418
    goal_result = rule_runner.run_goal_rule(
1✔
419
        UpdateBuildFilesGoal,
420
        args=["--update-build-files-formatter=yapf", "::"],
421
        global_args=extra_args or (),
422
        env_inherit=BLACK_ENV_INHERIT,
423
    )
424
    rewritten_build = Path(rule_runner.build_root, "BUILD").read_text()
1✔
425
    return goal_result, rewritten_build
1✔
426

427

428
def test_yapf_fixer_fixes() -> None:
1✔
429
    result, build = run_yapf("target( name =  't' )")
1✔
430
    assert result.exit_code == 0
1✔
431
    assert result.stdout == dedent(
1✔
432
        """\
433
        Updated BUILD:
434
          - Format with Yapf
435
        """
436
    )
437
    assert build == "target(name='t')\n"
1✔
438

439

440
def test_yapf_fixer_noops() -> None:
1✔
441
    result, build = run_yapf('target(name="t")\n')
1✔
442
    assert result.exit_code == 0
1✔
443
    assert not result.stdout
1✔
444
    assert build == 'target(name="t")\n'
1✔
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