Coverage for tests / unit / parsers / test_taplo_parser.py: 100%

144 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Unit tests for taplo parser.""" 

2 

3from __future__ import annotations 

4 

5import pytest 

6from assertpy import assert_that 

7 

8from lintro.parsers.taplo.taplo_parser import parse_taplo_output 

9 

10 

11@pytest.mark.parametrize( 

12 "output", 

13 [ 

14 None, 

15 "", 

16 " \n \n ", 

17 ], 

18 ids=["none", "empty", "whitespace_only"], 

19) 

20def test_parse_taplo_output_returns_empty_for_no_content(output: str | None) -> None: 

21 """Parse empty, None, or whitespace-only output returns empty list. 

22 

23 Args: 

24 output: The taplo output to parse. 

25 """ 

26 result = parse_taplo_output(output) 

27 assert_that(result).is_empty() 

28 

29 

30def test_parse_taplo_output_extracts_all_fields() -> None: 

31 """Parse error-level issue extracts all fields correctly.""" 

32 output = """error[invalid_value]: invalid value 

33 --> pyproject.toml:5:10 

34 | 

35 5 | version = 

36 | ^ expected a value""" 

37 result = parse_taplo_output(output) 

38 assert_that(result).is_length(1) 

39 assert_that(result[0].file).is_equal_to("pyproject.toml") 

40 assert_that(result[0].line).is_equal_to(5) 

41 assert_that(result[0].column).is_equal_to(10) 

42 assert_that(result[0].level).is_equal_to("error") 

43 assert_that(result[0].code).is_equal_to("invalid_value") 

44 assert_that(result[0].message).is_equal_to("invalid value") 

45 

46 

47@pytest.mark.parametrize( 

48 ("level", "code", "output"), 

49 [ 

50 ( 

51 "error", 

52 "invalid_value", 

53 "error[invalid_value]: missing value\n --> file.toml:1:5", 

54 ), 

55 ( 

56 "warning", 

57 "deprecated_syntax", 

58 "warning[deprecated_syntax]: use new syntax\n --> file.toml:2:1", 

59 ), 

60 ( 

61 "error", 

62 "expected_table_array", 

63 "error[expected_table_array]: expected table array\n --> config.toml:10:1", 

64 ), 

65 ], 

66 ids=["error_invalid_value", "warning_deprecated", "error_expected_table_array"], 

67) 

68def test_parse_taplo_output_severity_levels( 

69 level: str, 

70 code: str, 

71 output: str, 

72) -> None: 

73 """Parse issues with different severity levels. 

74 

75 Args: 

76 level: The expected severity level. 

77 code: The expected error code. 

78 output: The taplo output to parse. 

79 """ 

80 result = parse_taplo_output(output) 

81 assert_that(result).is_length(1) 

82 assert_that(result[0].level).is_equal_to(level) 

83 assert_that(result[0].code).is_equal_to(code) 

84 

85 

86def test_parse_taplo_output_multiple_issues() -> None: 

87 """Parse multiple issues from output.""" 

88 output = """error[invalid_value]: first error 

89 --> file1.toml:1:5 

90 | 

91 1 | key = 

92 | ^ expected value 

93 

94error[syntax_error]: second error 

95 --> file2.toml:10:1 

96 | 

9710 | [invalid 

98 | ^ unclosed bracket 

99 

100warning[deprecated]: third issue 

101 --> file3.toml:5:3""" 

102 result = parse_taplo_output(output) 

103 assert_that(result).is_length(3) 

104 assert_that(result[0].line).is_equal_to(1) 

105 assert_that(result[0].file).is_equal_to("file1.toml") 

106 assert_that(result[1].line).is_equal_to(10) 

107 assert_that(result[1].file).is_equal_to("file2.toml") 

108 assert_that(result[2].line).is_equal_to(5) 

109 assert_that(result[2].file).is_equal_to("file3.toml") 

110 

111 

112def test_parse_taplo_output_non_matching_lines_ignored() -> None: 

113 """Non-matching lines are ignored.""" 

114 output = """Some header text 

115Taplo version 0.9.0 

116 

117error[invalid_value]: actual error 

118 --> pyproject.toml:5:10 

119 

120Other random output 

121Done.""" 

122 result = parse_taplo_output(output) 

123 assert_that(result).is_length(1) 

124 assert_that(result[0].code).is_equal_to("invalid_value") 

125 

126 

127def test_parse_taplo_output_file_with_path() -> None: 

128 """Parse file with directory path.""" 

129 output = """error[invalid_key]: invalid key name 

130 --> config/settings/app.toml:10:1""" 

131 result = parse_taplo_output(output) 

132 assert_that(result[0].file).is_equal_to("config/settings/app.toml") 

133 

134 

135def test_parse_taplo_output_blank_lines_between_issues() -> None: 

136 """Handle blank lines between issues.""" 

137 output = """error[error_one]: first 

138 --> file.toml:1:1 

139 

140 

141error[error_two]: second 

142 --> file.toml:5:1""" 

143 result = parse_taplo_output(output) 

144 assert_that(result).is_length(2) 

145 

146 

147# ============================================================================= 

148# Edge case tests 

149# ============================================================================= 

150 

151 

152def test_parse_taplo_output_unicode_in_message() -> None: 

153 """Handle Unicode characters in error messages.""" 

154 output = """error[invalid_value]: caractere invalide 

155 --> file.toml:1:1""" 

156 result = parse_taplo_output(output) 

157 assert_that(result).is_length(1) 

158 assert_that(result[0].message).contains("invalide") 

159 

160 

161def test_parse_taplo_output_very_long_message() -> None: 

162 """Handle extremely long error messages.""" 

163 long_text = "x" * 5000 

164 output = f"""error[long_error]: {long_text} 

165 --> file.toml:1:1""" 

166 result = parse_taplo_output(output) 

167 assert_that(result).is_length(1) 

168 assert_that(len(result[0].message)).is_equal_to(5000) 

169 

170 

171def test_parse_taplo_output_very_large_line_number() -> None: 

172 """Handle very large line numbers.""" 

173 output = """error[error]: error on large line 

174 --> file.toml:999999:1""" 

175 result = parse_taplo_output(output) 

176 assert_that(result).is_length(1) 

177 assert_that(result[0].line).is_equal_to(999999) 

178 

179 

180def test_parse_taplo_output_special_chars_in_message() -> None: 

181 """Handle special characters in error messages.""" 

182 output = """error[special]: Use "quotes" and <brackets> and [arrays] 

183 --> file.toml:1:1""" 

184 result = parse_taplo_output(output) 

185 assert_that(result).is_length(1) 

186 assert_that(result[0].message).contains("quotes") 

187 assert_that(result[0].message).contains("<brackets>") 

188 

189 

190def test_parse_taplo_output_colon_in_message() -> None: 

191 """Handle colons in error messages.""" 

192 output = """error[invalid]: expected format: key = value 

193 --> file.toml:1:1""" 

194 result = parse_taplo_output(output) 

195 assert_that(result).is_length(1) 

196 assert_that(result[0].message).contains("key = value") 

197 

198 

199def test_parse_taplo_output_deeply_nested_path() -> None: 

200 """Handle deeply nested file paths.""" 

201 deep_path = "a/b/c/d/e/f/g/h/i/j/config.toml" 

202 output = f"""error[nested]: deep path error 

203 --> {deep_path}:1:1""" 

204 result = parse_taplo_output(output) 

205 assert_that(result).is_length(1) 

206 assert_that(result[0].file).is_equal_to(deep_path) 

207 

208 

209def test_parse_taplo_output_code_with_underscores() -> None: 

210 """Handle error codes with underscores.""" 

211 output = """error[expected_table_array_header]: expected table array 

212 --> file.toml:1:1""" 

213 result = parse_taplo_output(output) 

214 assert_that(result).is_length(1) 

215 assert_that(result[0].code).is_equal_to("expected_table_array_header") 

216 

217 

218def test_parse_taplo_output_column_position() -> None: 

219 """Verify column position is correctly extracted.""" 

220 output = """error[syntax]: error at column 

221 --> file.toml:5:42""" 

222 result = parse_taplo_output(output) 

223 assert_that(result).is_length(1) 

224 assert_that(result[0].column).is_equal_to(42) 

225 

226 

227def test_parse_taplo_output_windows_path() -> None: 

228 """Handle Windows-style paths if present.""" 

229 # Note: Taplo typically uses forward slashes even on Windows, 

230 # but we test path handling nonetheless 

231 output = """error[error]: windows path 

232 --> C:/Users/test/config.toml:1:1""" 

233 result = parse_taplo_output(output) 

234 assert_that(result).is_length(1) 

235 # The path includes drive letter 

236 assert_that(result[0].file).contains("config.toml") 

237 

238 

239def test_parse_taplo_output_location_with_extra_spaces() -> None: 

240 """Handle location line with varying whitespace.""" 

241 output = """error[invalid]: error message 

242 --> pyproject.toml:5:10""" 

243 result = parse_taplo_output(output) 

244 assert_that(result).is_length(1) 

245 assert_that(result[0].file).is_equal_to("pyproject.toml") 

246 

247 

248def test_parse_taplo_output_issue_dataclass_fields() -> None: 

249 """Verify TaploIssue has correct field defaults.""" 

250 output = """error[test_code]: test message 

251 --> test.toml:1:1""" 

252 result = parse_taplo_output(output) 

253 assert_that(result).is_length(1) 

254 issue = result[0] 

255 

256 # Verify all fields are populated 

257 assert_that(issue.file).is_equal_to("test.toml") 

258 assert_that(issue.line).is_equal_to(1) 

259 assert_that(issue.column).is_equal_to(1) 

260 assert_that(issue.level).is_equal_to("error") 

261 assert_that(issue.code).is_equal_to("test_code") 

262 assert_that(issue.message).is_equal_to("test message") 

263 

264 

265# ============================================================================= 

266# Format check output tests (taplo fmt --check) 

267# ============================================================================= 

268 

269 

270def test_parse_taplo_output_fmt_check_format() -> None: 

271 """Parse taplo fmt --check output format. 

272 

273 Taplo fmt --check outputs: 

274 ERROR taplo:format_files: the file is not properly formatted path="file.toml" 

275 """ 

276 output = 'ERROR taplo:format_files: the file is not properly formatted path="/tmp/test.toml"' 

277 result = parse_taplo_output(output) 

278 

279 assert_that(result).is_length(1) 

280 assert_that(result[0].file).is_equal_to("/tmp/test.toml") 

281 assert_that(result[0].level).is_equal_to("error") 

282 assert_that(result[0].code).is_equal_to("format") 

283 assert_that(result[0].message).is_equal_to("the file is not properly formatted") 

284 

285 

286def test_parse_taplo_output_fmt_check_multiple_files() -> None: 

287 """Parse taplo fmt --check output with multiple files.""" 

288 output = """ERROR taplo:format_files: the file is not properly formatted path="config.toml" 

289ERROR taplo:format_files: the file is not properly formatted path="pyproject.toml" 

290ERROR operation failed error=some files were not properly formatted""" 

291 result = parse_taplo_output(output) 

292 

293 assert_that(result).is_length(2) 

294 assert_that(result[0].file).is_equal_to("config.toml") 

295 assert_that(result[1].file).is_equal_to("pyproject.toml") 

296 

297 

298def test_parse_taplo_output_fmt_check_with_info_lines() -> None: 

299 """Parse taplo fmt --check output with INFO lines mixed in.""" 

300 output = """ INFO taplo:format_files:collect_files: found files total=1 excluded=0 

301ERROR taplo:format_files: the file is not properly formatted path="test.toml" 

302ERROR operation failed error=some files were not properly formatted""" 

303 result = parse_taplo_output(output) 

304 

305 assert_that(result).is_length(1) 

306 assert_that(result[0].file).is_equal_to("test.toml") 

307 

308 

309def test_parse_taplo_output_fmt_check_rust_log_error_format() -> None: 

310 """Parse taplo fmt --check output when RUST_LOG=error is set. 

311 

312 When RUST_LOG=error is set, taplo outputs a simplified format without 

313 the taplo:format_files: prefix. 

314 """ 

315 output = """ERROR the file is not properly formatted path="/tmp/test.toml" 

316ERROR operation failed error=some files were not properly formatted""" 

317 result = parse_taplo_output(output) 

318 

319 assert_that(result).is_length(1) 

320 assert_that(result[0].file).is_equal_to("/tmp/test.toml") 

321 assert_that(result[0].level).is_equal_to("error") 

322 assert_that(result[0].code).is_equal_to("format") 

323 assert_that(result[0].message).is_equal_to("the file is not properly formatted") 

324 

325 

326def test_parse_taplo_output_ansi_codes_stripped() -> None: 

327 """Strip ANSI escape codes from output for consistent CI/local parsing.""" 

328 # Output with ANSI color codes (common in CI environments) 

329 output = """\x1b[31merror[invalid_value]: invalid value\x1b[0m 

330 --> pyproject.toml:5:10""" 

331 result = parse_taplo_output(output) 

332 assert_that(result).is_length(1) 

333 assert_that(result[0].file).is_equal_to("pyproject.toml") 

334 assert_that(result[0].code).is_equal_to("invalid_value")