Coverage for tests / unit / tools / semgrep / test_options.py: 100%
88 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Unit tests for Semgrep plugin options."""
3from __future__ import annotations
5import pytest
6from assertpy import assert_that
8from lintro.tools.definitions.semgrep import (
9 SEMGREP_DEFAULT_CONFIG,
10 SEMGREP_DEFAULT_TIMEOUT,
11 SemgrepPlugin,
12)
14# =============================================================================
15# Tests for SemgrepPlugin default options
16# =============================================================================
19@pytest.mark.parametrize(
20 ("option_name", "expected_value"),
21 [
22 ("timeout", SEMGREP_DEFAULT_TIMEOUT),
23 ("config", SEMGREP_DEFAULT_CONFIG),
24 ("exclude", None),
25 ("include", None),
26 ("severity", None),
27 ("timeout_threshold", None),
28 ("jobs", None),
29 ("verbose", False),
30 ("quiet", False),
31 ],
32 ids=[
33 "timeout_equals_default",
34 "config_equals_auto",
35 "exclude_is_none",
36 "include_is_none",
37 "severity_is_none",
38 "timeout_threshold_is_none",
39 "jobs_is_none",
40 "verbose_is_false",
41 "quiet_is_false",
42 ],
43)
44def test_default_options_values(
45 semgrep_plugin: SemgrepPlugin,
46 option_name: str,
47 expected_value: object,
48) -> None:
49 """Default options have correct values.
51 Args:
52 semgrep_plugin: The SemgrepPlugin instance to test.
53 option_name: The name of the option to check.
54 expected_value: The expected value for the option.
55 """
56 assert_that(
57 semgrep_plugin.definition.default_options[option_name],
58 ).is_equal_to(expected_value)
61# =============================================================================
62# Tests for SemgrepPlugin.set_options method - valid options
63# =============================================================================
66@pytest.mark.parametrize(
67 ("option_name", "option_value"),
68 [
69 ("config", "auto"),
70 ("config", "p/python"),
71 ("config", "p/javascript"),
72 ("config", "/path/to/rules.yaml"),
73 ("exclude", ["test_*.py", "vendor/*"]),
74 ("include", ["src/*.py", "lib/*.py"]),
75 ("severity", "INFO"),
76 ("severity", "WARNING"),
77 ("severity", "ERROR"),
78 ("jobs", 4),
79 ("jobs", 1),
80 ("timeout_threshold", 30),
81 ("timeout_threshold", 0),
82 ("verbose", True),
83 ("quiet", True),
84 ],
85 ids=[
86 "config_auto",
87 "config_python_ruleset",
88 "config_javascript_ruleset",
89 "config_custom_path",
90 "exclude_patterns",
91 "include_patterns",
92 "severity_info",
93 "severity_warning",
94 "severity_error",
95 "jobs_4",
96 "jobs_1",
97 "timeout_threshold_30",
98 "timeout_threshold_0",
99 "verbose_true",
100 "quiet_true",
101 ],
102)
103def test_set_options_valid(
104 semgrep_plugin: SemgrepPlugin,
105 option_name: str,
106 option_value: object,
107) -> None:
108 """Set valid options correctly.
110 Args:
111 semgrep_plugin: The SemgrepPlugin instance to test.
112 option_name: The name of the option to set.
113 option_value: The value to set for the option.
114 """
115 semgrep_plugin.set_options(**{option_name: option_value}) # type: ignore[arg-type]
117 # Severity is normalized to uppercase
118 expected: object
119 if option_name == "severity" and isinstance(option_value, str):
120 expected = option_value.upper()
121 else:
122 expected = option_value
124 assert_that(semgrep_plugin.options.get(option_name)).is_equal_to(expected)
127def test_set_options_severity_lowercase(semgrep_plugin: SemgrepPlugin) -> None:
128 """Set severity option with lowercase value normalizes to uppercase.
130 Args:
131 semgrep_plugin: The SemgrepPlugin instance to test.
132 """
133 semgrep_plugin.set_options(severity="info")
134 assert_that(semgrep_plugin.options.get("severity")).is_equal_to("INFO")
137# =============================================================================
138# Tests for SemgrepPlugin.set_options method - invalid types
139# =============================================================================
142@pytest.mark.parametrize(
143 ("option_name", "invalid_value", "error_match"),
144 [
145 ("severity", "CRITICAL", "Invalid Semgrep severity"),
146 ("severity", "invalid", "Invalid Semgrep severity"),
147 ("jobs", 0, "jobs must be a positive integer"),
148 ("jobs", -1, "jobs must be a positive integer"),
149 ("jobs", "four", "jobs must be a positive integer"),
150 ("timeout_threshold", -1, "timeout_threshold must be a non-negative integer"),
151 (
152 "timeout_threshold",
153 "slow",
154 "timeout_threshold must be a non-negative integer",
155 ),
156 ("exclude", "*.py", "exclude must be a list"),
157 ("include", "*.py", "include must be a list"),
158 ("config", 123, "config must be a string"),
159 ],
160 ids=[
161 "invalid_severity_critical",
162 "invalid_severity_unknown",
163 "invalid_jobs_zero",
164 "invalid_jobs_negative",
165 "invalid_jobs_type",
166 "invalid_timeout_threshold_negative",
167 "invalid_timeout_threshold_type",
168 "invalid_exclude_type",
169 "invalid_include_type",
170 "invalid_config_type",
171 ],
172)
173def test_set_options_invalid_type(
174 semgrep_plugin: SemgrepPlugin,
175 option_name: str,
176 invalid_value: object,
177 error_match: str,
178) -> None:
179 """Raise ValueError for invalid option types.
181 Args:
182 semgrep_plugin: The SemgrepPlugin instance to test.
183 option_name: The name of the option being tested.
184 invalid_value: An invalid value for the option.
185 error_match: Pattern expected in the error message.
186 """
187 with pytest.raises(ValueError, match=error_match):
188 semgrep_plugin.set_options(**{option_name: invalid_value}) # type: ignore[arg-type]
191# =============================================================================
192# Tests for SemgrepPlugin._build_check_command method
193# =============================================================================
196def test_build_check_command_basic(semgrep_plugin: SemgrepPlugin) -> None:
197 """Build basic command with default options.
199 Args:
200 semgrep_plugin: The SemgrepPlugin instance to test.
201 """
202 cmd = semgrep_plugin._build_check_command(files=["src/"])
204 assert_that(cmd).contains("semgrep")
205 assert_that(cmd).contains("scan")
206 assert_that(cmd).contains("--json")
207 assert_that(cmd).contains("--config")
208 # Default config is "auto"
209 config_idx = cmd.index("--config")
210 assert_that(cmd[config_idx + 1]).is_equal_to("auto")
211 assert_that(cmd).contains("src/")
214def test_build_check_command_with_config(semgrep_plugin: SemgrepPlugin) -> None:
215 """Build command with custom config option.
217 Args:
218 semgrep_plugin: The SemgrepPlugin instance to test.
219 """
220 semgrep_plugin.set_options(config="p/python")
221 cmd = semgrep_plugin._build_check_command(files=["app.py"])
223 assert_that(cmd).contains("--config")
224 config_idx = cmd.index("--config")
225 assert_that(cmd[config_idx + 1]).is_equal_to("p/python")
228def test_build_check_command_with_exclude(semgrep_plugin: SemgrepPlugin) -> None:
229 """Build command with exclude patterns.
231 Args:
232 semgrep_plugin: The SemgrepPlugin instance to test.
233 """
234 semgrep_plugin.set_options(exclude=["tests/*", "vendor/*"])
235 cmd = semgrep_plugin._build_check_command(files=["src/"])
237 # Each exclude pattern should have its own --exclude flag
238 exclude_indices = [i for i, x in enumerate(cmd) if x == "--exclude"]
239 assert_that(exclude_indices).is_length(2)
240 assert_that(cmd[exclude_indices[0] + 1]).is_equal_to("tests/*")
241 assert_that(cmd[exclude_indices[1] + 1]).is_equal_to("vendor/*")
244def test_build_check_command_with_include(semgrep_plugin: SemgrepPlugin) -> None:
245 """Build command with include patterns.
247 Args:
248 semgrep_plugin: The SemgrepPlugin instance to test.
249 """
250 semgrep_plugin.set_options(include=["*.py", "*.js"])
251 cmd = semgrep_plugin._build_check_command(files=["src/"])
253 # Each include pattern should have its own --include flag
254 include_indices = [i for i, x in enumerate(cmd) if x == "--include"]
255 assert_that(include_indices).is_length(2)
256 assert_that(cmd[include_indices[0] + 1]).is_equal_to("*.py")
257 assert_that(cmd[include_indices[1] + 1]).is_equal_to("*.js")
260def test_build_check_command_with_severity(semgrep_plugin: SemgrepPlugin) -> None:
261 """Build command with severity filter.
263 Args:
264 semgrep_plugin: The SemgrepPlugin instance to test.
265 """
266 semgrep_plugin.set_options(severity="ERROR")
267 cmd = semgrep_plugin._build_check_command(files=["src/"])
269 assert_that(cmd).contains("--severity")
270 severity_idx = cmd.index("--severity")
271 assert_that(cmd[severity_idx + 1]).is_equal_to("ERROR")
274def test_build_check_command_with_jobs(semgrep_plugin: SemgrepPlugin) -> None:
275 """Build command with jobs option.
277 Args:
278 semgrep_plugin: The SemgrepPlugin instance to test.
279 """
280 semgrep_plugin.set_options(jobs=4)
281 cmd = semgrep_plugin._build_check_command(files=["src/"])
283 assert_that(cmd).contains("--jobs")
284 jobs_idx = cmd.index("--jobs")
285 assert_that(cmd[jobs_idx + 1]).is_equal_to("4")
288def test_build_check_command_with_timeout_threshold(
289 semgrep_plugin: SemgrepPlugin,
290) -> None:
291 """Build command with timeout_threshold option.
293 Args:
294 semgrep_plugin: The SemgrepPlugin instance to test.
295 """
296 semgrep_plugin.set_options(timeout_threshold=30)
297 cmd = semgrep_plugin._build_check_command(files=["src/"])
299 assert_that(cmd).contains("--timeout")
300 timeout_idx = cmd.index("--timeout")
301 assert_that(cmd[timeout_idx + 1]).is_equal_to("30")
304def test_build_check_command_with_verbose(semgrep_plugin: SemgrepPlugin) -> None:
305 """Build command with verbose flag.
307 Args:
308 semgrep_plugin: The SemgrepPlugin instance to test.
309 """
310 semgrep_plugin.set_options(verbose=True)
311 cmd = semgrep_plugin._build_check_command(files=["src/"])
313 assert_that(cmd).contains("--verbose")
316def test_build_check_command_with_quiet(semgrep_plugin: SemgrepPlugin) -> None:
317 """Build command with quiet flag.
319 Args:
320 semgrep_plugin: The SemgrepPlugin instance to test.
321 """
322 semgrep_plugin.set_options(quiet=True)
323 cmd = semgrep_plugin._build_check_command(files=["src/"])
325 assert_that(cmd).contains("--quiet")
328def test_build_check_command_with_all_options(semgrep_plugin: SemgrepPlugin) -> None:
329 """Build command with all options set.
331 Args:
332 semgrep_plugin: The SemgrepPlugin instance to test.
333 """
334 semgrep_plugin.set_options(
335 config="p/security-audit",
336 exclude=["tests/*"],
337 include=["*.py"],
338 severity="WARNING",
339 timeout_threshold=60,
340 jobs=8,
341 verbose=True,
342 )
343 cmd = semgrep_plugin._build_check_command(files=["src/", "lib/"])
345 assert_that(cmd).contains("--config")
346 assert_that(cmd).contains("--exclude")
347 assert_that(cmd).contains("--include")
348 assert_that(cmd).contains("--severity")
349 assert_that(cmd).contains("--timeout")
350 assert_that(cmd).contains("--jobs")
351 assert_that(cmd).contains("--verbose")
352 assert_that(cmd).contains("src/")
353 assert_that(cmd).contains("lib/")