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

1"""Unit tests for Semgrep plugin options.""" 

2 

3from __future__ import annotations 

4 

5import pytest 

6from assertpy import assert_that 

7 

8from lintro.tools.definitions.semgrep import ( 

9 SEMGREP_DEFAULT_CONFIG, 

10 SEMGREP_DEFAULT_TIMEOUT, 

11 SemgrepPlugin, 

12) 

13 

14# ============================================================================= 

15# Tests for SemgrepPlugin default options 

16# ============================================================================= 

17 

18 

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. 

50 

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) 

59 

60 

61# ============================================================================= 

62# Tests for SemgrepPlugin.set_options method - valid options 

63# ============================================================================= 

64 

65 

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. 

109 

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] 

116 

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 

123 

124 assert_that(semgrep_plugin.options.get(option_name)).is_equal_to(expected) 

125 

126 

127def test_set_options_severity_lowercase(semgrep_plugin: SemgrepPlugin) -> None: 

128 """Set severity option with lowercase value normalizes to uppercase. 

129 

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") 

135 

136 

137# ============================================================================= 

138# Tests for SemgrepPlugin.set_options method - invalid types 

139# ============================================================================= 

140 

141 

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. 

180 

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] 

189 

190 

191# ============================================================================= 

192# Tests for SemgrepPlugin._build_check_command method 

193# ============================================================================= 

194 

195 

196def test_build_check_command_basic(semgrep_plugin: SemgrepPlugin) -> None: 

197 """Build basic command with default options. 

198 

199 Args: 

200 semgrep_plugin: The SemgrepPlugin instance to test. 

201 """ 

202 cmd = semgrep_plugin._build_check_command(files=["src/"]) 

203 

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/") 

212 

213 

214def test_build_check_command_with_config(semgrep_plugin: SemgrepPlugin) -> None: 

215 """Build command with custom config option. 

216 

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"]) 

222 

223 assert_that(cmd).contains("--config") 

224 config_idx = cmd.index("--config") 

225 assert_that(cmd[config_idx + 1]).is_equal_to("p/python") 

226 

227 

228def test_build_check_command_with_exclude(semgrep_plugin: SemgrepPlugin) -> None: 

229 """Build command with exclude patterns. 

230 

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/"]) 

236 

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/*") 

242 

243 

244def test_build_check_command_with_include(semgrep_plugin: SemgrepPlugin) -> None: 

245 """Build command with include patterns. 

246 

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/"]) 

252 

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") 

258 

259 

260def test_build_check_command_with_severity(semgrep_plugin: SemgrepPlugin) -> None: 

261 """Build command with severity filter. 

262 

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/"]) 

268 

269 assert_that(cmd).contains("--severity") 

270 severity_idx = cmd.index("--severity") 

271 assert_that(cmd[severity_idx + 1]).is_equal_to("ERROR") 

272 

273 

274def test_build_check_command_with_jobs(semgrep_plugin: SemgrepPlugin) -> None: 

275 """Build command with jobs option. 

276 

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/"]) 

282 

283 assert_that(cmd).contains("--jobs") 

284 jobs_idx = cmd.index("--jobs") 

285 assert_that(cmd[jobs_idx + 1]).is_equal_to("4") 

286 

287 

288def test_build_check_command_with_timeout_threshold( 

289 semgrep_plugin: SemgrepPlugin, 

290) -> None: 

291 """Build command with timeout_threshold option. 

292 

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/"]) 

298 

299 assert_that(cmd).contains("--timeout") 

300 timeout_idx = cmd.index("--timeout") 

301 assert_that(cmd[timeout_idx + 1]).is_equal_to("30") 

302 

303 

304def test_build_check_command_with_verbose(semgrep_plugin: SemgrepPlugin) -> None: 

305 """Build command with verbose flag. 

306 

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/"]) 

312 

313 assert_that(cmd).contains("--verbose") 

314 

315 

316def test_build_check_command_with_quiet(semgrep_plugin: SemgrepPlugin) -> None: 

317 """Build command with quiet flag. 

318 

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/"]) 

324 

325 assert_that(cmd).contains("--quiet") 

326 

327 

328def test_build_check_command_with_all_options(semgrep_plugin: SemgrepPlugin) -> None: 

329 """Build command with all options set. 

330 

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/"]) 

344 

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/")