From c48fbd95580d1a056ceb4f10a106c430e00ec622 Mon Sep 17 00:00:00 2001 From: Crt Mori Date: Thu, 29 Nov 2018 17:03:46 +0100 Subject: [PATCH 1/3] Initial solution for Coverity regex checker The output is produced with following command: ``` cov-run-desktop --text-output-style=oneline `git diff --name-only --ignore-submodules master` ``` --- src/mlx/warnings.py | 10 ++++-- src/mlx/warnings_checker.py | 23 ++++++++++++++ tests/coverity_single_defect.txt | 4 +++ tests/test_coverity.py | 52 ++++++++++++++++++++++++++++++++ tests/test_integration.py | 4 +++ 5 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 tests/coverity_single_defect.txt create mode 100644 tests/test_coverity.py diff --git a/src/mlx/warnings.py b/src/mlx/warnings.py index cadebbe2..0455636b 100644 --- a/src/mlx/warnings.py +++ b/src/mlx/warnings.py @@ -9,7 +9,7 @@ import subprocess import sys import glob -from mlx.warnings_checker import SphinxChecker, DoxyChecker, JUnitChecker, XMLRunnerChecker +from mlx.warnings_checker import SphinxChecker, DoxyChecker, JUnitChecker, XMLRunnerChecker, CoverityChecker from .__warnings_version__ import version as warnings_version __version__ = warnings_version @@ -28,7 +28,7 @@ def __init__(self, verbose = False, configfile= None): self.checkerList = {} self.verbose = verbose self.publicCheckers = [SphinxChecker(self.verbose), DoxyChecker(self.verbose), JUnitChecker(self.verbose), - XMLRunnerChecker(self.verbose)] + XMLRunnerChecker(self.verbose), CoverityChecker(self.verbose)] if configfile is not None: with open(configfile, 'r') as f: @@ -183,6 +183,7 @@ def config_parser_json(self, config): def warnings_wrapper(args): parser = argparse.ArgumentParser(prog='mlx-warnings') group1 = parser.add_argument_group('Configuration command line options') + group1.add_argument('--coverity', dest='coverity', action='store_true') group1.add_argument('-d', '--doxygen', dest='doxygen', action='store_true') group1.add_argument('-s', '--sphinx', dest='sphinx', action='store_true') group1.add_argument('-j', '--junit', dest='junit', action='store_true') @@ -206,7 +207,8 @@ def warnings_wrapper(args): # Read config file if args.configfile is not None: - if args.sphinx or args.doxygen or args.junit or (args.maxwarnings != 0) or (args.minwarnings != 0): + checkersflag = args.sphinx or args.doxygen or args.junit or args.coverity or args.xmlrunner + if checkersflag or (args.maxwarnings != 0) or (args.minwarnings != 0): print("Configfile cannot be provided with other arguments") sys.exit(2) warnings = WarningsPlugin(verbose=args.verbose, configfile=args.configfile) @@ -220,6 +222,8 @@ def warnings_wrapper(args): warnings.activate_checker_name('junit') if args.xmlrunner: warnings.activate_checker_name('xmlrunner') + if args.coverity: + warnings.activate_checker_name('coverity') warnings.set_maximum(args.maxwarnings) warnings.set_minimum(args.minwarnings) diff --git a/src/mlx/warnings_checker.py b/src/mlx/warnings_checker.py index 4b43371f..ae749632 100644 --- a/src/mlx/warnings_checker.py +++ b/src/mlx/warnings_checker.py @@ -16,6 +16,9 @@ PYTHON_XMLRUNNER_REGEX = r"(\s*(ERROR|FAILED) (\[\d+.\d\d\ds\]: \s*(.+)))\n?" xmlrunner_pattern = re.compile(PYTHON_XMLRUNNER_REGEX) +COVERITY_WARNING_REGEX = r"(?:((?:[/.]|[A-Za-z]).+?):(-?\d+):) (CID) \d+ \(#(?P\d+) of (?P\d+)\): (?P.+)\): (?P\w+), *(.+)\n?" +coverity_pattern = re.compile(COVERITY_WARNING_REGEX) + class WarningsChecker(object): name = 'checker' @@ -189,4 +192,24 @@ def check(self, content): return +class CoverityChecker(RegexChecker): + name = 'coverity' + pattern = coverity_pattern + CLASSIFICATION = "Unclassified" + + def check(self, content): + ''' + Function for counting the number of warnings, but adopted for Coverity + output + + Args: + content (str): The content to parse + ''' + matches = re.finditer(self.pattern, content) + for match in matches: + if match.group('curr') == match.group('max'): + if match.group('classification') in self.CLASSIFICATION: + self.count += 1 + if self.verbose: + print(match.group(0).strip()) diff --git a/tests/coverity_single_defect.txt b/tests/coverity_single_defect.txt new file mode 100644 index 00000000..b521afdb --- /dev/null +++ b/tests/coverity_single_defect.txt @@ -0,0 +1,4 @@ +/src/somefile.c:80: CID 113396 (#1 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27. +/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27. +src/something/src/somefile.c:82: 1. misra_violation: Essential type of the left hand operand "0U" (unsigned) is not the same as that of the right operand "1U"(signed). + diff --git a/tests/test_coverity.py b/tests/test_coverity.py new file mode 100644 index 00000000..25ff3e0a --- /dev/null +++ b/tests/test_coverity.py @@ -0,0 +1,52 @@ +try: + from StringIO import StringIO +except ImportError: + from io import StringIO +from mock import patch +from unittest import TestCase + +from mlx.warnings import WarningsPlugin + + +class TestCoverityWarnings(TestCase): + def setUp(self): + self.warnings = WarningsPlugin(verbose=True) + self.warnings.activate_checker_name('coverity') + + def test_no_warning_normal_text(self): + dut = 'This should not be treated as warning' + self.warnings.check(dut) + self.assertEqual(self.warnings.return_count(), 0) + + def test_no_warning_but_still_command_output(self): + dut = 'src/something/src/somefile.c:82: 1. misra_violation: Essential type of the left hand operand "0U" (unsigned) is not the same as that of the right operand "1U"(signed).' + self.warnings.check(dut) + self.assertEqual(self.warnings.return_count(), 0) + + def test_single_warning(self): + dut = '/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' + with patch('sys.stdout', new=StringIO()) as fake_out: + self.warnings.check(dut) + self.assertEqual(self.warnings.return_count(), 1) + self.assertIn(dut, fake_out.getvalue()) + + def test_single_warning_count_one(self): + dut1 = '/src/somefile.c:80: CID 113396 (#1 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' + dut2 = '/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' + with patch('sys.stdout', new=StringIO()) as fake_out: + self.warnings.check(dut1) + self.warnings.check(dut2) + self.assertEqual(self.warnings.return_count(), 1) + self.assertIn(dut2, fake_out.getvalue()) + + def test_single_warning_real_output(self): + dut1 = '/src/somefile.c:80: CID 113396 (#1 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' + dut2 = '/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' + dut3 = 'src/something/src/somefile.c:82: 1. misra_violation: Essential type of the left hand operand "0U" (unsigned) is not the same as that of the right operand "1U"(signed).' + with patch('sys.stdout', new=StringIO()) as fake_out: + self.warnings.check(dut1) + self.warnings.check(dut2) + self.warnings.check(dut3) + self.assertEqual(self.warnings.return_count(), 1) + self.assertIn(dut2, fake_out.getvalue()) + diff --git a/tests/test_integration.py b/tests/test_integration.py index b137a67b..9212c8f3 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -26,6 +26,10 @@ def test_single_argument(self): retval = warnings_wrapper(['--junit', 'tests/junit_single_fail.xml']) self.assertEqual(1, retval) + def test_single_defect_coverity(self): + retval = warnings_wrapper(['--coverity', 'tests/coverity_single_defect.txt']) + self.assertEqual(1, retval) + def test_two_arguments(self): retval = warnings_wrapper(['--junit', 'tests/junit_single_fail.xml', 'tests/junit_double_fail.xml']) self.assertEqual(1 + 2, retval) From fb6055a121895356e206ec8760f8989c4a7b5211 Mon Sep 17 00:00:00 2001 From: Crt Mori Date: Thu, 29 Nov 2018 17:10:47 +0100 Subject: [PATCH 2/3] Update readme with coverity parser commands --- README.rst | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.rst b/README.rst index 8551b23f..3e2376bb 100644 --- a/README.rst +++ b/README.rst @@ -175,6 +175,37 @@ command: python -m mlx.warnings --doxygen --command +Parse for Coverity Defects +-------------------------- + +Coverity is a static analysis tool which has option to run desktop analysis +on your local changes and report the results back directly in the console. +You only need to list affected files and below example lists changed files +between your branch and master, which it then forwards to `cov-run-desktop`: + +.. code-block:: bash + + cov-run-desktop --text-output-style=oneline `git diff --name-only --ignore-submodules master` + + +You can pipe the results to logfile, which you pass to warnings-plugin, or you use +the `--command` argument and execute the `cov-run-desktop` through + +.. code-block:: bash + + # command line log file + mlx-warnings cov-run-desktop-output.txt --coverity + # command line command execution + mlx-warnings --coverity --command + + # explicitly as python module for log file + python3 -m mlx.warnings --coverity cov-run-desktop-output.txt + python -m mlx.warnings --coverity cov-run-desktop-output.txt + # explicitly as python module + python3 -m mlx.warnings --coverity --command + python -m mlx.warnings --coverity --command + + Parse for JUnit failures ------------------------ From 8d1d6cf350bdf62553c1cb6a87cd2f75dbea87ec Mon Sep 17 00:00:00 2001 From: Crt Mori Date: Thu, 29 Nov 2018 20:41:50 +0100 Subject: [PATCH 3/3] Remove code-climate issue --- src/mlx/warnings_checker.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mlx/warnings_checker.py b/src/mlx/warnings_checker.py index ae749632..27285334 100644 --- a/src/mlx/warnings_checker.py +++ b/src/mlx/warnings_checker.py @@ -207,9 +207,9 @@ def check(self, content): ''' matches = re.finditer(self.pattern, content) for match in matches: - if match.group('curr') == match.group('max'): - if match.group('classification') in self.CLASSIFICATION: - self.count += 1 - if self.verbose: - print(match.group(0).strip()) + if (match.group('curr') == match.group('max')) and \ + (match.group('classification') in self.CLASSIFICATION): + self.count += 1 + if self.verbose: + print(match.group(0).strip())