blob: 69f7a0363f360375e3d62c8040d73cd64c7c6f63 [file] [log] [blame]
# Copyright 2021 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unit tests for tricium_cargo_tidy.py."""
import json
from chromite.lib import cros_test_lib
from chromite.scripts import tricium_cargo_clippy
# These test cases were made by:
# 1) modifying trace_events to lint poorly
# 2) running USE="rust_clippy" emerge trace_events
# 3) removing null fields
# 4) replacing strings with shorter test strings
valid_test_cases = {
json.dumps({
'reason': 'compiler-message',
'package_id': 'pkg 0.1.0 (path+file:///path/to/repo/pkg)',
'target': {
'kind': ['lib'],
'crate_types': ['lib'],
'name': 'trace_events',
'src_path': '/path/to/repo/pkg/src/main_file',
'edition': '2018',
'doctest': True,
'test': True
},
'message': {
'rendered': 'warning: rendered message 1',
'children': [
{
'children': [],
'level': 'note',
'message': '`#[warn(bare_trait_objects)]` message 1',
'spans': []
},
{
'children': [],
'level': 'help',
'message': 'sub message 1',
'spans': [{
'file_name': 'src/file_name_1',
'byte_end': 7342, 'byte_start': 7336,
'column_end': 35, 'column_start': 29,
'is_primary': True,
'line_end': 262, 'line_start': 262,
'suggested_replacement': 'dyn Tracer',
'suggestion_applicability': 'MachineApplicable',
'text': [{
'highlight_end': 35, 'highlight_start': 29,
'text': 'highlight 1'
}]
}]
}
],
'code': {'code': 'bare_trait_objects'},
'level': 'warning',
'message': 'message 1',
'spans': [{
'byte_end': 7342, 'byte_start': 7336,
'column_end': 35, 'column_start': 29,
'file_name': 'src/file_name_1',
'is_primary': True,
'line_end': 262, 'line_start': 262,
'text': [{
'highlight_end': 35, 'highlight_start': 29,
'text': 'highlight 1'
}]
}]
}
}): {
'locations': [
tricium_cargo_clippy.CodeLocation(
file_path='pkg/src/file_name_1',
line_start=262,
line_end=262,
column_start=29,
column_end=35
)
],
'level': 'warning',
'message': 'warning: rendered message 1',
},
json.dumps({
'reason': 'compiler-message',
'package_id': 'pkg 1.2.3 (path+file:///path/to/repo/pkg)',
'target': {
'kind': ['lib'],
'crate_types': ['lib'],
'name': 'trace_events',
'src_path': '/path/to/repo/pkg/src/main',
'edition': '2018', 'doctest': True, 'test': True
},
'message': {
'rendered': 'warning: rendered message 2',
'children': [
{
'level': 'note',
'children': [],
'message': 'submessage 2.1',
'spans': []
},
{
'level': 'help',
'children': [],
'message': 'submessage 2.2',
'spans': []
},
{
'level': 'help',
'children': [],
'message': 'submessage 2.3',
'spans': [{
'file_name': 'src/file_name_2',
'byte_end': 7342, 'byte_start': 7327,
'column_end': 35, 'column_start': 20,
'line_end': 262, 'line_start': 262,
'is_primary': True,
'suggested_replacement': '&Tracer',
'suggestion_applicability': 'MachineApplicable',
'text': [{
'highlight_end': 35,
'highlight_start': 20,
'text': 'highlight 2'
}]
}]
}
],
'code': {'code': 'clippy::redundant_static_lifetimes'},
'level': 'warning',
'message': 'message 2',
'spans': [{
'file_name': 'src/file_name_2',
'byte_end': 7335, 'byte_start': 7328,
'column_end': 28, 'column_start': 21,
'line_end': 262, 'line_start': 262,
'is_primary': True,
'text': [{
'highlight_end': 28,
'highlight_start': 21,
'text': 'highlight 2'
}]
}]
}
}): {
'locations': [
tricium_cargo_clippy.CodeLocation(
file_path='pkg/src/file_name_2',
line_start=262,
line_end=262,
column_start=21,
column_end=28
),
tricium_cargo_clippy.CodeLocation(
file_path='pkg/src/file_name_2',
line_start=262,
line_end=262,
column_start=20,
column_end=35
)
],
'level': 'warning',
'message': 'warning: rendered message 2',
},
json.dumps({
'reason': 'compiler-message',
'package_id': 'pkg 0.1.0 (path+file:///path/to/repo/pkg)',
'target': {
'kind': ['lib'],
'crate_types': ['lib'],
'name': 'trace_events',
'src_path': '/path/to/repo/pkg/path/to/main',
'edition': '2018', 'doctest': True, 'test': True
},
'message': {
'rendered': 'warning: rendered message 3',
'children': [
{
'children': [],
'level': 'note',
'message': 'submessage 3.1',
'spans': []
},
{
'children': [],
'level': 'help',
'message': 'submessage 3.2',
'spans': [
{
'file_name': 'path/to/file_name_3',
'byte_end': 448, 'byte_start': 447,
'column_end': 8, 'column_start': 7,
'line_end': 14, 'line_start': 14,
'is_primary': True,
'suggested_replacement': '_x',
'suggestion_applicability': 'MachineApplicable',
'text': [{
'highlight_end': 8, 'highlight_start': 7,
'text': 'highlight 3'
}]
}
]
}
],
'code': {'code': 'unused_variables'},
'level': 'warning',
'message': 'message 3',
'spans': [{
'file_name': 'path/to/file_name_3',
'byte_end': 448, 'byte_start': 447,
'column_end': 8, 'column_start': 7,
'line_end': 14, 'line_start': 14,
'is_primary': True,
'text': [{
'highlight_end': 8,
'highlight_start': 7,
'text': 'highlight 3'
}]
}]
}
}): {
'locations': [
tricium_cargo_clippy.CodeLocation(
file_path='pkg/path/to/file_name_3',
line_start=14,
line_end=14,
column_start=7,
column_end=8
)
],
'level': 'warning',
'message': 'warning: rendered message 3',
},
json.dumps({'reason': 'build-script-executed'}): {
'skipped': True
},
json.dumps({'reason': 'compiler-artifact'}): {
'skipped': True
}
}
invalid_test_cases = {
'not json': ['json'],
r'{}': ['reason'],
json.dumps({
'reason': 'compiler-message',
}): ['level', 'message'],
json.dumps({
'reason': 'compiler-message',
'target': {
'src_path': 'file path'
},
'message': {
'rendered': 'warning: a message'
}
}): ['level'],
json.dumps({
'reason': 'compiler-message',
'level': 'warning',
'target': {
'src_path': 'file path'
},
}): ['message'],
}
test_git_repo = '/path/to/repo'
test_package_path = '/path/to/repo/pkg'
class TriciumCargoClippyTests(cros_test_lib.LoggingTestCase):
"""Tests for Cargo Clippy."""
def test_parse_locations(self):
"""Tests that parse_locations is as expected."""
for test_case, exp_results in valid_test_cases.items():
if 'locations' not in exp_results:
continue
test_json = json.loads(test_case)
locations = list(tricium_cargo_clippy.parse_locations(
test_json, test_package_path, test_git_repo))
self.assertEqual(locations, exp_results['locations'])
def test_parse_locations_ebuild_directories(self):
"""Tests that parse_locations strips ebuild work directories."""
expected_location = 'src/foo'
example_finding = json.dumps({
'reason': 'compiler-message',
'level': 'warning',
'message': {
'rendered': 'warning: a message',
'spans': [{
'file_name': expected_location,
'column_end': 1,
'column_start': 2,
'line_end': 3,
'line_start': 4,}]}})
for work_dir in (
'/build/atlas/tmp/portage/dev-rust/pkg-0.12.3-r3/work/pkg-0.12.3',
'/build/atlas/tmp/portage/dev-rust/pkg-99999/work/pkg-9999'):
inputs = [json.dumps({'package_path': work_dir}), example_finding]
diagnostic = next(tricium_cargo_clippy.parse_diagnostics('', inputs, ''))
self.assertEqual(next(diagnostic.locations).file_path, expected_location)
def test_parse_level(self):
"""Tests that parse_level is as expected."""
for i, (test_case, exp_results) in enumerate(valid_test_cases.items()):
if 'level' not in exp_results:
continue
test_json = json.loads(test_case)
level = tricium_cargo_clippy.parse_level('valid', i, test_json)
self.assertEqual(level, exp_results['level'])
def test_parse_message(self):
"""Tests that parse_message is as expected."""
for i, (test_case, exp_results) in enumerate(valid_test_cases.items()):
if 'message' not in exp_results:
continue
test_json = json.loads(test_case)
message = tricium_cargo_clippy.parse_message('valid', i, test_json)
self.assertEqual(message, exp_results['message'])
def test_parse_diagnostics(self):
"""Tests that parse_diagnostics yields correct diagnostics."""
package_path_json = json.dumps({'package_path': test_package_path})
orig_jsons = [package_path_json] + list(valid_test_cases.keys())
diags = list(tricium_cargo_clippy.parse_diagnostics(
'valid_test_cases', orig_jsons, test_git_repo))
# Verify parse_diagnostics retrieved correct amount of diagnostics
exp_len = len([
values for values in valid_test_cases.values()
if not values.get('skipped')
])
self.assertEqual(len(diags), exp_len)
# Verify diagnostics are from correct source
for i, diag in enumerate(diags):
locations = list(diag.locations)
expected_locations = list(valid_test_cases.values())[i].get('locations')
self.assertEqual(locations, expected_locations)
def test_logs_invalid_parse_diagnostic_cases(self):
"""Tests that parse_diagnostics logs proper exceptions."""
package_path_json = json.dumps({'package_path': test_package_path})
for invalid_case, exp_errors in invalid_test_cases.items():
with self.assertRaises(
tricium_cargo_clippy.Error,
msg=f'Expected error parsing {invalid_case} but got none.') as ctx:
list(tricium_cargo_clippy.parse_diagnostics(
'invalid', [package_path_json, invalid_case], test_git_repo))
if 'json' in exp_errors:
exp_error = tricium_cargo_clippy.CargoClippyJSONError('invalid', 1)
elif 'reason' in exp_errors:
exp_error = tricium_cargo_clippy.CargoClippyReasonError('invalid', 1)
else:
for field in ('locations', 'level', 'message'):
if field in exp_errors:
exp_error = tricium_cargo_clippy.CargoClippyFieldError(
'invalid', 1, field)
break
self.assertIs(type(ctx.exception), type(exp_error))
self.assertEqual(ctx.exception.args, exp_error.args)
def test_filter_diagnostics(self):
file_path = 'some_filepath.json'
example_code_location = tricium_cargo_clippy.CodeLocation(
file_path=file_path,
line_start=1,
line_end=4,
column_start=0,
column_end=12
)
accepted_diags = [
tricium_cargo_clippy.ClippyDiagnostic(
locations=[example_code_location],
level='warning',
message='warning: be warned.'
)
]
ignored_diags = [
# "aborting due to previous error" messages
tricium_cargo_clippy.ClippyDiagnostic(
locations=[example_code_location],
level='warning',
message='warning: aborting due to previous error...'
),
# No locations provided
tricium_cargo_clippy.ClippyDiagnostic(
locations=[],
level='warning',
message='warning: 6 warnings emitted.'
)
]
filtered_diags = list(tricium_cargo_clippy.filter_diagnostics(
accepted_diags + ignored_diags))
self.assertEqual(filtered_diags, accepted_diags)