Skip to content

Commit

Permalink
Merge pull request #150 from melexis/fix-cov-var
Browse files Browse the repository at this point in the history
Support environment variables in the configuration for CoverityChecker
  • Loading branch information
JasperCraeghs authored Nov 26, 2024
2 parents 811bab3 + 8dd1bbd commit 13cc6b0
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 76 deletions.
30 changes: 12 additions & 18 deletions src/mlx/warnings/regex_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,13 @@ class CoverityChecker(RegexChecker):
def __init__(self, verbose=False):
super().__init__(verbose)
self._cq_description_template = Template('Coverity: $checker')
self.checkers = {}
self.checkers = {
"unclassified": CoverityClassificationChecker("unclassified", verbose=self.verbose),
"pending": CoverityClassificationChecker("pending", verbose=self.verbose),
"bug": CoverityClassificationChecker("bug", verbose=self.verbose),
"intentional": CoverityClassificationChecker("intentional", verbose=self.verbose),
"false positive": CoverityClassificationChecker("false positive", verbose=self.verbose),
}

@property
def counted_warnings(self):
Expand Down Expand Up @@ -140,15 +146,14 @@ def check(self, content):
matches = re.finditer(self.pattern, content)
for match in matches:
if (classification := match.group("classification").lower()) in self.checkers:
self.checkers[classification].check(match)
else:
checker = CoverityClassificationChecker(classification=classification, verbose=self.verbose)
self.checkers[classification] = checker
checker = self.checkers[classification]
checker.cq_enabled = self.cq_enabled
checker.exclude_patterns = self.exclude_patterns
checker.cq_description_template = self.cq_description_template
checker.cq_default_path = self.cq_default_path
checker.check(match)
else:
print(f"WARNING: Unrecognized classification {match.group('classification')!r}")

def parse_config(self, config):
"""Process configuration
Expand All @@ -165,22 +170,11 @@ def parse_config(self, config):
self.add_patterns(value, self.exclude_patterns)
for classification, checker_config in config.items():
classification_key = classification.lower().replace("_", " ")
if classification_key in CoverityClassificationChecker.SEVERITY_MAP:
checker = CoverityClassificationChecker(classification=classification_key, verbose=self.verbose)
if maximum := checker_config.get("max", 0):
checker.maximum = int(maximum)
if minimum := checker_config.get("min", 0):
checker.minimum = int(minimum)
self.checkers[classification_key] = checker
if classification_key in self.checkers:
self.checkers[classification_key].parse_config(checker_config)
else:
print(f"WARNING: Unrecognized classification {classification!r}")

for checker in self.checkers.values():
checker.cq_enabled = self.cq_enabled
checker.exclude_patterns = self.exclude_patterns
checker.cq_description_template = self.cq_description_template
checker.cq_default_path = self.cq_default_path


class CoverityClassificationChecker(WarningsChecker):
SEVERITY_MAP = {
Expand Down
2 changes: 2 additions & 0 deletions src/mlx/warnings/warnings.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ def check_logfile(self, file):
if not self.activated_checkers:
print("No checkers activated. Please use activate_checker function")
elif "polyspace" in self.activated_checkers:
if len(self.activated_checkers) > 1:
raise WarningsConfigError("Polyspace checker cannot be combined with other warnings checkers")
self.activated_checkers["polyspace"].check(file)
else:
content = file.read()
Expand Down
16 changes: 13 additions & 3 deletions tests/test_coverity.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import filecmp
import os
from io import StringIO
from unittest import TestCase
from pathlib import Path
import filecmp

from unittest import TestCase, mock
from unittest.mock import patch

from mlx.warnings import WarningsPlugin, warnings_wrapper, Finding
Expand All @@ -11,6 +11,16 @@
TEST_OUT_DIR = Path(__file__).parent / 'test_out'


def ordered(obj):
if isinstance(obj, dict):
return sorted((k, ordered(v)) for k, v in obj.items())
if isinstance(obj, list):
return sorted(ordered(x) for x in obj)
else:
return obj


@mock.patch.dict(os.environ, {"MIN_COV_WARNINGS": "1", "MAX_COV_WARNINGS": "2"})
class TestCoverityWarnings(TestCase):
def setUp(self):
Finding.fingerprints = {}
Expand Down
14 changes: 7 additions & 7 deletions tests/test_in/code_quality_format.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
}
}
},
"fingerprint": "8c59fc98f57e808819b5f04054aa5de3"
"fingerprint": "39030b49f58180c8172732f84d9d5fff"
},
{
"severity": "critical",
Expand All @@ -53,7 +53,7 @@
}
}
},
"fingerprint": "77c88c2e8519e4416776e8b96284e144"
"fingerprint": "e24313e32a3969502b2bc7c9ad9b1527"
},
{
"severity": "major",
Expand All @@ -67,7 +67,7 @@
}
}
},
"fingerprint": "a48d5550fd854f154597077dc51d1f8a"
"fingerprint": "bf26b89fc5898e957279a23a9540640f"
},
{
"severity": "critical",
Expand All @@ -81,7 +81,7 @@
}
}
},
"fingerprint": "22d31cc45bf8d96df2f1174df53d1adc"
"fingerprint": "6ca9fab3cdb5050ad01b2b02ec348180"
},
{
"severity": "critical",
Expand All @@ -95,7 +95,7 @@
}
}
},
"fingerprint": "041265549f9d63651d00019265a3ad90"
"fingerprint": "322c085b4098450c371da8c99046476b"
},
{
"severity": "major",
Expand All @@ -109,7 +109,7 @@
}
}
},
"fingerprint": "75e6473e0ee239e0f0b7094b12404afc"
"fingerprint": "bec264a0725556cfe0514b3aa168715c"
},
{
"severity": "major",
Expand All @@ -123,6 +123,6 @@
}
}
},
"fingerprint": "3c02f687278c969c497d8974994e8f76"
"fingerprint": "37dd4fe77518650ac6d416ea8e2e617f"
}
]
5 changes: 5 additions & 0 deletions tests/test_in/config_example_coverity.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
coverity:
enabled: true
unclassified:
min: $MIN_COV_WARNINGS
max: '$MAX_COV_WARNINGS'
intentional:
min: 0
max: -1
bug:
min: 0
max: 0
pending:
min: 0
max: 0
false_positive:
min: 0
max: -1
sphinx:
enabled: false
Expand Down
43 changes: 43 additions & 0 deletions tests/test_in/config_example_polyspace_error.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
sphinx:
enabled: true
min: 0
max: 0
exclude:
- RemovedInSphinx\d+Warning
- 'WARNING: toctree'
doxygen:
enabled: false
junit:
enabled: false
xmlrunner:
enabled: false
coverity:
enabled: false
robot:
enabled: false
polyspace:
enabled: true
run-time check:
- color: red
min: 0
max: 0
- color: orange
min: 0
max: 10
global variable:
- color: red
min: 0
max: 0
- color: orange
min: 0
max: 10
defect:
- information: 'impact: high'
min: 0
max: 0
- information: 'impact: medium'
min: 0
max: 10
- information: 'impact: low'
min: 0
max: 30
84 changes: 42 additions & 42 deletions tests/test_in/coverity_cq.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,46 @@
[
{
"severity": "major",
"description": "Coverity: MISRA C-2012 Declarations and Definitions (MISRA C-2012 Rule 8.5, Required)",
"location": {
"path": "some/path/dummy_uncl.h",
"positions": {
"begin": {
"line": 194,
"column": 14
}
}
},
"fingerprint": "990f3714acdb07ca108f645285bacdf8"
},
{
"severity": "major",
"description": "Coverity: MISRA C-2012 Declarations and Definitions (MISRA C-2012 Rule 8.6, Required)",
"location": {
"path": "some/path/dummy_uncl.c",
"positions": {
"begin": {
"line": 1404,
"column": 14
}
}
},
"fingerprint": "c24f5c885dd07424839f347e7fb0cfd9"
},
{
"severity": "major",
"description": "Coverity: MISRA C-2012 Identifiers (MISRA C-2012 Rule 5.8, Required)",
"location": {
"path": "some/path/dummy_uncl.c",
"positions": {
"begin": {
"line": 923,
"column": 13
}
}
},
"fingerprint": "1f2f1b28535924cd9ab0dc2635aa70c7"
},
{
"severity": "info",
"description": "Coverity: MISRA C-2012 Standard C Environment (MISRA C-2012 Rule 1.2, Advisory)",
Expand Down Expand Up @@ -68,47 +110,5 @@
}
},
"fingerprint": "5dbd93275d026e6b77330c48eda8d964"
},
{
"severity": "major",
"description": "Coverity: MISRA C-2012 Declarations and Definitions (MISRA C-2012 Rule 8.5, Required)",
"location": {
"path": "some/path/dummy_uncl.h",
"positions": {
"begin": {
"line": 194,
"column": 14
}
}
},
"fingerprint": "990f3714acdb07ca108f645285bacdf8"
},
{
"severity": "major",
"description": "Coverity: MISRA C-2012 Declarations and Definitions (MISRA C-2012 Rule 8.6, Required)",
"location": {
"path": "some/path/dummy_uncl.c",
"positions": {
"begin": {
"line": 1404,
"column": 14
}
}
},
"fingerprint": "c24f5c885dd07424839f347e7fb0cfd9"
},
{
"severity": "major",
"description": "Coverity: MISRA C-2012 Identifiers (MISRA C-2012 Rule 5.8, Required)",
"location": {
"path": "some/path/dummy_uncl.c",
"positions": {
"begin": {
"line": 923,
"column": 13
}
}
},
"fingerprint": "1f2f1b28535924cd9ab0dc2635aa70c7"
}
]
28 changes: 22 additions & 6 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@

from unittest.mock import patch

from mlx.warnings import warnings_wrapper, WarningsConfigError
from mlx.warnings import exceptions, warnings_wrapper, WarningsConfigError, Finding

TEST_IN_DIR = Path(__file__).parent / 'test_in'
TEST_OUT_DIR = Path(__file__).parent / 'test_out'


class TestIntegration(TestCase):
def setUp(self):
Finding.fingerprints = {}
if not TEST_OUT_DIR.exists():
TEST_OUT_DIR.mkdir()

Expand Down Expand Up @@ -368,10 +369,25 @@ def test_cq_description_format(self, path_cwd_mock):
filename = 'code_quality_format.json'
out_file = str(TEST_OUT_DIR / filename)
ref_file = str(TEST_IN_DIR / filename)
retval = warnings_wrapper([
'--code-quality', out_file,
'--config', 'tests/test_in/config_cq_description_format.json',
'tests/test_in/mixed_warnings.txt',
])
with patch('sys.stdout', new=StringIO()) as fake_output:
retval = warnings_wrapper([
'--code-quality', out_file,
'--config', 'tests/test_in/config_cq_description_format.json',
'tests/test_in/mixed_warnings.txt',
])
output = fake_output.getvalue().splitlines(keepends=False)
self.assertIn("WARNING: Unrecognized classification 'max'", output)
self.assertIn("WARNING: Unrecognized classification 'min'", output)
self.assertEqual(2, retval)
self.assertTrue(filecmp.cmp(out_file, ref_file), '{} differs from {}'.format(out_file, ref_file))

@patch('pathlib.Path.cwd')
def test_polyspace_error(self, path_cwd_mock):
config_file = str(TEST_IN_DIR / 'config_example_polyspace_error.yml')
with self.assertRaises(exceptions.WarningsConfigError) as context:
warnings_wrapper([
'--config', config_file,
'tests/test_in/mixed_warnings.txt',
])
self.assertEqual(str(context.exception), 'Polyspace checker cannot be combined with other warnings checkers')

1 change: 1 addition & 0 deletions tests/test_polyspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

class TestCodeProverWarnings(unittest.TestCase):
def setUp(self):
Finding.fingerprints = {}
self.warnings = WarningsPlugin(verbose=True)
self.dut = self.warnings.activate_checker_name('polyspace')
self.dut.checkers = [
Expand Down

0 comments on commit 13cc6b0

Please sign in to comment.