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