blob: f26939d2ded4dbe07f68e3f46cc827b25dbf80fd [file] [log] [blame]
Ryan Beltrancfc5c362021-03-02 18:36:18 +00001# Copyright 2021 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Unit tests for tricium_cargo_tidy.py."""
6
7import json
Ryan Beltrancfc5c362021-03-02 18:36:18 +00008
9from chromite.lib import cros_test_lib
10from chromite.scripts import tricium_cargo_clippy
11
Ryan Beltrancfc5c362021-03-02 18:36:18 +000012
13# These test cases were made by:
14# 1) modifying trace_events to lint poorly
15# 2) running USE="rust_clippy" emerge trace_events
16# 3) removing null fields
17# 4) replacing strings with shorter test strings
18valid_test_cases = {
19 json.dumps({
20 'reason': 'compiler-message',
21 'package_id': 'package_id_1',
22 'target': {
23 'kind': ['lib'],
24 'crate_types': ['lib'],
25 'name': 'trace_events',
26 'src_path': '/absolute/path/to/file_path_1',
27 'edition': '2018',
28 'doctest': True,
29 'test': True
30 },
31 'message': {
32 'rendered': 'warning: rendered message 1',
33 'children': [
34 {
35 'children': [],
36 'level': 'note',
37 'message': '`#[warn(bare_trait_objects)]` message 1',
38 'spans': []
39 },
40 {
41 'children': [],
42 'level': 'help',
43 'message': 'sub message 1',
44 'spans': [{
45 'file_name': 'file_name_1',
46 'byte_end': 7342, 'byte_start': 7336,
47 'column_end': 35, 'column_start': 29,
48 'is_primary': True,
49 'line_end': 262, 'line_start': 262,
50 'suggested_replacement': 'dyn Tracer',
51 'suggestion_applicability': 'MachineApplicable',
52 'text': [{
53 'highlight_end': 35, 'highlight_start': 29,
54 'text': 'highlight 1'
55 }]
56 }]
57 }
58 ],
59 'code': {'code': 'bare_trait_objects'},
60 'level': 'warning',
61 'message': 'message 1',
62 'spans': [{
63 'byte_end': 7342, 'byte_start': 7336,
64 'column_end': 35, 'column_start': 29,
65 'file_name': 'file_name_1',
66 'is_primary': True,
67 'line_end': 262, 'line_start': 262,
68 'text': [{
69 'highlight_end': 35, 'highlight_start': 29,
70 'text': 'highlight 1'
71 }]
72 }]
73 }
74 }): {
75 'file_path': '/absolute/path/to/file_path_1',
76 'locations': [
77 tricium_cargo_clippy.CodeLocation(
78 file_path='/absolute/path/to/file_path_1',
79 file_name='file_name_1',
80 line_start=262,
81 line_end=262,
82 column_start=29,
83 column_end=35
84 )
85 ],
86 'level': 'warning',
87 'message': 'warning: rendered message 1',
88 },
89 json.dumps({
90 'reason': 'compiler-message',
91 'package_id': 'package_id 1',
92 'target': {
93 'kind': ['lib'],
94 'crate_types': ['lib'],
95 'name': 'trace_events',
96 'src_path': '/absolute/path/to/file_path_2',
97 'edition': '2018', 'doctest': True, 'test': True
98 },
99 'message': {
100 'rendered': 'warning: rendered message 2',
101 'children': [
102 {
103 'level': 'note',
104 'children': [],
105 'message': 'submessage 2.1',
106 'spans': []
107 },
108 {
109 'level': 'help',
110 'children': [],
111 'message': 'submessage 2.2',
112 'spans': []
113 },
114 {
115 'level': 'help',
116 'children': [],
117 'message': 'submessage 2.3',
118 'spans': [{
119 'file_name': 'file_name_2',
120 'byte_end': 7342, 'byte_start': 7327,
121 'column_end': 35, 'column_start': 20,
122 'line_end': 262, 'line_start': 262,
123 'is_primary': True,
124 'suggested_replacement': '&Tracer',
125 'suggestion_applicability': 'MachineApplicable',
126 'text': [{
127 'highlight_end': 35,
128 'highlight_start': 20,
129 'text': 'highlight 2'
130 }]
131 }]
132 }
133 ],
134 'code': {'code': 'clippy::redundant_static_lifetimes'},
135 'level': 'warning',
136 'message': 'message 2',
137 'spans': [{
138 'file_name': 'file_name_2',
139 'byte_end': 7335, 'byte_start': 7328,
140 'column_end': 28, 'column_start': 21,
141 'line_end': 262, 'line_start': 262,
142 'is_primary': True,
143 'text': [{
144 'highlight_end': 28,
145 'highlight_start': 21,
146 'text': 'highlight 2'
147 }]
148 }]
149 }
150 }): {
151 'file_path': '/absolute/path/to/file_path_2',
152 'locations': [
153 tricium_cargo_clippy.CodeLocation(
154 file_path='/absolute/path/to/file_path_2',
155 file_name='file_name_2',
156 line_start=262,
157 line_end=262,
158 column_start=21,
159 column_end=28
160 ),
161 tricium_cargo_clippy.CodeLocation(
162 file_path='/absolute/path/to/file_path_2',
163 file_name='file_name_2',
164 line_start=262,
165 line_end=262,
166 column_start=20,
167 column_end=35
168 )
169 ],
170 'level': 'warning',
171 'message': 'warning: rendered message 2',
172 },
173 json.dumps({
174 'reason': 'compiler-message',
175 'package_id': 'package id 3',
176 'target': {
177 'kind': ['lib'],
178 'crate_types': ['lib'],
179 'name': 'trace_events',
180 'src_path': '/absolute/path/to/file_path_3',
181 'edition': '2018', 'doctest': True, 'test': True
182 },
183 'message': {
184 'rendered': 'warning: rendered message 3',
185 'children': [
186 {
187 'children': [],
188 'level': 'note',
189 'message': 'submessage 3.1',
190 'spans': []
191 },
192 {
193 'children': [],
194 'level': 'help',
195 'message': 'submessage 3.2',
196 'spans': [
197 {
198 'file_name': 'file_name_3',
199 'byte_end': 448, 'byte_start': 447,
200 'column_end': 8, 'column_start': 7,
201 'line_end': 14, 'line_start': 14,
202 'is_primary': True,
203 'suggested_replacement': '_x',
204 'suggestion_applicability': 'MachineApplicable',
205 'text': [{
206 'highlight_end': 8, 'highlight_start': 7,
207 'text': 'highlight 3'
208 }]
209 }
210 ]
211 }
212 ],
213 'code': {'code': 'unused_variables'},
214 'level': 'warning',
215 'message': 'message 3',
216 'spans': [{
217 'file_name': 'file_name_3',
218 'byte_end': 448, 'byte_start': 447,
219 'column_end': 8, 'column_start': 7,
220 'line_end': 14, 'line_start': 14,
221 'is_primary': True,
222 'text': [{
223 'highlight_end': 8,
224 'highlight_start': 7,
225 'text': 'highlight 3'
226 }]
227 }]
228 }
229 }): {
230 'file_path': '/absolute/path/to/file_path_3',
231 'locations': [
232 tricium_cargo_clippy.CodeLocation(
233 file_path='/absolute/path/to/file_path_3',
234 file_name='file_name_3',
235 line_start=14,
236 line_end=14,
237 column_start=7,
238 column_end=8
239 )
240 ],
241 'level': 'warning',
242 'message': 'warning: rendered message 3',
243 },
244 json.dumps({'reason': 'build-script-executed'}): {
245 'skipped': True
246 },
247 json.dumps({'reason': 'compiler-artifact'}): {
248 'skipped': True
249 }
250}
251
252invalid_test_cases = {
253 'not json': ['json'],
254 r'{}': ['reason'],
255 json.dumps({
256 'reason': 'compiler-message',
257 }): ['file_path', 'level', 'message'],
258 json.dumps({
259 'reason': 'compiler-message',
260 'level': 'warning',
261 'message': {
262 'rendered': 'warning: a message'
263 }
264 }): ['file_path'],
265 json.dumps({
266 'reason': 'compiler-message',
267 'target': {
268 'src_path': 'file path'
269 },
270 'message': {
271 'rendered': 'warning: a message'
272 }
273 }): ['level'],
274 json.dumps({
275 'reason': 'compiler-message',
276 'level': 'warning',
277 'target': {
278 'src_path': 'file path'
279 },
280 }): ['message'],
281}
282
283
284class TriciumCargoClippyTests(cros_test_lib.LoggingTestCase):
285 """Tests for Cargo Clippy."""
286
287 def test_parse_file_path(self):
288 """Tests that parse_file_path is as expected."""
289 for i, (test_case, exp_results) in enumerate(valid_test_cases.items()):
290 if 'file_path' not in exp_results:
291 continue
292 test_json = json.loads(test_case)
293 file_path = tricium_cargo_clippy.parse_file_path('valid', i, test_json)
294 self.assertEqual(file_path, exp_results['file_path'])
295
296 def test_parse_locations(self):
297 """Tests that parse_locations is as expected."""
Ryan Beltran43a00662021-05-17 16:55:24 +0000298 for test_case, exp_results in valid_test_cases.items():
Ryan Beltrancfc5c362021-03-02 18:36:18 +0000299 if 'locations' not in exp_results:
300 continue
301 test_json = json.loads(test_case)
302 locations = list(tricium_cargo_clippy.parse_locations(
Ryan Beltran43a00662021-05-17 16:55:24 +0000303 test_json, exp_results['file_path']))
Ryan Beltrancfc5c362021-03-02 18:36:18 +0000304 self.assertEqual(locations, exp_results['locations'])
305
306 def test_parse_level(self):
307 """Tests that parse_level is as expected."""
308 for i, (test_case, exp_results) in enumerate(valid_test_cases.items()):
309 if 'level' not in exp_results:
310 continue
311 test_json = json.loads(test_case)
312 level = tricium_cargo_clippy.parse_level('valid', i, test_json)
313 self.assertEqual(level, exp_results['level'])
314
315 def test_parse_message(self):
316 """Tests that parse_message is as expected."""
317 for i, (test_case, exp_results) in enumerate(valid_test_cases.items()):
318 if 'message' not in exp_results:
319 continue
320 test_json = json.loads(test_case)
321 message = tricium_cargo_clippy.parse_message('valid', i, test_json)
322 self.assertEqual(message, exp_results['message'])
323
324 def test_parse_diagnostics(self):
325 """Tests that parse_diagnostics yields correct diagnostics."""
326 diags = list(tricium_cargo_clippy.parse_diagnostics(
327 'valid_test_cases', list(valid_test_cases.keys())))
328
329 # Verify parse_diagnostics retrieved correct amount of diagnostics
330 exp_len = len([
331 values for values in valid_test_cases.values()
332 if not values.get('skipped')
333 ])
334 self.assertEqual(len(diags), exp_len)
335
336 # Verify diagnostics are from correct source
337 for i, diag in enumerate(diags):
338 locations = list(diag.locations)
339 expected_locations = list(valid_test_cases.values())[i].get('locations')
340 self.assertEqual(locations, expected_locations)
341
342 def test_logs_invalid_parse_diagnostic_cases(self):
343 """Tests that parse_diagnostics logs proper exceptions."""
344 for invalid_case, exp_errors in invalid_test_cases.items():
345 with self.assertRaises(
346 tricium_cargo_clippy.Error,
347 msg=f'Expected error parsing {invalid_case} but got none.') as ctx:
348 list(tricium_cargo_clippy.parse_diagnostics('invalid', [invalid_case]))
349 if 'json' in exp_errors:
350 exp_error = tricium_cargo_clippy.CargoClippyJSONError('invalid', 0)
351 elif 'reason' in exp_errors:
352 exp_error = tricium_cargo_clippy.CargoClippyReasonError('invalid', 0)
353 else:
354 for field in ('file_path', 'locations', 'level', 'message'):
355 if field in exp_errors:
356 exp_error = tricium_cargo_clippy.CargoClippyFieldError(
357 'invalid', 0, field)
358 break
359 self.assertIs(type(ctx.exception), type(exp_error))
360 self.assertEqual(ctx.exception.args, exp_error.args)
361
362 def test_clippy_include_file_pattern(self):
363 """Tests that Clippy.include_file_pattern is as expected."""
364 pattern_test_cases = {
365 'a/b/c.d': {
366 'includes': ['a/b/c.d'],
367 'excludes': ['a/b/c/d', 'a/b/c', 'b/c.d', 'a/b/c.e', '/a/b/c.d'],
368 },
369 'a/*/c': {
370 'includes': ['a/b/c', 'a/bbb/c', 'a/b.b/c', 'a/*/c'],
371 'excludes': ['a/b/b', '/a/b/c', 'a/b/d/c', 'a/c'],
372 },
373 'a/**/c': {
374 'includes': ['a/b1/b2/b3/c', 'a/b/c', 'a/c'],
375 'excludes': ['ac'],
376 },
377 'a/b*/c': {
378 'includes': ['a/b/c', 'a/b1/c', 'a/b2/c', 'a/b.b/c'],
379 'excludes': ['a/b/d/c', 'a/c'],
380 },
381 'a/b.*': {
382 'includes': ['a/b.', 'a/b.json', 'a/b.txt'],
383 'excludes': ['a/b', 'a/b.c/d', 'a/b1.json'],
384 },
385 }
386 for pattern, cases in pattern_test_cases.items():
387 re_pattern = tricium_cargo_clippy.include_file_pattern(pattern)
388 for case in cases['includes']:
389 self.assertTrue(
390 bool(re_pattern.fullmatch(case)),
391 f'Pattern {pattern} should match case {case} but did not. '
392 f'Hint: generated regex was {re_pattern}'
393 )
394 for case in cases['excludes']:
395 self.assertFalse(
396 bool(re_pattern.fullmatch(case)),
397 f'Pattern {pattern} should not match case {case} but did. '
398 f'Hint: generated regex was {re_pattern}'
399 )
Ryan Beltran43a00662021-05-17 16:55:24 +0000400
401 def test_filter_diagnostics(self):
402 file_filter = 'acceptable_filepath.json'
403 example_code_location = tricium_cargo_clippy.CodeLocation(
404 file_path=file_filter,
405 file_name=file_filter,
406 line_start=1,
407 line_end=4,
408 column_start=0,
409 column_end=12
410 )
411 accepted_diags = [
412 tricium_cargo_clippy.ClippyDiagnostic(
413 file_path=file_filter,
414 locations=[example_code_location],
415 level='warning',
416 message='warning: be warned.'
417 )
418 ]
419 ignored_diags = [
420 # File filter not matched
421 tricium_cargo_clippy.ClippyDiagnostic(
422 file_path='not_a_match.json',
423 locations=[example_code_location],
424 level='warning',
425 message='warning: be warned.'
426 ),
427 # "aborting due to previous error" messages
428 tricium_cargo_clippy.ClippyDiagnostic(
429 file_path=file_filter,
430 locations=[example_code_location],
431 level='warning',
432 message='warning: aborting due to previous error...'
433 ),
434 # No locations provided
435 tricium_cargo_clippy.ClippyDiagnostic(
436 file_path=file_filter,
437 locations=[],
438 level='warning',
439 message='warning: 6 warnings emitted.'
440 )
441 ]
442 filtered_diags = list(tricium_cargo_clippy.filter_diagnostics(
443 accepted_diags + ignored_diags, file_filter))
444 self.assertEqual(filtered_diags, accepted_diags)