• 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

40.65
/src/python/pants/backend/tools/semgrep/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 collections.abc import Sequence
3✔
7
from textwrap import dedent
3✔
8

9
import pytest
3✔
10

11
from pants.core.goals.lint import LintResult, Partitions
3✔
12
from pants.core.target_types import FileTarget
3✔
13
from pants.core.util_rules import source_files
3✔
14
from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest
3✔
15
from pants.engine.addresses import Address
3✔
16
from pants.engine.internals.native_engine import EMPTY_DIGEST
3✔
17
from pants.engine.target import Target
3✔
18
from pants.testutil.python_interpreter_selection import all_major_minor_python_versions
3✔
19
from pants.testutil.rule_runner import QueryRule, RuleRunner
3✔
20

21
from .rules import PartitionMetadata, SemgrepLintRequest
3✔
22
from .rules import rules as semgrep_rules
3✔
23
from .subsystem import SemgrepFieldSet, SemgrepSubsystem
3✔
24
from .subsystem import rules as semgrep_subsystem_rules
3✔
25

26
DIR = "src"
3✔
27
FILE = "file.txt"
3✔
28

29
# https://semgrep.dev/docs/cli-reference/#exit-codes
30
SEMGREP_ERROR_FAILURE_RETURN_CODE = 1
3✔
31

32
GOOD_FILE = "nothing_bad"
3✔
33
BAD_FILE = "bad_pattern\nalso_bad"
3✔
34
RULES = dedent(
3✔
35
    """\
36
    rules:
37
    - id: find-bad-pattern
38
      patterns:
39
        - pattern: bad_pattern
40
      message: >-
41
        bad pattern found!
42
      languages: [generic]
43
      severity: ERROR
44
      paths:
45
        # 'generic' means this finds itself
46
        exclude:
47
         - '*.yml'
48
    """
49
)
50
RULES2 = dedent(
3✔
51
    """\
52
    rules:
53
    - id: find-another-bad-pattern
54
      patterns:
55
        - pattern: also_bad
56
      message: >-
57
        second bad found!
58
      languages: [generic]
59
      severity: ERROR
60
      paths:
61
        # 'generic' means this finds itself
62
        exclude:
63
         - '*.yml'
64
    """
65
)
66

67
SINGLE_FILE_BUILD = dedent(
3✔
68
    f"""\
69
    file(name="f", source="{FILE}")
70
    """
71
)
72

73
BAD_FILE_LAYOUT = {
3✔
74
    f"{DIR}/{FILE}": BAD_FILE,
75
    f"{DIR}/.semgrep.yml": RULES,
76
    f"{DIR}/BUILD": SINGLE_FILE_BUILD,
77
}
78
GOOD_FILE_LAYOUT = {
3✔
79
    f"{DIR}/{FILE}": GOOD_FILE,
80
    f"{DIR}/.semgrep.yml": RULES,
81
    f"{DIR}/BUILD": SINGLE_FILE_BUILD,
82
}
83

84

85
@pytest.fixture
3✔
86
def rule_runner() -> RuleRunner:
3✔
87
    return RuleRunner(
3✔
88
        rules=[
89
            *semgrep_rules(),
90
            *semgrep_subsystem_rules(),
91
            *source_files.rules(),
92
            QueryRule(Partitions, (SemgrepLintRequest.PartitionRequest,)),
93
            QueryRule(LintResult, (SemgrepLintRequest.Batch,)),
94
            QueryRule(SourceFiles, (SourceFilesRequest,)),
95
        ],
96
        target_types=[FileTarget],
97
    )
98

99

100
def run_semgrep(
3✔
101
    rule_runner: RuleRunner,
102
    targets: list[Target],
103
    *,
104
    extra_args: Sequence[str] = (),
105
    use_default_semgrep_args: bool = False,
106
) -> tuple[LintResult, ...]:
107
    if not use_default_semgrep_args:
3✔
108
        # clear out the default --quiet so that we can check the 'Ran ... rules on ... files:
109
        # ... findings' output
110
        extra_args = ("--semgrep-args=[]", *extra_args)
3✔
111

112
    rule_runner.set_options(
3✔
113
        [
114
            "--backend-packages=pants.backend.tools.semgrep",
115
            f"--python-interpreter-constraints={SemgrepSubsystem.default_interpreter_constraints!r}",
116
            *extra_args,
117
        ],
118
        env_inherit={"PATH", "PYENV_ROOT", "HOME"},
119
    )
120
    partitions = rule_runner.request(
3✔
121
        Partitions[SemgrepFieldSet, PartitionMetadata],
122
        [
123
            SemgrepLintRequest.PartitionRequest(
124
                tuple(SemgrepFieldSet.create(tgt) for tgt in targets)
125
            )
126
        ],
127
    )
128

129
    return tuple(
3✔
130
        rule_runner.request(
131
            LintResult,
132
            [SemgrepLintRequest.Batch("", partition.elements, partition.metadata)],
133
        )
134
        for partition in partitions
135
    )
136

137

138
def assert_success(
3✔
139
    rule_runner: RuleRunner, target: Target, *, extra_args: Sequence[str] = ()
140
) -> None:
141
    results = run_semgrep(rule_runner, [target], extra_args=extra_args)
×
142

143
    assert len(results) == 1
×
144
    result = results[0]
×
145
    assert result.stdout == ""
×
146
    assert "Ran 1 rule on 1 file: 0 findings" in result.stderr
×
147
    assert result.exit_code == 0
×
148
    assert result.report == EMPTY_DIGEST
×
149

150

151
@pytest.mark.parametrize(
3✔
152
    "major_minor_interpreter",
153
    all_major_minor_python_versions(SemgrepSubsystem.default_interpreter_constraints),
154
)
155
def test_passing(rule_runner: RuleRunner, major_minor_interpreter: str) -> None:
3✔
156
    rule_runner.write_files(GOOD_FILE_LAYOUT)
×
157
    tgt = rule_runner.get_target(Address(DIR, target_name="f"))
×
158
    assert_success(
×
159
        rule_runner,
160
        tgt,
161
        extra_args=[f"--python-interpreter-constraints=['=={major_minor_interpreter}.*']"],
162
    )
163

164

165
@pytest.mark.platform_specific_behavior
3✔
166
@pytest.mark.parametrize(
3✔
167
    "files,config_name",
168
    [
169
        pytest.param(
170
            BAD_FILE_LAYOUT,
171
            None,
172
        ),
173
        pytest.param(
174
            {
175
                f"{DIR}/bad.txt": BAD_FILE,
176
                f"{DIR}/BUILD": """file(name="f", source="bad.txt")""",
177
                ".custom_semgrep_file.yml": RULES,
178
            },
179
            ".custom_semgrep_file.yml",
180
            id="via custom config file",
181
        ),
182
    ],
183
)
184
def test_failing(rule_runner: RuleRunner, files: dict[str, str], config_name: str | None) -> None:
3✔
185
    rule_runner.write_files(files)
3✔
186
    tgt = rule_runner.get_target(Address(DIR, target_name="f"))
3✔
187

188
    extra_args = [f"--semgrep-config-name={config_name}"] if config_name else []
3✔
189
    results = run_semgrep(rule_runner, [tgt], extra_args=extra_args)
3✔
190
    assert len(results) == 1
3✔
191
    result = results[0]
3✔
192
    assert "find-bad-pattern" in result.stdout
3✔
193
    assert "Ran 1 rule on 1 file: 1 finding" in result.stderr
3✔
194
    assert result.exit_code == SEMGREP_ERROR_FAILURE_RETURN_CODE
3✔
195
    assert result.report == EMPTY_DIGEST
3✔
196

197

198
def test_multiple_targets(rule_runner: RuleRunner) -> None:
3✔
199
    rule_runner.write_files(
×
200
        {
201
            f"{DIR}/good.txt": GOOD_FILE,
202
            f"{DIR}/bad.txt": BAD_FILE,
203
            f"{DIR}/.semgrep.yml": RULES,
204
            f"{DIR}/BUILD": dedent(
205
                """\
206
                file(name="g", source="good.txt")
207
                file(name="b", source="bad.txt")
208
                """
209
            ),
210
        }
211
    )
212

213
    tgts = [rule_runner.get_target(Address(DIR, target_name=name)) for name in ["g", "b"]]
×
214

215
    results = run_semgrep(
×
216
        rule_runner,
217
        tgts,
218
    )
219
    assert len(results) == 1
×
220
    result = results[0]
×
221
    assert "find-bad-pattern" in result.stdout
×
222
    assert "Ran 1 rule on 2 files: 1 finding" in result.stderr
×
223
    assert result.exit_code == SEMGREP_ERROR_FAILURE_RETURN_CODE
×
224
    assert result.report == EMPTY_DIGEST
×
225

226

227
@pytest.mark.parametrize(
3✔
228
    "files,config_name",
229
    [
230
        pytest.param(
231
            {
232
                **BAD_FILE_LAYOUT,
233
                ".semgrep.yml": RULES2,
234
            },
235
            None,
236
            id="via nesting",
237
        ),
238
        pytest.param(
239
            {
240
                f"{DIR}/bad.txt": BAD_FILE,
241
                f"{DIR}/BUILD": """file(name="f", source="bad.txt")""",
242
                ".semgrep/one.yml": RULES,
243
                ".semgrep/vendored/two.yml": RULES2,
244
            },
245
            None,
246
            id="via .semgrep directory",
247
        ),
248
        pytest.param(
249
            {
250
                f"{DIR}/bad.txt": BAD_FILE,
251
                f"{DIR}/BUILD": """file(name="f", source="bad.txt")""",
252
                ".semgrep/one.yml": RULES,
253
                ".semgrep/vendored/two.yml": RULES2,
254
            },
255
            None,
256
            id="via recursive .semgrep directory",
257
        ),
258
        pytest.param(
259
            {
260
                f"{DIR}/bad.txt": BAD_FILE,
261
                f"{DIR}/BUILD": """file(name="f", source="bad.txt")""",
262
                "custom_semgrep_dir/one.yml": RULES,
263
                "custom_semgrep_dir/vendored/two.yml": RULES2,
264
            },
265
            "custom_semgrep_dir",
266
            id="via custom recursive config directory",
267
        ),
268
    ],
269
)
270
def test_multiple_configs(
3✔
271
    rule_runner: RuleRunner, files: dict[str, str], config_name: str | None
272
) -> None:
273
    rule_runner.write_files(files)
×
274

275
    tgt = rule_runner.get_target(Address(DIR, target_name="f"))
×
276
    extra_args = [f"--semgrep-config-name={config_name}"] if config_name else []
×
277
    results = run_semgrep(rule_runner, [tgt], extra_args=extra_args)
×
278

279
    assert len(results) == 1
×
280
    result = results[0]
×
281
    assert "find-bad-pattern" in result.stdout
×
282
    assert "find-another-bad-pattern" in result.stdout
×
283
    assert "Ran 2 rules on 1 file: 2 findings" in result.stderr
×
284
    assert result.exit_code == SEMGREP_ERROR_FAILURE_RETURN_CODE
×
285
    assert result.report == EMPTY_DIGEST
×
286

287

288
def test_semgrepignore(rule_runner: RuleRunner) -> None:
3✔
289
    rule_runner.write_files({**BAD_FILE_LAYOUT, ".semgrepignore": FILE})
×
290

291
    tgt = rule_runner.get_target(Address(DIR, target_name="f"))
×
292
    results = run_semgrep(rule_runner, [tgt])
×
293

294
    assert len(results) == 1
×
295
    result = results[0]
×
296
    assert result.stdout == ""
×
297
    assert "Ran 1 rule on 0 files: 0 findings" in result.stderr
×
298
    assert result.exit_code == 0
×
299
    assert result.report == EMPTY_DIGEST
×
300

301

302
def test_partition_by_config(rule_runner: RuleRunner) -> None:
3✔
303
    file_dirs = []
×
304

305
    def file___(dir: str) -> dict[str, str]:
×
306
        file_dirs.append(dir)
×
307
        return {
×
308
            f"{dir}/{FILE}": GOOD_FILE,
309
            f"{dir}/BUILD": f"""file(name="f", source="{FILE}")""",
310
        }
311

312
    def semgrep(dir: str) -> dict[str, str]:
×
313
        return {f"{dir}/.semgrep.yml": RULES}
×
314

315
    rule_runner.write_files(
×
316
        {
317
            # 'y'/'n' indicates whether that level has semgrep config
318
            **semgrep("y"),
319
            **file___("y/dir1"),
320
            **file___("y/dir2"),
321
            **file___("y/n/dir1"),
322
            **file___("y/n/dir2"),
323
            **semgrep("y/y"),
324
            **file___("y/y/dir1"),
325
            **file___("y/y/dir2"),
326
            **file___("n"),
327
        }
328
    )
329

330
    field_sets = tuple(
×
331
        SemgrepFieldSet.create(rule_runner.get_target(Address(dir, target_name="f")))
332
        for dir in file_dirs
333
    )
334

335
    partitions = rule_runner.request(
×
336
        Partitions[SemgrepFieldSet, PartitionMetadata],
337
        [SemgrepLintRequest.PartitionRequest(field_sets)],
338
    )
339

340
    sorted_partitions = sorted(
×
341
        (
342
            sorted(field_set.address.spec for field_set in partition.elements),
343
            sorted(str(f) for f in partition.metadata.config_files),
344
        )
345
        for partition in partitions
346
    )
347

348
    assert sorted_partitions == [
×
349
        (
350
            ["y/dir1:f", "y/dir2:f", "y/n/dir1:f", "y/n/dir2:f"],
351
            ["y/.semgrep.yml"],
352
        ),
353
        (
354
            ["y/y/dir1:f", "y/y/dir2:f"],
355
            ["y/.semgrep.yml", "y/y/.semgrep.yml"],
356
        ),
357
        # n: doesn't appear in any partition
358
    ]
359

360

361
def test_skip(rule_runner: RuleRunner) -> None:
3✔
362
    rule_runner.write_files(BAD_FILE_LAYOUT)
×
363
    tgt = rule_runner.get_target(Address(DIR, target_name="f"))
×
364

365
    results = run_semgrep(rule_runner, [tgt], extra_args=["--semgrep-skip"])
×
366
    assert not results
×
367

368

369
@pytest.mark.xfail(
3✔
370
    reason=""" TODO: --semgrep-force does rerun the underlying process, but the LintResult's
371
    contents are the same (same stdout etc.), these are deduped, and thus we cannot detect the
372
    rerun""",
373
    # no point spending time on this
374
    run=False,
375
)
376
def test_force(rule_runner: RuleRunner) -> None:
3✔
377
    rule_runner.write_files(GOOD_FILE_LAYOUT)
×
378
    tgt = rule_runner.get_target(Address(DIR, target_name="f"))
×
379

380
    # Should not receive a memoized result if force=True.
381
    results1 = run_semgrep(rule_runner, [tgt], extra_args=["--semgrep-force"])
×
382
    results2 = run_semgrep(rule_runner, [tgt], extra_args=["--semgrep-force"])
×
383

384
    assert len(results1) == len(results2) == 1
×
385
    assert results1[0].exit_code == results2[0].exit_code == 0
×
386
    assert results1[0] is not results2[0]
×
387

388
    # But should if force=False.
389
    results1 = run_semgrep(rule_runner, [tgt])
×
390
    results2 = run_semgrep(rule_runner, [tgt])
×
391
    assert len(results1) == len(results2) == 1
×
392
    assert results1[0].exit_code == results2[0].exit_code == 0
×
393
    assert results1[0] is results2[0]
×
394

395

396
def test_default_args(rule_runner: RuleRunner) -> None:
3✔
397
    rule_runner.write_files(BAD_FILE_LAYOUT)
×
398
    tgt = rule_runner.get_target(Address(DIR, target_name="f"))
×
399

400
    results = run_semgrep(rule_runner, [tgt], use_default_semgrep_args=True)
×
401
    assert len(results) == 1
×
402
    result = results[0]
×
403
    assert "find-bad-pattern" in result.stdout
×
404
    assert result.stderr == ""
×
405
    assert result.exit_code == SEMGREP_ERROR_FAILURE_RETURN_CODE
×
406
    assert result.report == EMPTY_DIGEST
×
407

408

409
def test_extra_args(rule_runner: RuleRunner) -> None:
3✔
410
    rule_runner.write_files(BAD_FILE_LAYOUT)
×
411
    tgt = rule_runner.get_target(Address(DIR, target_name="f"))
×
412

413
    results = run_semgrep(rule_runner, [tgt], extra_args=["--semgrep-args=--quiet"])
×
414
    assert len(results) == 1
×
415
    result = results[0]
×
416
    assert "find-bad-pattern" in result.stdout
×
417
    assert result.stderr == ""
×
418
    assert result.exit_code == SEMGREP_ERROR_FAILURE_RETURN_CODE
×
419
    assert result.report == EMPTY_DIGEST
×
420

421

422
def test_semgrep_pex_contents_is_ignored(rule_runner: RuleRunner) -> None:
3✔
423
    rule_runner.write_files(
×
424
        {
425
            # Validate that this doesn't traverse into the PEX created for semgrep itself: a
426
            # top-level __main__.py will translate into --include=__main__.py (aka **/__main__.py)
427
            # which will, naively, find the __main__.py in the PEX.
428
            "__main__.py": "",
429
            ".semgrep.yml": RULES,
430
            "BUILD": """file(name="f", source="__main__.py")""",
431
        }
432
    )
433

434
    tgt = rule_runner.get_target(Address("", target_name="f"))
×
435
    results = run_semgrep(rule_runner, [tgt])
×
436

437
    assert len(results) == 1
×
438
    result = results[0]
×
439
    assert result.stdout == ""
×
440
    # Without the --exclude, this would run on 2 files.
441
    assert "Ran 1 rule on 1 file: 0 findings" in result.stderr
×
442
    assert result.exit_code == 0
×
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