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

pantsbuild / pants / 18252174847

05 Oct 2025 01:36AM UTC coverage: 43.382% (-36.9%) from 80.261%
18252174847

push

github

web-flow
run tests on mac arm (#22717)

Just doing the minimal to pull forward the x86_64 pattern.

ref #20993

25776 of 59416 relevant lines covered (43.38%)

1.3 hits per line

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

50.76
/src/python/pants/backend/python/lint/ruff/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
3✔
5

6
from pathlib import Path
3✔
7

8
import pytest
3✔
9

10
from pants.backend.python import target_types_rules
3✔
11
from pants.backend.python.lint.ruff import skip_field
3✔
12
from pants.backend.python.lint.ruff.check import skip_field as ruff_check_skip_field
3✔
13
from pants.backend.python.lint.ruff.check.rules import (
3✔
14
    RuffCheckFieldSet,
15
    RuffFixRequest,
16
    RuffLintRequest,
17
)
18
from pants.backend.python.lint.ruff.check.rules import rules as ruff_check_rules
3✔
19
from pants.backend.python.lint.ruff.check.skip_field import SkipRuffCheckField
3✔
20
from pants.backend.python.lint.ruff.format import skip_field as ruff_format_skip_field
3✔
21
from pants.backend.python.lint.ruff.format.rules import RuffFormatFieldSet, RuffFormatRequest
3✔
22
from pants.backend.python.lint.ruff.format.rules import rules as ruff_fmt_rules
3✔
23
from pants.backend.python.lint.ruff.format.skip_field import SkipRuffFormatField
3✔
24
from pants.backend.python.lint.ruff.skip_field import SkipRuffField
3✔
25
from pants.backend.python.lint.ruff.subsystem import rules as ruff_subsystem_rules
3✔
26
from pants.backend.python.target_types import PythonSourcesGeneratorTarget
3✔
27
from pants.core.goals.fix import FixResult
3✔
28
from pants.core.goals.fmt import FmtResult
3✔
29
from pants.core.goals.lint import LintResult
3✔
30
from pants.core.util_rules import config_files
3✔
31
from pants.core.util_rules.partitions import _EmptyMetadata
3✔
32
from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest
3✔
33
from pants.engine.addresses import Address
3✔
34
from pants.engine.fs import DigestContents
3✔
35
from pants.engine.target import Target
3✔
36
from pants.testutil.python_interpreter_selection import all_major_minor_python_versions
3✔
37
from pants.testutil.rule_runner import QueryRule, RuleRunner
3✔
38

39
GOOD_FILE = 'a = "string without any placeholders"\n'
3✔
40
BAD_FILE = 'a = f"string without any placeholders"\n'
3✔
41
UNFORMATTED_FILE = 'a ="string without any placeholders"\n'
3✔
42

43

44
@pytest.fixture
3✔
45
def rule_runner() -> RuleRunner:
3✔
46
    return RuleRunner(
3✔
47
        rules=[
48
            *ruff_check_rules(),
49
            *ruff_fmt_rules(),
50
            *skip_field.rules(),
51
            *ruff_check_skip_field.rules(),
52
            *ruff_format_skip_field.rules(),
53
            *ruff_subsystem_rules(),
54
            *config_files.rules(),
55
            *target_types_rules.rules(),
56
            QueryRule(FixResult, [RuffFixRequest.Batch]),
57
            QueryRule(LintResult, [RuffLintRequest.Batch]),
58
            QueryRule(FmtResult, [RuffFormatRequest.Batch]),
59
            QueryRule(SourceFiles, (SourceFilesRequest,)),
60
        ],
61
        target_types=[PythonSourcesGeneratorTarget],
62
    )
63

64

65
def run_ruff(
3✔
66
    rule_runner: RuleRunner,
67
    targets: list[Target],
68
    *,
69
    extra_args: list[str] | None = None,
70
) -> tuple[FixResult, LintResult, FmtResult]:
71
    args = ["--backend-packages=pants.backend.python.lint.ruff", *(extra_args or ())]
3✔
72
    rule_runner.set_options(args, env_inherit={"PATH", "PYENV_ROOT", "HOME"})
3✔
73

74
    check_field_sets = [
3✔
75
        RuffCheckFieldSet.create(tgt) for tgt in targets if RuffCheckFieldSet.is_applicable(tgt)
76
    ]
77
    check_source_reqs = [SourceFilesRequest(field_set.source for field_set in check_field_sets)]
3✔
78
    check_input_sources = rule_runner.request(SourceFiles, check_source_reqs)
3✔
79

80
    format_field_sets = [
3✔
81
        RuffFormatFieldSet.create(tgt) for tgt in targets if RuffFormatFieldSet.is_applicable(tgt)
82
    ]
83
    format_source_reqs = [SourceFilesRequest(field_set.source for field_set in format_field_sets)]
3✔
84
    format_input_sources = rule_runner.request(SourceFiles, format_source_reqs)
3✔
85

86
    fix_result = rule_runner.request(
3✔
87
        FixResult,
88
        [
89
            RuffFixRequest.Batch(
90
                "",
91
                tuple(check_field_sets),
92
                partition_metadata=_EmptyMetadata(),
93
                snapshot=check_input_sources.snapshot,
94
            ),
95
        ],
96
    )
97
    lint_result = rule_runner.request(
3✔
98
        LintResult,
99
        [
100
            RuffLintRequest.Batch(
101
                "",
102
                tuple(check_field_sets),
103
                partition_metadata=_EmptyMetadata(),
104
            ),
105
        ],
106
    )
107
    fmt_result = rule_runner.request(
3✔
108
        FmtResult,
109
        [
110
            RuffFormatRequest.Batch(
111
                "",
112
                tuple(format_field_sets),
113
                partition_metadata=_EmptyMetadata(),
114
                snapshot=format_input_sources.snapshot,
115
            )
116
        ],
117
    )
118

119
    return fix_result, lint_result, fmt_result
3✔
120

121

122
@pytest.mark.platform_specific_behavior
3✔
123
@pytest.mark.parametrize(
3✔
124
    "major_minor_interpreter",
125
    all_major_minor_python_versions(["CPython>=3.9,<4"]),
126
)
127
def test_passing(rule_runner: RuleRunner, major_minor_interpreter: str) -> None:
3✔
128
    rule_runner.write_files({"f.py": GOOD_FILE, "BUILD": "python_sources(name='t')"})
3✔
129
    tgt = rule_runner.get_target(Address("", target_name="t", relative_file_path="f.py"))
3✔
130
    fix_result, lint_result, fmt_result = run_ruff(
3✔
131
        rule_runner,
132
        [tgt],
133
        extra_args=[f"--python-interpreter-constraints=['=={major_minor_interpreter}.*']"],
134
    )
135
    assert lint_result.exit_code == 0
3✔
136
    assert fix_result.stderr == ""
3✔
137
    assert fix_result.stdout == "All checks passed!\n"
3✔
138
    assert not fix_result.did_change
3✔
139
    assert fix_result.output == rule_runner.make_snapshot({"f.py": GOOD_FILE})
3✔
140
    assert not fmt_result.did_change
3✔
141
    assert fmt_result.output == rule_runner.make_snapshot({"f.py": GOOD_FILE})
3✔
142

143

144
def test_failing(rule_runner: RuleRunner) -> None:
3✔
145
    rule_runner.write_files({"f.py": BAD_FILE, "BUILD": "python_sources(name='t')"})
×
146
    tgt = rule_runner.get_target(Address("", target_name="t", relative_file_path="f.py"))
×
147
    fix_result, lint_result, fmt_result = run_ruff(rule_runner, [tgt])
×
148
    assert lint_result.exit_code == 1
×
149
    assert fix_result.stdout == "Found 1 error (1 fixed, 0 remaining).\n"
×
150
    assert fix_result.stderr == ""
×
151
    assert fix_result.did_change
×
152
    assert fix_result.output == rule_runner.make_snapshot({"f.py": GOOD_FILE})
×
153
    assert not fmt_result.did_change
×
154
    assert fmt_result.output == rule_runner.make_snapshot({"f.py": BAD_FILE})
×
155

156

157
def test_multiple_targets(rule_runner: RuleRunner) -> None:
3✔
158
    rule_runner.write_files(
×
159
        {
160
            "good.py": GOOD_FILE,
161
            "bad.py": BAD_FILE,
162
            "unformatted.py": UNFORMATTED_FILE,
163
            "BUILD": "python_sources(name='t')",
164
        }
165
    )
166
    tgts = [
×
167
        rule_runner.get_target(Address("", target_name="t", relative_file_path="good.py")),
168
        rule_runner.get_target(Address("", target_name="t", relative_file_path="bad.py")),
169
        rule_runner.get_target(Address("", target_name="t", relative_file_path="unformatted.py")),
170
    ]
171
    fix_result, lint_result, fmt_result = run_ruff(rule_runner, tgts)
×
172
    assert lint_result.exit_code == 1
×
173
    assert fix_result.output == rule_runner.make_snapshot(
×
174
        {"good.py": GOOD_FILE, "bad.py": GOOD_FILE, "unformatted.py": UNFORMATTED_FILE}
175
    )
176
    assert fix_result.did_change is True
×
177
    assert fmt_result.output == rule_runner.make_snapshot(
×
178
        {"good.py": GOOD_FILE, "bad.py": BAD_FILE, "unformatted.py": GOOD_FILE}
179
    )
180
    assert fmt_result.did_change is True
×
181

182

183
def test_skip_field(rule_runner: RuleRunner) -> None:
3✔
184
    rule_runner.write_files(
×
185
        {
186
            "good.py": GOOD_FILE,
187
            "bad.py": BAD_FILE,
188
            "unformatted.py": UNFORMATTED_FILE,
189
            "BUILD": "python_sources(name='t', skip_ruff=True)",
190
        }
191
    )
192
    tgts = [
×
193
        rule_runner.get_target(Address("", target_name="t", relative_file_path="good.py")),
194
        rule_runner.get_target(Address("", target_name="t", relative_file_path="bad.py")),
195
        rule_runner.get_target(Address("", target_name="t", relative_file_path="unformatted.py")),
196
    ]
197
    for tgt in tgts:
×
198
        assert tgt.get(SkipRuffField).value is True
×
199

200
    fix_result, lint_result, fmt_result = run_ruff(rule_runner, tgts)
×
201

202
    assert lint_result.exit_code == 0
×
203
    assert fix_result.output == rule_runner.make_snapshot({})
×
204
    assert fix_result.did_change is False
×
205
    assert fmt_result.output == rule_runner.make_snapshot({})
×
206
    assert fmt_result.did_change is False
×
207

208

209
def test_skip_check_field(rule_runner: RuleRunner) -> None:
3✔
210
    rule_runner.write_files(
×
211
        {
212
            "good.py": GOOD_FILE,
213
            "bad.py": BAD_FILE,
214
            "unformatted.py": UNFORMATTED_FILE,
215
            "BUILD": "python_sources(name='t', skip_ruff_check=True)",
216
        }
217
    )
218
    tgts = [
×
219
        rule_runner.get_target(Address("", target_name="t", relative_file_path="good.py")),
220
        rule_runner.get_target(Address("", target_name="t", relative_file_path="bad.py")),
221
        rule_runner.get_target(Address("", target_name="t", relative_file_path="unformatted.py")),
222
    ]
223
    for tgt in tgts:
×
224
        assert tgt.get(SkipRuffCheckField).value is True
×
225

226
    fix_result, lint_result, fmt_result = run_ruff(rule_runner, tgts)
×
227

228
    assert lint_result.exit_code == 0
×
229
    assert fix_result.output == rule_runner.make_snapshot({})
×
230
    assert fix_result.did_change is False
×
231
    assert fmt_result.output == rule_runner.make_snapshot(
×
232
        {"good.py": GOOD_FILE, "bad.py": BAD_FILE, "unformatted.py": GOOD_FILE}
233
    )
234
    assert fmt_result.did_change is True
×
235

236

237
def test_skip_format_field(rule_runner: RuleRunner) -> None:
3✔
238
    rule_runner.write_files(
×
239
        {
240
            "good.py": GOOD_FILE,
241
            "bad.py": BAD_FILE,
242
            "unformatted.py": UNFORMATTED_FILE,
243
            "BUILD": "python_sources(name='t', skip_ruff_format=True)",
244
        }
245
    )
246
    tgts = [
×
247
        rule_runner.get_target(Address("", target_name="t", relative_file_path="good.py")),
248
        rule_runner.get_target(Address("", target_name="t", relative_file_path="bad.py")),
249
        rule_runner.get_target(Address("", target_name="t", relative_file_path="unformatted.py")),
250
    ]
251
    for tgt in tgts:
×
252
        assert tgt.get(SkipRuffFormatField).value is True
×
253

254
    fix_result, lint_result, fmt_result = run_ruff(rule_runner, tgts)
×
255

256
    assert lint_result.exit_code == 1
×
257
    assert fix_result.output == rule_runner.make_snapshot(
×
258
        {"good.py": GOOD_FILE, "bad.py": GOOD_FILE, "unformatted.py": UNFORMATTED_FILE}
259
    )
260
    assert fix_result.did_change is True
×
261
    assert fmt_result.output == rule_runner.make_snapshot({})
×
262
    assert fmt_result.did_change is False
×
263

264

265
@pytest.mark.parametrize(
3✔
266
    "file_path,config_path,extra_args,should_change",
267
    (
268
        [Path("f.py"), Path("pyproject.toml"), [], False],
269
        [Path("f.py"), Path("ruff.toml"), [], False],
270
        [Path("custom/f.py"), Path("custom/ruff.toml"), [], False],
271
        [Path("custom/f.py"), Path("custom/pyproject.toml"), [], False],
272
        [Path("f.py"), Path("custom/ruff.toml"), ["--ruff-config=custom/ruff.toml"], False],
273
        [Path("f.py"), Path("custom/ruff.toml"), [], True],
274
    ),
275
)
276
def test_config_file(
3✔
277
    rule_runner: RuleRunner,
278
    file_path: Path,
279
    config_path: Path,
280
    extra_args: list[str],
281
    should_change: bool,
282
) -> None:
283
    hierarchy = "[tool.ruff]\n" if config_path.stem == "pyproject" else ""
×
284
    rule_runner.write_files(
×
285
        {
286
            file_path: BAD_FILE,
287
            file_path.parent / "BUILD": "python_sources()",
288
            config_path: f'{hierarchy}ignore = ["F541"]',
289
        }
290
    )
291
    spec_path = str(file_path.parent).replace(".", "")
×
292
    rel_file_path = file_path.relative_to(*file_path.parts[:1]) if spec_path else file_path
×
293
    addr = Address(spec_path, relative_file_path=str(rel_file_path))
×
294
    tgt = rule_runner.get_target(addr)
×
295
    fix_result, lint_result, fmt_result = run_ruff(rule_runner, [tgt], extra_args=extra_args)
×
296
    assert lint_result.exit_code == bool(should_change)
×
297
    assert fix_result.did_change is should_change
×
298

299

300
def test_report_file(rule_runner: RuleRunner) -> None:
3✔
301
    rule_runner.write_files({"f.py": BAD_FILE, "BUILD": "python_sources(name='t')"})
×
302
    tgt = rule_runner.get_target(Address("", target_name="t", relative_file_path="f.py"))
×
303
    fix_result, lint_result, fmt_result = run_ruff(
×
304
        rule_runner,
305
        [tgt],
306
        extra_args=["--ruff-args='--output-file=reports/foo.txt'"],
307
    )
308
    assert lint_result.exit_code == 1
×
309
    report_files = rule_runner.request(DigestContents, [lint_result.report])
×
310
    assert len(report_files) == 1
×
311
    assert "f.py:1:5" in report_files[0].content.decode()
×
312
    assert "F541" in report_files[0].content.decode()
×
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