From ce366543d9db25a5d753b21dd98d6f7b60661d43 Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 3 Sep 2024 16:21:34 +0200 Subject: [PATCH 01/31] Fix command to avoid warning --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 49286506..5b1090f2 100644 --- a/README.rst +++ b/README.rst @@ -200,7 +200,7 @@ 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 + mlx-warnings --coverity cov-run-desktop-output.txt # command line command execution mlx-warnings --coverity --command From 6a5bef11a408a556f9fdbbf0d818cdc32460496a Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 17 Sep 2024 16:10:41 +0200 Subject: [PATCH 02/31] Add docstring to add_code_quality_finding --- src/mlx/warnings/regex_checker.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index cb833f6b..d0914305 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -49,6 +49,11 @@ def check(self, content): self.add_code_quality_finding(match) def add_code_quality_finding(self, match): + '''Add code quality finding + + Args: + match (re.Match): The regex match + ''' finding = { "severity": "major", "location": { From 4dde711d191c58403cbf1b72ff01756a1336fb2c Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 17 Sep 2024 17:15:15 +0200 Subject: [PATCH 03/31] Update COVERITY_WARNING_REGEX --- src/mlx/warnings/regex_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index d0914305..4cea11cf 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -13,7 +13,7 @@ PYTHON_XMLRUNNER_REGEX = r"(\s*(?PERROR|FAILED) (\[\d+\.\d{3}s\]: \s*(?P.+)))\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_WARNING_REGEX = r"(?P[\d\w/\\/-_]+\.\w+)(:(?P\d+)(:(?P\d+))?)?: ?(CID) \d+ \(#(?P\d+) of (?P\d+)\): (?P.+\)): (?P[\w ]+), *(.+)\n?" coverity_pattern = re.compile(COVERITY_WARNING_REGEX) From d0f5493228a6049b2fea0e6ae07df7c700496836 Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 17 Sep 2024 17:21:42 +0200 Subject: [PATCH 04/31] Allow max to be -1 -1 will be converted to math.inf --- src/mlx/warnings/warnings_checker.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index 23d87e70..3562cd47 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -1,4 +1,5 @@ import abc +from math import inf import os import re from string import Template @@ -78,6 +79,8 @@ def maximum(self): @maximum.setter def maximum(self, maximum): + if maximum == -1: + maximum = inf if self._minimum > maximum: raise ValueError("Invalid argument: maximum limit must be higher than minimum limit ({min}); cannot " "set {max}.".format(max=maximum, min=self._minimum)) From 95fb126166b0134536ed54267bbe8a855f563f49 Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 17 Sep 2024 17:29:21 +0200 Subject: [PATCH 05/31] Make use of CoverityClassificationChecker that checks warnings for a specific classification The classifications can be specified in a config file --- src/mlx/warnings/regex_checker.py | 113 ++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 7 deletions(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 4cea11cf..88c3b735 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -1,6 +1,7 @@ import hashlib import re from pathlib import Path +from string import Template from .warnings_checker import WarningsChecker @@ -100,7 +101,32 @@ def add_code_quality_finding(self, match): class CoverityChecker(RegexChecker): name = 'coverity' pattern = coverity_pattern - CLASSIFICATION = "Unclassified" + checkers = {} + + def return_count(self): + ''' Getter function for the amount of warnings found + + Returns: + int: Number of warnings found + ''' + self.count = 0 + for checker in self.checkers.values(): + self.count += checker.return_count() + return self.count + + def return_check_limits(self): + ''' Function for checking whether the warning count is within the configured limits + + Returns: + int: 0 if the amount of warnings is within limits, the count of warnings otherwise + (or 1 in case of a count of 0 warnings) + ''' + count = 0 + for checker in self.checkers.values(): + print(f"Counted failures for classification {checker.classification!r}") + count += checker.return_check_limits() + print(f"total warnings = {count}") + return count def check(self, content): ''' @@ -112,12 +138,85 @@ def check(self, content): ''' 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 - match_string = match.group(0).strip() - self.counted_warnings.append(match_string) - self.print_when_verbose(match_string) + 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.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) + + def parse_config(self, config): + """Parsing configuration dict extracted by previously opened JSON or yaml/yml file + + Args: + config (dict): Content of configuration file + """ + for key in config: + if key == "enabled": + continue + if key == "cq_description_template": + self.cq_description_template = Template(config['cq_description_template']) + continue + if key == "cq_default_path": + self.cq_default_path = config['cq_default_path'] + continue + if key == "exclude": + self.add_patterns(config.get("exclude"), self.exclude_patterns) + continue + if (classification := key) in ["unclassified", "pending", "false_positive", "intentional", "bug"]: + classification_lower = classification.lower().replace("_", " ") + checker = CoverityClassificationChecker(classification=classification_lower, verbose=self.verbose) + if isinstance((maximum := config[classification].get("max", 0)), (int, str)): + checker.maximum = int(maximum) + if isinstance((minimum := config[classification].get("min", 0)), (int, str)): + checker.minimum = int(minimum) + checker.cq_findings = self.cq_findings # share object with sub-checkers + self.checkers[classification_lower] = checker + else: + print(f"WARNING: Unrecognized classification {key!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): + def __init__(self, classification, **kwargs): + """Initialize the CoverityClassificationChecker: + + Args: + classification (str): The coverity classification + """ + super().__init__(**kwargs) + self.classification = classification + + def return_count(self): + ''' Getter function for the amount of warnings found + + Returns: + int: Number of warnings found + ''' + return self.count + + def check(self, content): + ''' + Function for counting the number of warnings, but adopted for Coverity + output + + Args: + content (re.Match): The regex match + ''' + match_string = content.group(0).strip() + if not self._is_excluded(match_string): + self.count += 1 + self.counted_warnings.append(match_string) + self.print_when_verbose(match_string) class DoxyChecker(RegexChecker): From 21eab6d1a1d2f0ef8573a0a07703796eca1e9309 Mon Sep 17 00:00:00 2001 From: JWM Date: Wed, 18 Sep 2024 11:11:38 +0200 Subject: [PATCH 06/31] Add counted_warnings to parent class Take all the warnings of the children --- src/mlx/warnings/regex_checker.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 88c3b735..6bfc3a43 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -103,6 +103,14 @@ class CoverityChecker(RegexChecker): pattern = coverity_pattern checkers = {} + @property + def counted_warnings(self): + ''' List: list of counted warnings (str) ''' + all_counted_warnings = [] + for checker in self.checkers.values(): + all_counted_warnings.extend(checker.counted_warnings) + return all_counted_warnings + def return_count(self): ''' Getter function for the amount of warnings found From 62cac54b2da3db1466cf63305d05fd221c44421b Mon Sep 17 00:00:00 2001 From: JWM Date: Wed, 18 Sep 2024 11:44:52 +0200 Subject: [PATCH 07/31] Add code quality report for coverity checker --- src/mlx/warnings/regex_checker.py | 86 +++++++++++++++++++++++++++++++ src/mlx/warnings/warnings.py | 2 +- 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 6bfc3a43..71a8923b 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -1,8 +1,10 @@ import hashlib +import os import re from pathlib import Path from string import Template +from .exceptions import WarningsConfigError from .warnings_checker import WarningsChecker DOXYGEN_WARNING_REGEX = r"(?:(?P(?:[/.]|[A-Za-z]).+?):(?P-?\d+):\s*(?P[Ww]arning|[Ee]rror)|<.+>:(?P-?\d+)(?::\s*(?P[Ww]arning|[Ee]rror))?): (?P.+(?:(?!\s*([Nn]otice|[Ww]arning|[Ee]rror): )[^/<\n][^:\n][^/\n].+)*)|\s*\b(?P[Nn]otice|[Ww]arning|[Ee]rror): (?!notes)(?P.+)\n?" @@ -103,6 +105,10 @@ class CoverityChecker(RegexChecker): pattern = coverity_pattern checkers = {} + def __init__(self, verbose=False): + super().__init__(verbose) + self._cq_description_template = Template('Coverity: $checker') + @property def counted_warnings(self): ''' List: list of counted warnings (str) ''' @@ -111,6 +117,15 @@ def counted_warnings(self): all_counted_warnings.extend(checker.counted_warnings) return all_counted_warnings + @property + def cq_description_template(self): + ''' Template: string.Template instance based on the configured template string ''' + return self._cq_description_template + + @cq_description_template.setter + def cq_description_template(self, template_obj): + self._cq_description_template = template_obj + def return_count(self): ''' Getter function for the amount of warnings found @@ -195,6 +210,14 @@ def parse_config(self, config): class CoverityClassificationChecker(WarningsChecker): + SEVERITY_MAP = { + 'false positive': 'info', + 'intentional': 'info', + 'bug': 'major', + 'unclassified': 'major', + 'pending': 'critical', + } + def __init__(self, classification, **kwargs): """Initialize the CoverityClassificationChecker: @@ -204,6 +227,15 @@ def __init__(self, classification, **kwargs): super().__init__(**kwargs) self.classification = classification + @property + def cq_description_template(self): + ''' Template: string.Template instance based on the configured template string ''' + return self._cq_description_template + + @cq_description_template.setter + def cq_description_template(self, template_obj): + self._cq_description_template = template_obj + def return_count(self): ''' Getter function for the amount of warnings found @@ -212,6 +244,58 @@ def return_count(self): ''' return self.count + def add_code_quality_finding(self, match): + '''Add code quality finding + + Args: + match (re.Match): The regex match + ''' + finding = { + "severity": "major", + "location": { + "path": self.cq_default_path, + "positions": { + "begin": { + "line": 1, + "column": 1 + } + } + } + } + groups = {name: result for name, result in match.groupdict().items() if result} + try: + description = self.cq_description_template.substitute(os.environ, **groups) + except KeyError as err: + raise WarningsConfigError(f"Failed to find environment variable from configuration value " + f"'cq_description_template': {err}") from err + if "classification" in groups: + finding["severity"] = self.SEVERITY_MAP[groups.get("classification", "unclassified").lower()] + if "path" in groups: + path = Path(groups["path"]) + if path.is_absolute(): + try: + path = path.relative_to(Path.cwd()) + except ValueError as err: + raise ValueError("Failed to convert abolute path to relative path for Code Quality report: " + f"{err}") from err + finding["location"]["path"] = str(path) + if "line" in groups: + try: + line_number = int(groups["line"], 0) + except (TypeError, ValueError): + line_number = 1 + finding["location"]["positions"]["begin"]["line"] = line_number + if "col" in groups: + try: + column_number = int(groups["col"], 0) + except (TypeError, ValueError): + column_number = 1 + finding["location"]["positions"]["begin"]["column"] = column_number + + finding["description"] = description + finding["fingerprint"] = hashlib.md5(str(match.group(0).strip()).encode('utf8')).hexdigest() + self.cq_findings.append(finding) + def check(self, content): ''' Function for counting the number of warnings, but adopted for Coverity @@ -225,6 +309,8 @@ def check(self, content): self.count += 1 self.counted_warnings.append(match_string) self.print_when_verbose(match_string) + if self.cq_enabled: + self.add_code_quality_finding(content) class DoxyChecker(RegexChecker): diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index 634fd56e..bdfbba26 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -59,7 +59,7 @@ def activate_checker(self, checker): Args: checker (WarningsChecker): checker object ''' - checker.cq_enabled = self.cq_enabled and checker.name in ('doxygen', 'sphinx', 'xmlrunner', 'polyspace') + checker.cq_enabled = self.cq_enabled and checker.name in ('doxygen', 'sphinx', 'xmlrunner', 'polyspace', 'coverity') self.activated_checkers[checker.name] = checker def activate_checker_name(self, name): From 36dc254366d4a523a4ddacc7ea3ff9b88c7ed049 Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 19 Sep 2024 10:28:34 +0200 Subject: [PATCH 08/31] Let checkers be an instance variable to pass tests --- src/mlx/warnings/regex_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 71a8923b..4b354ae5 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -103,11 +103,11 @@ def add_code_quality_finding(self, match): class CoverityChecker(RegexChecker): name = 'coverity' pattern = coverity_pattern - checkers = {} def __init__(self, verbose=False): super().__init__(verbose) self._cq_description_template = Template('Coverity: $checker') + self.checkers = {} @property def counted_warnings(self): From 80982c320e7496692a1ee51283965c8e63c711db Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 19 Sep 2024 10:29:25 +0200 Subject: [PATCH 09/31] Only count one of the same multiple warnings --- src/mlx/warnings/regex_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 4b354ae5..5d80875c 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -305,7 +305,7 @@ def check(self, content): content (re.Match): The regex match ''' match_string = content.group(0).strip() - if not self._is_excluded(match_string): + if not self._is_excluded(match_string) and (content.group('curr') == content.group('max')): self.count += 1 self.counted_warnings.append(match_string) self.print_when_verbose(match_string) From c9ee144214d808b95adaf4e6ec984afacb997607 Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 19 Sep 2024 11:18:13 +0200 Subject: [PATCH 10/31] Use updated configuration for coverity --- tests/test_in/config_example.json | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/test_in/config_example.json b/tests/test_in/config_example.json index 04d827ac..a0a44261 100644 --- a/tests/test_in/config_example.json +++ b/tests/test_in/config_example.json @@ -23,8 +23,21 @@ }, "coverity": { "enabled": true, - "min": 0, - "max": 0 + "cq_default_path": "dummy/path", + "intentional": { + "min": 0, + "max": "-1" + }, + "bug": { + "max": 0 + }, + "pending": { + "min": 0, + "max": 0 + }, + "false_positive": { + "max": -1 + } }, "robot": { "enabled": false, From e8d02b003b1a341c08b4a779ad106a86798de854 Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 19 Sep 2024 11:19:01 +0200 Subject: [PATCH 11/31] Fix tests where conversion to relative path won't work --- tests/test_in/mixed_warnings.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_in/mixed_warnings.txt b/tests/test_in/mixed_warnings.txt index 4a25ccb6..b46a5c4d 100644 --- a/tests/test_in/mixed_warnings.txt +++ b/tests/test_in/mixed_warnings.txt @@ -15,5 +15,5 @@ WARNING: List item 'CL-UNDEFINED_CL_ITEM' in merge/pull request 138 is not defin 'ERROR [0.000s]: test_some_error_test (something.anything.somewhere)' # Coverity -/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. - +/home/user/myproject/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/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. From 052ba9c6c583822f6b9905c2e26c18c90084f0e4 Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Mon, 23 Sep 2024 17:00:45 +0200 Subject: [PATCH 12/31] Update docstring Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- src/mlx/warnings/regex_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 5d80875c..20773323 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -173,7 +173,7 @@ def check(self, content): checker.check(match) def parse_config(self, config): - """Parsing configuration dict extracted by previously opened JSON or yaml/yml file + """Process configuration Args: config (dict): Content of configuration file From db061fb26d7f70d4ce1dfa70ff28b80b1025c17e Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:00:58 +0200 Subject: [PATCH 13/31] Use walrus operator + remove unclassified since major is default severity Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- src/mlx/warnings/regex_checker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 20773323..76e5dfc7 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -268,8 +268,8 @@ def add_code_quality_finding(self, match): except KeyError as err: raise WarningsConfigError(f"Failed to find environment variable from configuration value " f"'cq_description_template': {err}") from err - if "classification" in groups: - finding["severity"] = self.SEVERITY_MAP[groups.get("classification", "unclassified").lower()] + if classification_raw := groups.get("classification"): + finding["severity"] = self.SEVERITY_MAP[classification_raw.lower()] if "path" in groups: path = Path(groups["path"]) if path.is_absolute(): From ba2e649e858be86a4b8eb326b9a52705bd3a8c35 Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:25:35 +0200 Subject: [PATCH 14/31] Use kwargs to initialize Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- src/mlx/warnings/regex_checker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 76e5dfc7..b11f7e10 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -104,8 +104,8 @@ class CoverityChecker(RegexChecker): name = 'coverity' pattern = coverity_pattern - def __init__(self, verbose=False): - super().__init__(verbose) + def __init__(self, **kwargs): + super().__init__(**kwargs) self._cq_description_template = Template('Coverity: $checker') self.checkers = {} From 85b095b4aa4a67f8e478137b2699b57a0bac206f Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:32:18 +0200 Subject: [PATCH 15/31] Update COVERITY_WARNING_REGEX Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- src/mlx/warnings/regex_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index b11f7e10..f1cc363a 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -16,7 +16,7 @@ PYTHON_XMLRUNNER_REGEX = r"(\s*(?PERROR|FAILED) (\[\d+\.\d{3}s\]: \s*(?P.+)))\n?" xmlrunner_pattern = re.compile(PYTHON_XMLRUNNER_REGEX) -COVERITY_WARNING_REGEX = r"(?P[\d\w/\\/-_]+\.\w+)(:(?P\d+)(:(?P\d+))?)?: ?(CID) \d+ \(#(?P\d+) of (?P\d+)\): (?P.+\)): (?P[\w ]+), *(.+)\n?" +COVERITY_WARNING_REGEX = r"(?P[\d\w/\\/-_]+\.\w+)(:(?P\d+)(:(?P\d+))?)?: ?CID \d+ \(#(?P\d+) of (?P\d+)\): (?P.+): (?P[\w ]+),.+" coverity_pattern = re.compile(COVERITY_WARNING_REGEX) From 4383fbe18f54b17a9da3f546fe3736ad7de03b0a Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:43:02 +0200 Subject: [PATCH 16/31] Fix typo Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- src/mlx/warnings/regex_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index f1cc363a..cf8fba15 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -293,7 +293,7 @@ def add_code_quality_finding(self, match): finding["location"]["positions"]["begin"]["column"] = column_number finding["description"] = description - finding["fingerprint"] = hashlib.md5(str(match.group(0).strip()).encode('utf8')).hexdigest() + finding["fingerprint"] = hashlib.md5(str(match.group(0).strip()).encode('utf-8')).hexdigest() self.cq_findings.append(finding) def check(self, content): From bc518bef114ee3245e373bf3990ccd5ccfca4094 Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:59:55 +0200 Subject: [PATCH 17/31] Use default column Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- src/mlx/warnings/regex_checker.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index cf8fba15..cc49aa6b 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -287,10 +287,9 @@ def add_code_quality_finding(self, match): finding["location"]["positions"]["begin"]["line"] = line_number if "col" in groups: try: - column_number = int(groups["col"], 0) + finding["location"]["positions"]["begin"]["column"] = int(groups["col"], 0) except (TypeError, ValueError): - column_number = 1 - finding["location"]["positions"]["begin"]["column"] = column_number + pass finding["description"] = description finding["fingerprint"] = hashlib.md5(str(match.group(0).strip()).encode('utf-8')).hexdigest() From c5b72edf6a3d14cbc673ce0ef215244874d50dbe Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 24 Sep 2024 11:21:22 +0200 Subject: [PATCH 18/31] Process all non-classification keys first --- src/mlx/warnings/regex_checker.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index cc49aa6b..4134654d 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -178,19 +178,15 @@ def parse_config(self, config): Args: config (dict): Content of configuration file """ - for key in config: - if key == "enabled": - continue - if key == "cq_description_template": - self.cq_description_template = Template(config['cq_description_template']) - continue - if key == "cq_default_path": - self.cq_default_path = config['cq_default_path'] - continue - if key == "exclude": - self.add_patterns(config.get("exclude"), self.exclude_patterns) - continue - if (classification := key) in ["unclassified", "pending", "false_positive", "intentional", "bug"]: + config.pop("enabled") + if value := config.pop("cq_description_template", None): + self.cq_description_template = Template(value) + if value := config.pop("cq_default_path", None): + self.cq_default_path = value + if value:= config.pop("exclude", None): + self.add_patterns(value, self.exclude_patterns) + for classification in config: + if classification in ["unclassified", "pending", "false_positive", "intentional", "bug"]: classification_lower = classification.lower().replace("_", " ") checker = CoverityClassificationChecker(classification=classification_lower, verbose=self.verbose) if isinstance((maximum := config[classification].get("max", 0)), (int, str)): @@ -200,7 +196,7 @@ def parse_config(self, config): checker.cq_findings = self.cq_findings # share object with sub-checkers self.checkers[classification_lower] = checker else: - print(f"WARNING: Unrecognized classification {key!r}") + print(f"WARNING: Unrecognized classification {classification!r}") for checker in self.checkers.values(): checker.cq_enabled = self.cq_enabled From d9f92e09d3214c72c8972fd9cb0c32adb501f60a Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 24 Sep 2024 11:30:11 +0200 Subject: [PATCH 19/31] Get rid of duplicated code Combine line and column --- src/mlx/warnings/regex_checker.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 4134654d..c202aa99 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -16,7 +16,7 @@ PYTHON_XMLRUNNER_REGEX = r"(\s*(?PERROR|FAILED) (\[\d+\.\d{3}s\]: \s*(?P.+)))\n?" xmlrunner_pattern = re.compile(PYTHON_XMLRUNNER_REGEX) -COVERITY_WARNING_REGEX = r"(?P[\d\w/\\/-_]+\.\w+)(:(?P\d+)(:(?P\d+))?)?: ?CID \d+ \(#(?P\d+) of (?P\d+)\): (?P.+): (?P[\w ]+),.+" +COVERITY_WARNING_REGEX = r"(?P[\d\w/\\/-_]+\.\w+)(:(?P\d+)(:(?P\d+))?)?: ?CID \d+ \(#(?P\d+) of (?P\d+)\): (?P.+): (?P[\w ]+),.+" coverity_pattern = re.compile(COVERITY_WARNING_REGEX) @@ -275,17 +275,12 @@ def add_code_quality_finding(self, match): raise ValueError("Failed to convert abolute path to relative path for Code Quality report: " f"{err}") from err finding["location"]["path"] = str(path) - if "line" in groups: - try: - line_number = int(groups["line"], 0) - except (TypeError, ValueError): - line_number = 1 - finding["location"]["positions"]["begin"]["line"] = line_number - if "col" in groups: - try: - finding["location"]["positions"]["begin"]["column"] = int(groups["col"], 0) - except (TypeError, ValueError): - pass + for group_name in ("line", "column"): + if group_name in groups: + try: + finding["location"]["positions"]["begin"][group_name] = int(groups[group_name], 0) + except (TypeError, ValueError): + pass finding["description"] = description finding["fingerprint"] = hashlib.md5(str(match.group(0).strip()).encode('utf-8')).hexdigest() From 0d4f25f86eec65933f717bc3f5fbe4385dcb56e5 Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 24 Sep 2024 11:32:02 +0200 Subject: [PATCH 20/31] Fix typos of `utf-8` --- src/mlx/warnings/polyspace_checker.py | 2 +- src/mlx/warnings/regex_checker.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index f7e37b86..e3aa317a 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -245,7 +245,7 @@ def add_code_quality_finding(self, row): finding["description"] = description exclude = ("new", "status", "severity", "comment", "key") row_without_key = [value for key, value in row.items() if key not in exclude] - finding["fingerprint"] = hashlib.md5(str(row_without_key).encode('utf8')).hexdigest() + finding["fingerprint"] = hashlib.md5(str(row_without_key).encode('utf-8')).hexdigest() self.cq_findings.append(finding) def check(self, content): diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index c202aa99..4c8aff87 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -96,7 +96,7 @@ def add_code_quality_finding(self, match): lineno = 1 finding["location"]["lines"]["begin"] = lineno break - finding["fingerprint"] = hashlib.md5(str(finding).encode('utf8')).hexdigest() + finding["fingerprint"] = hashlib.md5(str(finding).encode('utf-8')).hexdigest() self.cq_findings.append(finding) From 6494a0b162a9f15459c79c661517ccb3d1cf69c8 Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 24 Sep 2024 11:36:15 +0200 Subject: [PATCH 21/31] Explain behaviour in docstring Multiple warnings are counted as one for the same CID --- src/mlx/warnings/regex_checker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 4c8aff87..e03a51c5 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -288,8 +288,8 @@ def add_code_quality_finding(self, match): def check(self, content): ''' - Function for counting the number of warnings, but adopted for Coverity - output + Function for counting the number of warnings, but adopted for Coverity output. + Multiple warnings for the same CID are counted as one. Args: content (re.Match): The regex match From e9ee1add606d638d9c70aa0706e5b0c660193aac Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 24 Sep 2024 11:38:19 +0200 Subject: [PATCH 22/31] Revert "Use kwargs to initialize" This reverts commit ba2e649e858be86a4b8eb326b9a52705bd3a8c35. --- src/mlx/warnings/regex_checker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index e03a51c5..7467a1f8 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -104,8 +104,8 @@ class CoverityChecker(RegexChecker): name = 'coverity' pattern = coverity_pattern - def __init__(self, **kwargs): - super().__init__(**kwargs) + def __init__(self, verbose=False): + super().__init__(verbose) self._cq_description_template = Template('Coverity: $checker') self.checkers = {} From 47926867fa1828ec5aefcb9e35ea6b1b665d3ef5 Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 24 Sep 2024 12:16:42 +0200 Subject: [PATCH 23/31] Add more info that is usefull to document --- README.rst | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 5b1090f2..0765fa57 100644 --- a/README.rst +++ b/README.rst @@ -193,7 +193,6 @@ between your branch and master, which it then forwards to ``cov-run-desktop``: 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 @@ -212,6 +211,50 @@ the ``--command`` argument and execute the ``cov-run-desktop`` through python -m mlx.warnings --coverity --command +We utilize `cov-run-desktop` in the following manner, where the output is saved in `coverity.log`: + +.. code-block:: bash + + cov-run-desktop --text-output-style=oneline --exit1-if-defects false --triage-attribute-regex "classification" ".*" | tee coverity.log + +Subsequently, we process the `coverity.log` file with the mlx-warnings plugin. +The plugin uses a configuration file (`warnings_coverity.yml`) and produces two outputs: +a text file (`warnings_coverity.txt`) and a code quality JSON file (`coverity_code_quality.json`). + +.. code-block:: bash + + mlx-warnings --config warnings_coverity.yml -o warnings_coverity.txt -C coverity_code_quality.json coverity.log + +This is an example of the configuration file: + +.. code-block:: yml + + sphinx: + enabled: false + doxygen: + enabled: false + junit: + enabled: false + xmlrunner: + enabled: false + coverity: + enabled: true + intentional: + max: -1 + bug: + max: 0 + pending: + max: 0 + false_positive: + max: -1 + robot: + enabled: false + polyspace: + enabled: false + +For each classification, a minimum and maximum can be given. + + Parse for JUnit Failures ------------------------ @@ -392,8 +435,14 @@ The values for 'min' and 'max' can be set with environment variables via a }, "coverity": { "enabled": false, - "min": 0, - "max": 0 + "bug": { + "min": 0, + "max": 0 + }, + "pending": { + "min": 0, + "max": 0 + } }, "robot": { "enabled": false, From e9e8dbe81d71df5497321c023ed8f7e221476139 Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 24 Sep 2024 12:56:42 +0200 Subject: [PATCH 24/31] Remove checking the type --- src/mlx/warnings/regex_checker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 7467a1f8..629c0ef0 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -189,9 +189,9 @@ def parse_config(self, config): if classification in ["unclassified", "pending", "false_positive", "intentional", "bug"]: classification_lower = classification.lower().replace("_", " ") checker = CoverityClassificationChecker(classification=classification_lower, verbose=self.verbose) - if isinstance((maximum := config[classification].get("max", 0)), (int, str)): + if maximum := config[classification].get("max", 0): checker.maximum = int(maximum) - if isinstance((minimum := config[classification].get("min", 0)), (int, str)): + if minimum := config[classification].get("min", 0): checker.minimum = int(minimum) checker.cq_findings = self.cq_findings # share object with sub-checkers self.checkers[classification_lower] = checker From 369053f5fa0722afe80c9bd75b469d82d6c3a59d Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 24 Sep 2024 13:00:05 +0200 Subject: [PATCH 25/31] Add missing space --- src/mlx/warnings/regex_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 629c0ef0..950ce006 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -183,7 +183,7 @@ def parse_config(self, config): self.cq_description_template = Template(value) if value := config.pop("cq_default_path", None): self.cq_default_path = value - if value:= config.pop("exclude", None): + if value := config.pop("exclude", None): self.add_patterns(value, self.exclude_patterns) for classification in config: if classification in ["unclassified", "pending", "false_positive", "intentional", "bug"]: From 544e7c899acd22a6b4006c6e4819b63b32f49d89 Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Thu, 26 Sep 2024 10:49:02 +0200 Subject: [PATCH 26/31] Use short name yaml for file names *.yml and *.yaml Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 0765fa57..96629691 100644 --- a/README.rst +++ b/README.rst @@ -227,7 +227,7 @@ a text file (`warnings_coverity.txt`) and a code quality JSON file (`coverity_co This is an example of the configuration file: -.. code-block:: yml +.. code-block:: yaml sphinx: enabled: false From f1d84bd8052b61cf91dea2ce4080d1105af24ee6 Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 26 Sep 2024 16:56:10 +0200 Subject: [PATCH 27/31] Use CoverityClassificationChecker.SEVERITY_MAP to get rid of duplicated classifications --- src/mlx/warnings/regex_checker.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 950ce006..584274ab 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -186,15 +186,15 @@ def parse_config(self, config): if value := config.pop("exclude", None): self.add_patterns(value, self.exclude_patterns) for classification in config: - if classification in ["unclassified", "pending", "false_positive", "intentional", "bug"]: - classification_lower = classification.lower().replace("_", " ") - checker = CoverityClassificationChecker(classification=classification_lower, verbose=self.verbose) + classification_key = classification.lower().replace("_", " ") + if classification_key in CoverityClassificationChecker.SEVERITY_MAP: + checker = CoverityClassificationChecker(classification=classification_key, verbose=self.verbose) if maximum := config[classification].get("max", 0): checker.maximum = int(maximum) if minimum := config[classification].get("min", 0): checker.minimum = int(minimum) checker.cq_findings = self.cq_findings # share object with sub-checkers - self.checkers[classification_lower] = checker + self.checkers[classification_key] = checker else: print(f"WARNING: Unrecognized classification {classification!r}") From 3f7ad21f42491bc81a4b7fc3a25fe643d693159c Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 26 Sep 2024 17:24:22 +0200 Subject: [PATCH 28/31] Document code quality report with coverity checker enabled --- README.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.rst b/README.rst index 96629691..c746feed 100644 --- a/README.rst +++ b/README.rst @@ -254,6 +254,8 @@ This is an example of the configuration file: For each classification, a minimum and maximum can be given. +.. note:: + The warnings-plugin counts only one warning if there are multiple warnings for the same CID. Parse for JUnit Failures ------------------------ @@ -583,6 +585,13 @@ Polyspace `column title `_ in lowercase as the variable name. The default template is ``Polyspace: $check``. +Coverity + Named groups of the regular expression can also be used, next to environment variables. + The capturing groups are: `path`, `line`, `column`, `curr`, `max`, `checker` and `classification`. + `curr` and `max` represents respectively the current warning and the maximum amount of warnings with the same CID. + These are always the same if you take the note of `Parse for Coverity Defects`_ into account. + The default template is ``Coverity: $checker``. + Other The template should contain ``$description``, which is the default. From 7ad1b911b584565114c88cedc09c81add2fce7c5 Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:26:44 +0200 Subject: [PATCH 29/31] Iterate over items() instead of keys to increase readability Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- src/mlx/warnings/regex_checker.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 584274ab..a498f908 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -185,14 +185,14 @@ def parse_config(self, config): self.cq_default_path = value if value := config.pop("exclude", None): self.add_patterns(value, self.exclude_patterns) - for classification in config: + 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 := config[classification].get("max", 0): - checker.maximum = int(maximum) - if minimum := config[classification].get("min", 0): - checker.minimum = int(minimum) + if maximum := checker_config.get("max", 0): + checker.maximum = int(maximum, 0) + if minimum := checker_config.get("min", 0): + checker.minimum = int(minimum, 0) checker.cq_findings = self.cq_findings # share object with sub-checkers self.checkers[classification_key] = checker else: From e263239f5321c0b33cd205a9d9c7fd919eb637b8 Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Thu, 26 Sep 2024 18:00:26 +0200 Subject: [PATCH 30/31] Fix TypeError: int() can't convert non-string with explicit base Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- src/mlx/warnings/regex_checker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index a498f908..ecdad437 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -190,9 +190,9 @@ def parse_config(self, config): 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, 0) + checker.maximum = int(maximum) if minimum := checker_config.get("min", 0): - checker.minimum = int(minimum, 0) + checker.minimum = int(minimum) checker.cq_findings = self.cq_findings # share object with sub-checkers self.checkers[classification_key] = checker else: From f896b0e18c3b02013b3fc5a06434bc74e2dff398 Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Thu, 26 Sep 2024 20:03:48 +0200 Subject: [PATCH 31/31] Reduce info in documentation about code-quality report to make it more clear Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- README.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index c746feed..49608a05 100644 --- a/README.rst +++ b/README.rst @@ -586,10 +586,8 @@ Polyspace The default template is ``Polyspace: $check``. Coverity - Named groups of the regular expression can also be used, next to environment variables. - The capturing groups are: `path`, `line`, `column`, `curr`, `max`, `checker` and `classification`. - `curr` and `max` represents respectively the current warning and the maximum amount of warnings with the same CID. - These are always the same if you take the note of `Parse for Coverity Defects`_ into account. + Named groups of the regular expression can be used as variables. + Useful names are: `checker` and `classification`. The default template is ``Coverity: $checker``. Other