| #!/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() |