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

pantsbuild / pants / 18847018991

27 Oct 2025 03:45PM UTC coverage: 92.254% (+12.0%) from 80.282%
18847018991

Pull #22816

github

web-flow
Merge f1312fa87 into 06e216752
Pull Request #22816: Update Pants internal Python to 3.14

39 of 40 new or added lines in 11 files covered. (97.5%)

382 existing lines in 22 files now uncovered.

89230 of 96722 relevant lines covered (92.25%)

3.72 hits per line

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

51.02
/src/python/pants/backend/adhoc/code_quality_tool_integration_test.py
1
# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3
from textwrap import dedent  # noqa: PNT20
1✔
4

5
import pytest
1✔
6

7
from pants.backend.adhoc.code_quality_tool import (
1✔
8
    CodeQualityToolRuleBuilder,
9
    CodeQualityToolTarget,
10
    CodeQualityToolUnsupportedGoalError,
11
    base_rules,
12
)
13
from pants.backend.adhoc.run_system_binary import rules as run_system_binary_rules
1✔
14
from pants.backend.adhoc.target_types import SystemBinaryTarget
1✔
15
from pants.backend.python import register as register_python
1✔
16
from pants.backend.python.target_types import PythonSourceTarget
1✔
17
from pants.core.goals.fix import Fix
1✔
18
from pants.core.goals.fmt import Fmt
1✔
19
from pants.core.goals.lint import Lint
1✔
20
from pants.core.register import rules as core_rules
1✔
21
from pants.core.target_types import FileTarget
1✔
22
from pants.core.util_rules import source_files
1✔
23
from pants.engine import process
1✔
24
from pants.testutil.rule_runner import RuleRunner
1✔
25

26

27
def test_error_on_unrecognized_goal():
1✔
28
    with pytest.raises(CodeQualityToolUnsupportedGoalError):
1✔
29
        CodeQualityToolRuleBuilder(
1✔
30
            goal="package", target="build-support:flake8_tool", name="Flake8", scope="flake8_tool"
31
        )
32

33

34
def make_rule_runner(*cfgs: CodeQualityToolRuleBuilder):
1✔
35
    rules = [
1✔
36
        *source_files.rules(),
37
        *core_rules(),
38
        *process.rules(),
39
        *register_python.rules(),
40
        *run_system_binary_rules(),
41
        *base_rules(),
42
    ]
43
    for cfg in cfgs:
1✔
44
        rules.extend(cfg.rules())
1✔
45

46
    return RuleRunner(
1✔
47
        target_types=[
48
            CodeQualityToolTarget,
49
            FileTarget,
50
            PythonSourceTarget,
51
            SystemBinaryTarget,
52
        ],
53
        rules=rules,
54
    )
55

56

57
def test_lint_tool():
1✔
58
    cfg = CodeQualityToolRuleBuilder(
1✔
59
        goal="lint", target="build-support:no_badcode_tool", name="No Bad Code", scope="nobadcode"
60
    )
61

62
    rule_runner = make_rule_runner(cfg)
1✔
63

64
    # linter is a python script that detects the presence
65
    # of a configurable list of problem strings
66
    rule_runner.write_files(
1✔
67
        {
68
            "build-support/BUILD": dedent(
69
                """
70
            python_source(name="no_badcode", source="no_badcode.py")
71
            file(name="badcode_conf", source="badcode.conf")
72

73
            code_quality_tool(
74
                name="no_badcode_tool",
75
                runnable=":no_badcode",
76
                execution_dependencies=[":badcode_conf"],
77
                file_glob_include=["**/*.py"],
78
                file_glob_exclude=["messy_ignored_dir/**", "build-support/**"],
79
                args=["build-support/badcode.conf"],
80
            )
81
            """
82
            ),
83
            "build-support/no_badcode.py": dedent(
84
                """
85
            import sys
86

87
            config_file = sys.argv[1]
88
            with open(config_file) as cfgfile:
89
                badcode_strings = cfgfile.read().strip().split(",")
90

91
            failed = False
92
            for fpath in sys.argv[2:]:
93
                with open(fpath) as f:
94
                    for i, line in enumerate(f):
95
                        for badcode_string in badcode_strings:
96
                            if badcode_string in line:
97
                                print(f"{fpath}:{i + 1} found {badcode_string}")
98
                                failed = True
99
            if failed:
100
                sys.exit(1)
101
            """
102
            ),
103
            "build-support/badcode.conf": "badcode,brokencode,sillycode",
104
            "good_file.py": "okcode = 5",
105
            "messy_ignored_dir/messy_file.py": "brokencode = 10",
106
            "not_a_dot_py_file.md": "This is sillycode",
107
        }
108
    )
109

110
    res = rule_runner.run_goal_rule(Lint, args=["::"])
1✔
UNCOV
111
    assert res.exit_code == 0
×
UNCOV
112
    assert "nobadcode succeeded" in res.stderr
×
113

UNCOV
114
    res = rule_runner.run_goal_rule(Lint, args=["good_file.py"])
×
UNCOV
115
    assert res.exit_code == 0
×
UNCOV
116
    assert "nobadcode succeeded" in res.stderr
×
117

UNCOV
118
    rule_runner.write_files({"bad_file.py": "brokencode = 5\n"})
×
119

UNCOV
120
    res = rule_runner.run_goal_rule(Lint, args=["::"])
×
UNCOV
121
    assert res.exit_code == 1
×
UNCOV
122
    assert "nobadcode failed" in res.stderr
×
123

UNCOV
124
    res = rule_runner.run_goal_rule(Lint, args=["bad_file.py"])
×
UNCOV
125
    assert res.exit_code == 1
×
UNCOV
126
    assert "nobadcode failed" in res.stderr
×
127

UNCOV
128
    res = rule_runner.run_goal_rule(Lint, args=["good_file.py"])
×
UNCOV
129
    assert res.exit_code == 0
×
UNCOV
130
    assert "nobadcode succeeded" in res.stderr
×
131

132

133
def test_fix_tool():
1✔
134
    cfg = CodeQualityToolRuleBuilder(
1✔
135
        goal="fix", target="//:bad_to_good_tool", name="Bad to Good", scope="badtogood"
136
    )
137

138
    rule_runner = make_rule_runner(cfg)
1✔
139

140
    # Fixer replaces the string 'badcode' with 'goodcode'
141
    rule_runner.write_files(
1✔
142
        {
143
            "BUILD": dedent(
144
                """
145
            python_source(name="bad_to_good", source="bad_to_good.py")
146

147
            code_quality_tool(
148
                name="bad_to_good_tool",
149
                runnable=":bad_to_good",
150
                file_glob_include=["**/*.py"],
151
                file_glob_exclude=["bad_to_good.py"],
152
            )
153
            """
154
            ),
155
            "bad_to_good.py": dedent(
156
                """
157
            import sys
158

159
            for fpath in sys.argv[1:]:
160
                with open(fpath) as f:
161
                    contents = f.read()
162
                if 'badcode' in contents:
163
                    with open(fpath, 'w') as f:
164
                        f.write(contents.replace('badcode', 'goodcode'))
165
                """
166
            ),
167
            "good_fmt.py": "thisisfine = 5\n",
168
            "needs_repair.py": "badcode = 10\n",
169
        }
170
    )
171

172
    res = rule_runner.run_goal_rule(Lint, args=["::"])
1✔
UNCOV
173
    assert res.exit_code == 1
×
UNCOV
174
    assert "badtogood failed" in res.stderr
×
175

UNCOV
176
    res = rule_runner.run_goal_rule(Fix, args=["::"])
×
UNCOV
177
    assert res.exit_code == 0
×
UNCOV
178
    assert "badtogood made changes" in res.stderr
×
179

UNCOV
180
    assert "goodcode = 10\n" == rule_runner.read_file("needs_repair.py")
×
181

UNCOV
182
    res = rule_runner.run_goal_rule(Lint, args=["::"])
×
UNCOV
183
    assert res.exit_code == 0
×
UNCOV
184
    assert "badtogood succeeded" in res.stderr
×
185

186

187
def test_several_formatters():
1✔
188
    bad_to_good_cfg = CodeQualityToolRuleBuilder(
1✔
189
        goal="fmt", target="//:bad_to_good_tool", name="Bad to Good", scope="badtogood"
190
    )
191

192
    underscoreit_cfg = CodeQualityToolRuleBuilder(
1✔
193
        goal="fmt", target="//:underscore_it_tool", name="Underscore It", scope="underscoreit"
194
    )
195

196
    rule_runner = make_rule_runner(bad_to_good_cfg, underscoreit_cfg)
1✔
197

198
    # One formatter replaces the string 'badcode' with 'goodcode'
199
    # The other adds an underscore to 'goodcode' -> 'good_code'
200
    rule_runner.write_files(
1✔
201
        {
202
            "BUILD": dedent(
203
                """
204
            python_source(name="bad_to_good", source="bad_to_good.py")
205
            python_source(name="underscore_it", source="underscore_it.py")
206

207
            code_quality_tool(
208
                name="bad_to_good_tool",
209
                runnable=":bad_to_good",
210
                file_glob_include=["**/*.py"],
211
                file_glob_exclude=["underscore_it.py", "bad_to_good.py"],
212
            )
213

214
            code_quality_tool(
215
                name="underscore_it_tool",
216
                runnable=":underscore_it",
217
                file_glob_include=["**/*.py"],
218
                file_glob_exclude=["underscore_it.py", "bad_to_good.py"],
219
            )
220
            """
221
            ),
222
            "bad_to_good.py": dedent(
223
                """
224
            import sys
225

226
            for fpath in sys.argv[1:]:
227
                with open(fpath) as f:
228
                    contents = f.read()
229
                if 'badcode' in contents:
230
                    with open(fpath, 'w') as f:
231
                        f.write(contents.replace('badcode', 'goodcode'))
232
                """
233
            ),
234
            "underscore_it.py": dedent(
235
                """
236
            import sys
237

238
            for fpath in sys.argv[1:]:
239
                with open(fpath) as f:
240
                    contents = f.read()
241
                if 'goodcode' in contents:
242
                    with open(fpath, 'w') as f:
243
                        f.write(contents.replace('goodcode', 'good_code'))
244
                """
245
            ),
246
            "needs_repair.py": "badcode = 10\n",
247
        }
248
    )
249

250
    res = rule_runner.run_goal_rule(Lint, args=["::"])
1✔
UNCOV
251
    assert res.exit_code == 1
×
UNCOV
252
    assert "badtogood failed" in res.stderr
×
UNCOV
253
    assert "underscoreit succeeded" in res.stderr
×
254

UNCOV
255
    res = rule_runner.run_goal_rule(Fmt, args=["::"])
×
UNCOV
256
    assert res.exit_code == 0
×
UNCOV
257
    assert "badtogood made changes" in res.stderr
×
UNCOV
258
    assert "underscoreit made changes" in res.stderr
×
259

UNCOV
260
    assert "good_code = 10\n" == rule_runner.read_file("needs_repair.py")
×
261

UNCOV
262
    res = rule_runner.run_goal_rule(Lint, args=["::"])
×
UNCOV
263
    assert res.exit_code == 0
×
UNCOV
264
    assert "badtogood succeeded" in res.stderr
×
UNCOV
265
    assert "underscoreit succeeded" in res.stderr
×
266

UNCOV
267
    rule_runner.write_files({"only_fix_underscores.py": "goodcode = 5\nbadcode = 10\n"})
×
UNCOV
268
    res = rule_runner.run_goal_rule(Fmt, args=["--only=underscoreit", "only_fix_underscores.py"])
×
UNCOV
269
    assert res.exit_code == 0
×
UNCOV
270
    assert "underscoreit made changes" in res.stderr
×
UNCOV
271
    assert "badtogood" not in res.stderr
×
UNCOV
272
    assert "good_code = 5\nbadcode = 10\n" == rule_runner.read_file("only_fix_underscores.py")
×
273

UNCOV
274
    rule_runner.write_files({"do_not_underscore.py": "goodcode = 50\nbadcode = 100\n"})
×
275

UNCOV
276
    res = rule_runner.run_goal_rule(
×
277
        Fmt, global_args=["--underscoreit-skip"], args=["do_not_underscore.py"]
278
    )
UNCOV
279
    assert res.exit_code == 0
×
UNCOV
280
    assert "badtogood made changes" in res.stderr
×
UNCOV
281
    assert "underscoreit" not in res.stderr
×
UNCOV
282
    assert "goodcode = 50\ngoodcode = 100\n" == rule_runner.read_file("do_not_underscore.py")
×
283

284

285
def test_execution_dependencies_and_runnable_dependencies():
1✔
286
    input_contents = "input contents\n"
1✔
287
    output_contents = "original output contents"
1✔
288
    # confirm that comparing these strings confirms that the test behaved as expected
289
    assert input_contents != output_contents
1✔
290

291
    cfg = CodeQualityToolRuleBuilder(
1✔
292
        goal="fmt", target="b:overwriter", name="Overwriter", scope="overwriter"
293
    )
294

295
    # Formatter that copies the `b/input.txt` file over `b/output.txt` file via bash (relying on
296
    # there being only one file, and a workaround for #19103 meaning we can't use `cp` as a system
297
    # binary)
298
    rule_runner = make_rule_runner(cfg)
1✔
299
    rule_runner.write_files(
1✔
300
        {
301
            # put the runnable in its own directory, so we're sure that the dependencies are
302
            # resolved relative to the code_quality_tool target
303
            "a/BUILD": """system_binary(name="bash", binary_name="bash")""",
304
            "b/BUILD": dedent(
305
                """
306
                system_binary(name="renamed_cat", binary_name="cat")
307
                file(name="input", source="input.txt")
308
                file(name="output", source="output.txt")
309

310
                code_quality_tool(
311
                    name="overwriter",
312
                    runnable="a:bash",
313
                    args=["-c", "renamed_cat b/input.txt > $1", "ignored"],
314
                    execution_dependencies=[":input"],
315
                    runnable_dependencies=[":renamed_cat"],
316
                    file_glob_include=["**/*.txt"],
317
                )
318
                """
319
            ),
320
            "b/input.txt": input_contents,
321
            "b/output.txt": output_contents,
322
        }
323
    )
324
    res = rule_runner.run_goal_rule(Fmt, args=["b/output.txt"])
1✔
325
    assert res.exit_code == 0
1✔
326
    assert "overwriter made changes" in res.stderr
1✔
327
    assert input_contents == rule_runner.read_file("b/output.txt")
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

© 2025 Coveralls, Inc