blob: f0c50c8129571df769659bd8391a51b4c50cecda [file] [log] [blame]
# Copyright 2021 The ChromiumOS Authors
# 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) -> None:
"""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) -> None:
"""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) -> None:
"""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) -> None:
"""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) -> None:
"""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) -> None:
"""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) -> None:
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)