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

pantsbuild / pants / 26342152999

23 May 2026 07:59PM UTC coverage: 91.165% (-1.6%) from 92.792%
26342152999

push

github

web-flow
Run Linux ARM CI on Depot runners (#23363)

RunsOn is deprecating their v2 stack, and rather than migrate
to v3 we should use the resources kindly donated by Depot.

GitHub also now has Linux ARM runners, should we need them.

87305 of 95766 relevant lines covered (91.16%)

3.87 hits per line

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

44.44
/src/python/pants/backend/python/lint/bandit/rules_integration_test.py
1
# Copyright 2020 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
from collections.abc import Sequence
2✔
7
from textwrap import dedent
2✔
8

9
import pytest
2✔
10

11
from pants.backend.python import target_types_rules
2✔
12
from pants.backend.python.lint.bandit.rules import BanditRequest
2✔
13
from pants.backend.python.lint.bandit.rules import rules as bandit_rules
2✔
14
from pants.backend.python.lint.bandit.subsystem import BanditFieldSet
2✔
15
from pants.backend.python.lint.bandit.subsystem import rules as bandit_subsystem_rules
2✔
16
from pants.backend.python.target_types import PythonSourcesGeneratorTarget
2✔
17
from pants.backend.python.util_rules.interpreter_constraints import InterpreterConstraints
2✔
18
from pants.core.goals.lint import LintResult, Partitions
2✔
19
from pants.core.util_rules import config_files, source_files
2✔
20
from pants.engine.addresses import Address
2✔
21
from pants.engine.fs import EMPTY_DIGEST, DigestContents
2✔
22
from pants.engine.target import Target
2✔
23
from pants.testutil.python_interpreter_selection import (
2✔
24
    all_major_minor_python_versions,
25
    has_python_version,
26
    skip_unless_python310_and_python311_present,
27
)
28
from pants.testutil.python_rule_runner import PythonRuleRunner
2✔
29
from pants.testutil.rule_runner import QueryRule
2✔
30
from pants.util.resources import read_sibling_resource
2✔
31

32

33
@pytest.fixture
2✔
34
def rule_runner() -> PythonRuleRunner:
2✔
35
    return PythonRuleRunner(
2✔
36
        rules=[
37
            *bandit_rules(),
38
            *bandit_subsystem_rules(),
39
            *source_files.rules(),
40
            *config_files.rules(),
41
            *target_types_rules.rules(),
42
            QueryRule(Partitions, [BanditRequest.PartitionRequest]),
43
            QueryRule(LintResult, [BanditRequest.Batch]),
44
        ],
45
        target_types=[PythonSourcesGeneratorTarget],
46
    )
47

48

49
GOOD_FILE = "hashlib.sha256()\n"
2✔
50
BAD_FILE = "hashlib.md5()\n"  # An insecure hashing function.
2✔
51

52

53
def run_bandit(
2✔
54
    rule_runner: PythonRuleRunner, targets: list[Target], *, extra_args: list[str] | None = None
55
) -> Sequence[LintResult]:
56
    rule_runner.set_options(
2✔
57
        [
58
            "--backend-packages=pants.backend.python.lint.bandit",
59
            *(extra_args or ()),
60
        ],
61
        env_inherit={"PATH", "PYENV_ROOT", "HOME"},
62
    )
63
    partitions = rule_runner.request(
2✔
64
        Partitions[BanditFieldSet, InterpreterConstraints],
65
        [BanditRequest.PartitionRequest(tuple(BanditFieldSet.create(tgt) for tgt in targets))],
66
    )
67
    results = []
2✔
68
    for partition in partitions:
2✔
69
        result = rule_runner.request(
2✔
70
            LintResult,
71
            [BanditRequest.Batch("", partition.elements, partition.metadata)],
72
        )
73
        results.append(result)
2✔
74
    return tuple(results)
2✔
75

76

77
def assert_success(
2✔
78
    rule_runner: PythonRuleRunner, target: Target, *, extra_args: list[str] | None = None
79
) -> None:
80
    result = run_bandit(rule_runner, [target], extra_args=extra_args)
2✔
81
    assert len(result) == 1
2✔
82
    assert result[0].exit_code == 0
2✔
83
    assert "No issues identified." in result[0].stdout.strip()
2✔
84
    assert result[0].report == EMPTY_DIGEST
2✔
85

86

87
@pytest.mark.platform_specific_behavior
2✔
88
@pytest.mark.parametrize(
2✔
89
    "major_minor_interpreter",
90
    all_major_minor_python_versions(["CPython>=3.9,<3.15"]),
91
)
92
def test_passing(rule_runner: PythonRuleRunner, major_minor_interpreter: str) -> None:
2✔
93
    rule_runner.write_files({"f.py": GOOD_FILE, "BUILD": "python_sources(name='t')"})
2✔
94
    tgt = rule_runner.get_target(Address("", target_name="t", relative_file_path="f.py"))
2✔
95
    assert_success(
2✔
96
        rule_runner,
97
        tgt,
98
        extra_args=[f"--python-interpreter-constraints=['=={major_minor_interpreter}.*']"],
99
    )
100

101

102
def test_failing(rule_runner: PythonRuleRunner) -> None:
2✔
103
    rule_runner.write_files({"f.py": BAD_FILE, "BUILD": "python_sources(name='t')"})
×
104
    tgt = rule_runner.get_target(Address("", target_name="t", relative_file_path="f.py"))
×
105
    result = run_bandit(rule_runner, [tgt])
×
106
    assert len(result) == 1
×
107
    assert result[0].exit_code == 1
×
108
    assert "Issue: [B324:hashlib] Use of weak MD5 hash for security." in result[0].stdout
×
109
    assert result[0].report == EMPTY_DIGEST
×
110

111

112
def test_multiple_targets(rule_runner: PythonRuleRunner) -> None:
2✔
113
    rule_runner.write_files(
×
114
        {"good.py": GOOD_FILE, "bad.py": BAD_FILE, "BUILD": "python_sources(name='t')"}
115
    )
116
    tgts = [
×
117
        rule_runner.get_target(Address("", target_name="t", relative_file_path="good.py")),
118
        rule_runner.get_target(Address("", target_name="t", relative_file_path="bad.py")),
119
    ]
120
    result = run_bandit(rule_runner, tgts)
×
121
    assert len(result) == 1
×
122
    assert result[0].exit_code == 1
×
123
    assert "good.py" not in result[0].stdout
×
124
    assert "Issue: [B324:hashlib] Use of weak MD5 hash for security." in result[0].stdout
×
125
    assert result[0].report == EMPTY_DIGEST
×
126

127

128
@skip_unless_python310_and_python311_present
2✔
129
def test_uses_correct_python_version(rule_runner: PythonRuleRunner) -> None:
2✔
130
    rule_runner.write_files(
×
131
        {
132
            "f.py": "eg = ExceptionGroup('', [Exception()])'\n",
133
            "BUILD": dedent(
134
                """\
135
                python_sources(name="py310", interpreter_constraints=["==3.10.*"])
136
                python_sources(name="py311", interpreter_constraints=["==3.11.*"])
137
                """
138
            ),
139
        }
140
    )
141

142
    py310_tgt = rule_runner.get_target(Address("", target_name="py310", relative_file_path="f.py"))
×
143
    py310_result = run_bandit(rule_runner, [py310_tgt])
×
144
    assert len(py310_result) == 1
×
145
    assert py310_result[0].exit_code == 0
×
146
    assert "f.py (syntax error while parsing AST from file)" in py310_result[0].stdout
×
147

148
    py311_tgt = rule_runner.get_target(Address("", target_name="py311", relative_file_path="f.py"))
×
149
    py311_result = run_bandit(rule_runner, [py311_tgt])
×
150
    assert len(py311_result) == 1
×
151
    assert py311_result[0].exit_code == 0
×
152
    assert "No issues identified." in py311_result[0].stdout
×
153

154
    # Test that we partition incompatible targets when passed in a single batch. We expect Py38
155
    # to still fail, but Py39 should pass.
156
    combined_result = run_bandit(rule_runner, [py310_tgt, py311_tgt])
×
157
    assert len(combined_result) == 2
×
158

159
    batched_py310_result, batched_py311_result = sorted(
×
160
        combined_result, key=lambda result: result.stderr
161
    )
162
    assert batched_py310_result.exit_code == 0
×
163
    assert batched_py310_result.partition_description == "['CPython==3.10.*']"
×
164
    assert "f.py (syntax error while parsing AST from file)" in batched_py310_result.stdout
×
165

166
    assert batched_py311_result.exit_code == 0
×
167
    assert batched_py311_result.partition_description == "['CPython==3.11.*']"
×
168
    assert "No issues identified." in batched_py311_result.stdout
×
169

170

171
def test_respects_config_file(rule_runner: PythonRuleRunner) -> None:
2✔
172
    rule_runner.write_files(
×
173
        {
174
            "f.py": BAD_FILE,
175
            "BUILD": "python_sources(name='t')",
176
            ".bandit": "skips: ['B324']",
177
        }
178
    )
179
    tgt = rule_runner.get_target(Address("", target_name="t", relative_file_path="f.py"))
×
180
    assert_success(rule_runner, tgt, extra_args=["--bandit-config=.bandit"])
×
181

182

183
def test_respects_passthrough_args(rule_runner: PythonRuleRunner) -> None:
2✔
184
    rule_runner.write_files({"f.py": BAD_FILE, "BUILD": "python_sources(name='t')"})
×
185
    tgt = rule_runner.get_target(Address("", target_name="t", relative_file_path="f.py"))
×
186
    assert_success(rule_runner, tgt, extra_args=["--bandit-args='--skip=B324'"])
×
187

188

189
def test_skip(rule_runner: PythonRuleRunner) -> None:
2✔
190
    rule_runner.write_files({"f.py": BAD_FILE, "BUILD": "python_sources(name='t')"})
×
191
    tgt = rule_runner.get_target(Address("", target_name="t", relative_file_path="f.py"))
×
192
    result = run_bandit(rule_runner, [tgt], extra_args=["--bandit-skip"])
×
193
    assert not result
×
194

195

196
@pytest.mark.skipif(not (has_python_version("3.11")), reason="Missing Python 3.11")
2✔
197
def test_3rdparty_plugin(rule_runner: PythonRuleRunner) -> None:
2✔
198
    rule_runner.write_files(
×
199
        {
200
            "f.py": "aws_key = 'JalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY'\n",
201
            "BUILD": "python_sources(name='t', interpreter_constraints=['>=3.11,<3.12'])",
202
            "bandit.lock": read_sibling_resource(__name__, "bandit_plugin_test.lock"),
203
        }
204
    )
205
    tgt = rule_runner.get_target(Address("", target_name="t", relative_file_path="f.py"))
×
206
    result = run_bandit(
×
207
        rule_runner,
208
        [tgt],
209
        extra_args=[
210
            "--python-resolves={'bandit':'bandit.lock'}",
211
            "--bandit-install-from-resolve=bandit",
212
        ],
213
    )
214
    assert len(result) == 1
×
215
    assert result[0].exit_code == 1
×
216
    assert "Issue: [C100:hardcoded_aws_key]" in result[0].stdout
×
217
    assert result[0].report == EMPTY_DIGEST
×
218

219

220
def test_report_file(rule_runner: PythonRuleRunner) -> None:
2✔
221
    rule_runner.write_files({"f.py": BAD_FILE, "BUILD": "python_sources(name='t')"})
×
222
    tgt = rule_runner.get_target(Address("", target_name="t", relative_file_path="f.py"))
×
223
    result = run_bandit(
×
224
        rule_runner, [tgt], extra_args=["--bandit-args='--output=reports/output.txt'"]
225
    )
226
    assert len(result) == 1
×
227
    assert result[0].exit_code == 1
×
228
    assert result[0].stdout.strip() == ""
×
229
    report_files = rule_runner.request(DigestContents, [result[0].report])
×
230
    assert len(report_files) == 1
×
231
    assert (
×
232
        "Issue: [B324:hashlib] Use of weak MD5 hash for security."
233
        in report_files[0].content.decode()
234
    )
235

236

237
def test_type_stubs(rule_runner: PythonRuleRunner) -> None:
2✔
238
    rule_runner.write_files(
×
239
        {
240
            "f.pyi": BAD_FILE,
241
            "f.py": GOOD_FILE,
242
            "BUILD": "python_sources(name='t')",
243
        }
244
    )
245
    tgts = [
×
246
        rule_runner.get_target(Address("", target_name="t", relative_file_path="f.py")),
247
        rule_runner.get_target(Address("", target_name="t", relative_file_path="f.pyi")),
248
    ]
249
    result = run_bandit(rule_runner, tgts)
×
250
    assert len(result) == 1
×
251
    assert result[0].exit_code == 1
×
252
    assert "f.py " not in result[0].stdout
×
253
    assert "f.pyi" in result[0].stdout
×
254
    assert "Issue: [B324:hashlib] Use of weak MD5 hash for security." in result[0].stdout
×
255
    assert result[0].report == EMPTY_DIGEST
×
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