Repository: melexis/warnings-plugin

Issue:
Apply warnings plugin on Coverity Defects
We need another checker for increased Coverity Defects. Also in consideration we could use checker for cppcheck or any other static analysis tool.

Discussion:

Patch:
diff --git a/README.rst b/README.rst
index 8551b23..3e2376b 100644
--- a/README.rst
+++ b/README.rst
@@ -175,6 +175,37 @@ command:
     python -m mlx.warnings --doxygen --command <commandfordoxygen>
 
 
+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 <commandforcoverity>
+
+    # 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 <commandforcoverity>
+    python -m mlx.warnings --coverity --command <commandforcoverity>
+
+
 Parse for JUnit failures
 ------------------------
 
diff --git a/src/mlx/warnings.py b/src/mlx/warnings.py
index cadebbe..0455636 100644
--- a/src/mlx/warnings.py
+++ b/src/mlx/warnings.py
@@ -9,7 +9,7 @@ import os
 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 @@ class WarningsPlugin:
         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 @@ class WarningsPlugin:
 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 4b43371..2728533 100644
--- a/src/mlx/warnings_checker.py
+++ b/src/mlx/warnings_checker.py
@@ -16,6 +16,9 @@ sphinx_pattern = re.compile(SPHINX_WARNING_REGEX)
 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<curr>\d+) of (?P<max>\d+)\): (?P<checker>.+)\): (?P<classification>\w+), *(.+)\n?"
+coverity_pattern = re.compile(COVERITY_WARNING_REGEX)
+
 
 class WarningsChecker(object):
     name = 'checker'
@@ -189,4 +192,24 @@ class JUnitChecker(WarningsChecker):
             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')) and \
+                    (match.group('classification') in self.CLASSIFICATION):
+                self.count += 1
+                if self.verbose:
+                    print(match.group(0).strip())
 


Test patch:
diff --git a/tests/coverity_single_defect.txt b/tests/coverity_single_defect.txt
new file mode 100644
index 0000000..b521afd
--- /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 0000000..25ff3e0
--- /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 b137a67..9212c8f 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -26,6 +26,10 @@ class TestIntegration(TestCase):
         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)