blob: 7996eb75dabd6d0771ca19c117ebb26e5e1ca64d [file] [log] [blame]
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# Copyright 2019 Google Inc. All Rights Reserved.
"""Clang_Tidy_Warn tests
This is the test file for clang_tidy_warn.py.
It starts with unit testing of individual functions, and then tests the
functionality of the whole file by using artificial log files, and then tests
on the real world examples.
"""
from __future__ import print_function
from csv import writer
import clang_tidy_warn as ct_warn
import clang_tidy_warn_patterns as ct_patterns
import unittest
from contextlib import contextmanager
import StringIO
import warnings_pb2 # if missing, run compile_proto.sh to generate it
def get_test_vars():
"""create artificial warn_patterns and project names for testing purposes"""
project_names = ['ProjectA', 'ProjectB']
warn_patterns = [{
'severity': ct_patterns.Severity.FIXMENOW,
'projects': {
'ProjectA': 2,
'ProjectB': 0
},
'description': 'Test warning of severity 1 (FIXMENOW)'
},
{
'severity': ct_patterns.Severity.HIGH,
'projects': {
'ProjectA': 1,
'ProjectB': 3
},
'description': 'Test warning of severity 2 (HIGH)'
},
{
'severity': ct_patterns.Severity.MEDIUM,
'projects': {
'ProjectA': 0,
'ProjectB': 6
},
'description': 'Test warning of severity 3 (MEDIUM)'
}]
# pad warn_patterns with severities we are not using
for s in sorted(ct_patterns.Severity.levels, key=lambda s: s.value):
if s.value >= len(warn_patterns):
warn_patterns.append({'severity': s, 'projects': {}})
warn_patterns[s.value]['description'] = ""
warn_patterns[s.value]['members'] = []
warning_messages = [
"/ProjectB:1:1: warning: (1) Project B of severity 1",
"/ProjectB:1:1: warning: (2) Project B of severity 1",
"/ProjectB:1:1: warning: (3) Project B of severity 1",
"/ProjectA:22:23: warning: (1) Project A of severity 0",
"/ProjectA:22:23: warning: (2) Project A of severity 0",
"/ProjectA:22:23: warning: Project A of severity 1",
"/ProjectB:1:1: warning: (1) Project B of severity 2",
"/ProjectB:1:1: warning: (2) Project B of severity 2",
"/ProjectB:1:1: warning: (3) Project B of severity 2",
"/ProjectB:1:1: warning: (4) Project B of severity 2",
"/ProjectB:1:1: warning: (5) Project B of severity 2",
"/ProjectB:1:1: warning: (6) Project B of severity 2"
]
# [ warn_patterns index, project_names index, warning_messages index
warning_records = [[1, 1, 0, 0], [1, 1, 1, 0], [1, 1, 2, 0], [0, 0, 3, 0],
[0, 0, 4, 0], [1, 0, 5, 0], [2, 1, 6, 0], [2, 1, 7, 0],
[2, 1, 8, 0], [2, 1, 9, 0], [2, 1, 10, 0], [2, 1, 11, 0]]
expected_warnings = {
'ProjectA': {
0: 2,
1: 1,
2: 0,
3: 0,
4: 0,
5: 0,
6: 0,
7: 0,
8: 0
},
'ProjectB': {
0: 0,
1: 3,
2: 6,
3: 0,
4: 0,
5: 0,
6: 0,
7: 0,
8: 0
}
}
expected_total_by_project = {'ProjectA': 3, 'ProjectB': 9}
expected_total_by_severity = {
0: 2,
1: 4,
2: 6,
3: 0,
4: 0,
5: 0,
6: 0,
7: 0,
8: 0
}
expected_total_all_projects = 12
expected_stats_rows = [['ProjectA', 2, 1, 0, 3], ['ProjectB', 0, 3, 6, 9]]
res = {
'project_names': project_names,
'warn_patterns': warn_patterns,
'warnings': expected_warnings,
'total_by_project': expected_total_by_project,
'total_by_severity': expected_total_by_severity,
'total_all_projects': expected_total_all_projects,
'stats_rows': expected_stats_rows,
'warning_messages': warning_messages,
'warning_records': warning_records,
}
return res
def put_test_vars():
# save old warn patterns to reset to following this test
actual_warn_patterns = ct_warn.warn_patterns
actual_project_names = ct_warn.project_names
actual_warning_messages = ct_warn.warning_messages
actual_warning_records = ct_warn.warning_records
# run test w specified inputs
expected = get_test_vars()
ct_warn.warn_patterns = expected['warn_patterns']
ct_warn.project_names = expected['project_names']
ct_warn.warning_messages = expected['warning_messages']
ct_warn.warning_records = expected['warning_records']
return (actual_warn_patterns, actual_project_names, actual_warning_messages,
actual_warning_records)
def remove_test_vars(actual_warn_patterns, actual_project_names,
actual_warning_messages, actual_warning_records):
# reset to actual vals
ct_warn.project_names = actual_project_names
ct_warn.warn_patterns = actual_warn_patterns
ct_warn.warning_messages = actual_warning_messages
ct_warn.warning_records = actual_warning_records
def setup_classify():
"""Run prereqs for calling classify_one_warning
The module requires an explicit call to compile_patterns to have these created
and this happens outside of the methods we are testing so explicit setup is
necessary
"""
ct_warn.compile_patterns()
@contextmanager
def test_vars():
actual_warn_patterns, actual_project_names, actual_warning_messages, \
actual_warning_records = put_test_vars()
try:
yield
finally:
remove_test_vars(actual_warn_patterns, actual_project_names,
actual_warning_messages, actual_warning_records)
class Tests(unittest.TestCase):
"""Test Class for Clang-Tidy"""
def test_initialize_arrays(self):
names, patterns = ct_warn.initialize_arrays()
self.assertGreater(len(names), 0)
self.assertGreater(len(patterns), 0)
# check that warn_patterns was modified in-place properly
for w in ct_warn.warn_patterns:
self.assertIn('members', w)
self.assertIn('option', w)
self.assertIn('projects', w)
self.assertTrue(isinstance(w['projects'], dict))
def test_create_warnings(self):
with test_vars():
expected = get_test_vars()
self.assertEqual(expected['warnings'], ct_warn.create_warnings())
def test_get_total_by_project(self):
with test_vars():
expected = get_test_vars()
total_by_project = ct_warn.get_total_by_project(expected['warnings'])
self.assertEqual(total_by_project, expected['total_by_project'])
def test_get_total_by_severity(self):
with test_vars():
expected = get_test_vars()
total_by_severity = ct_warn.get_total_by_severity(expected['warnings'])
self.assertEqual(total_by_severity, expected['total_by_severity'])
def test_emit_row_counts_per_project(self):
with test_vars():
expected = get_test_vars()
total_all_projects, stats_rows = \
ct_warn.emit_row_counts_per_project(expected['warnings'],
expected['total_by_project'],
expected['total_by_severity'])
self.assertEqual(total_all_projects, expected['total_all_projects'])
self.assertEqual(stats_rows, expected['stats_rows'])
def test_classify_one_warning(self):
setup_classify()
line = ("external/libese/apps/weaver/weaver.c:340:17: "
"warning: unused variable 'READ_SUCCESS' [-Wunused-variable]")
warning = {"line": line, "link": ""}
results = []
# find expected result
expected_index = -1
for i, w in enumerate(ct_warn.warn_patterns):
if w['description'] == \
"Unused function, variable, label, comparison, etc.":
expected_index = i
break # we expect to find a single index
assert expected_index != -1
# check that the expected result is in index column of actual results
ct_warn.classify_one_warning(warning, results)
self.assertIn(expected_index, [result[2] for result in results])
def test_parse_compiler_output_exception(self):
with self.assertRaises(ValueError):
ct_warn.parse_compiler_output("bad compiler output")
def test_parse_compiler_output(self):
with test_vars():
expected = get_test_vars()
test_message = expected['warning_messages'][4] # /ProjectA:22:23 <text..>
file_path, line_number, col_number, warning_message = (
ct_warn.parse_compiler_output(test_message))
self.assertEqual(file_path, "/ProjectA")
self.assertEqual(line_number, 22)
self.assertEqual(col_number, 23)
self.assertEqual(warning_message, " warning: (2) Project A of severity 0")
def test_data_to_protobuf(self):
with test_vars():
parsed_warning_messages = []
for message in ct_warn.warning_messages:
parsed_warning_messages.append(
ct_warn.parse_compiler_output(message)[3])
warnings = ct_warn.generate_protobufs()
for warning in warnings: # check that each warning was found
self.assertIn(warning.matching_compiler_output, parsed_warning_messages)
def test_remove_prefix_found(self):
self.assertEqual(
ct_warn.remove_prefix("googley google code", "gle"), "gle code")
def test_remove_prefix_not_found(self):
self.assertEqual(
ct_warn.remove_prefix("google code", "bugs"), "google code")
if __name__ == '__main__':
unittest.main()