Source code for mlx.warnings.polyspace_checker
# SPDX-License-Identifier: Apache-2.0
import csv
import os
from io import TextIOWrapper
from string import Template
from .code_quality import Finding
from .exceptions import WarningsConfigError
from .warnings_checker import WarningsChecker
[docs]
class PolyspaceChecker(WarningsChecker):
name = "polyspace"
checkers = []
def __init__(self, *logging_args):
'''Constructor to set the default code quality description template to "Polyspace: $check"'''
super().__init__(*logging_args)
self._cq_description_template = Template("Polyspace: $check")
@property
def cq_findings(self):
"""List[dict]: list of code quality findings"""
for checker in self.checkers:
self._cq_findings.extend(checker.cq_findings)
return self._cq_findings
@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
@property
def minimum(self):
"""Gets the lowest minimum amount of warnings
Returns:
int: the lowest minimum for warnings
"""
if self.checkers:
return min(x.minimum for x in self.checkers)
return 0
@minimum.setter
def minimum(self, minimum):
for checker in self.checkers:
checker.minimum = minimum
@property
def maximum(self):
"""Gets the highest minimum amount of warnings
Returns:
int: the highest maximum for warnings
"""
if self.checkers:
return max(x.maximum for x in self.checkers)
return 0
@maximum.setter
def maximum(self, maximum):
for checker in self.checkers:
checker.maximum = maximum
[docs]
def check(self, content):
"""
Function for counting the number of failures in a TSV file exported by Polyspace
Args:
content (_io.TextIOWrapper): The open file to parse
"""
if not isinstance(content, TextIOWrapper):
raise TypeError(
f"{self.__class__.__name__} can't handle this type; expected {type(TextIOWrapper)}; got {type(content)}"
)
reader = csv.DictReader(content, dialect="excel-tab")
# set column names to lowercase
reader.fieldnames = [name.lower() for name in reader.fieldnames]
for row in reader:
for checker in self.checkers:
if row["family"].lower() == checker.family_value:
checker.check(row)
[docs]
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:
self.count += checker.return_count()
return self.count
[docs]
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:
count += checker.return_check_limits()
if count:
self.logger.warning(f"Returning error code {count}.")
return count
[docs]
def parse_config(self, config):
"""Parsing configuration dict extracted by previously opened JSON or yaml/yml file
Args:
config (dict): Content of configuration file
Raises:
ValueError: Expected a list of dicts as value of the key which represents the value of the 'Family' column.
These dicts need to consist 3 key-value pairs (Note: if 'min' or 'max' is not defined, it will get the
default value of 0):
{\n <column-name>: <value_to_check>,\n min: <number>,\n max: <number>\n}
"""
self.checkers = []
for family_value, data in config.items():
if family_value == "enabled":
continue
if family_value == "cq_description_template":
self.cq_description_template = Template(config["cq_description_template"])
continue
if family_value == "cq_default_path":
self.cq_default_path = config["cq_default_path"]
continue
if family_value == "exclude":
self.add_patterns(config.get("exclude"), self.exclude_patterns)
continue
for check in data:
for key, value in check.items():
if key in ["min", "max"]:
continue
column_name = key.lower()
check_value = value.lower()
checker = PolyspaceFamilyChecker(family_value, column_name, check_value, *self.logging_args)
checker.parse_config(check)
self.checkers.append(checker)
if not (column_name and check_value):
raise ValueError(
"Expected a list of dicts as value of the key which represents the value of the "
"'Family' column. These dicts need to consist 3 key-value pairs (Note: if 'min' or "
"'max' is not defined, it will get the default value of 0):\n"
"{\n <column_name>: <value_to_check>,\n min: <number>,\n max: <number>\n};"
f"got {column_name!r} as column_name and {check_value!r} as value_to_check"
)
for checker in self.checkers:
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
[docs]
class PolyspaceFamilyChecker(WarningsChecker):
name = "polyspace_sub"
code_quality_severity = {
"impact: high": "critical",
"impact: medium": "major",
"impact: low": "minor",
"red": "critical",
"orange": "major",
}
logging_fmt = "{checker.name_repr}: {checker.family_value:15s} : {checker.column_name:11s} : {checker.check_value:14s} | {message}"
def __init__(self, family_value, column_name, check_value, *logging_args):
"""Initialize the PolyspaceFamilyChecker
Args:
family_value (str): The value to search for in the 'Family' column
column_name (str): The name of the column
check_value (str): The value to check in the column
"""
super().__init__(*logging_args)
self.family_value = family_value
self.column_name = column_name
self.check_value = check_value
@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
[docs]
def add_code_quality_finding(self, row):
"""Add code quality finding
Args:
row (dict): The row of the warning with the corresponding colomn names
"""
try:
description = self.cq_description_template.substitute(os.environ, **row)
except KeyError as err:
raise WarningsConfigError(f"Failed to find environment variable from configuration value "
f"'cq_description_template': {err}") from err
finding = Finding(description)
# Attention to bug finder: items have color red for impact: high, medium and low.
if (severity := self.code_quality_severity.get(row["information"].lower())) is not None:
finding.severity = severity
elif (severity := self.code_quality_severity.get(row["color"].lower())) is not None:
finding.severity = severity
finding.path = row.get("file", self.cq_default_path)
finding.line = row.get("line", 1)
finding.column = row.get("col", 1)
self.cq_findings.append(finding.to_dict())
[docs]
def check(self, content):
"""
Function for counting the number of failures in a TSV/CSV file exported by Polyspace
Args:
content (dict): The row of the TSV file
"""
if content[self.column_name].lower() == self.check_value:
if content["status"].lower() in ["not a defect", "justified"]:
self.logger.info(f"Excluded defect with ID {content.get('id', None)!r} because the status is "
"'Not a defect' or 'Justified'")
else:
valid_content_values = [item or "" for item in content.values()]
tab_sep_string = "\t".join(valid_content_values)
if not self._is_excluded(tab_sep_string):
self.count = self.count + 1
verbose_log_msg = f"ID {content.get('id', None)!r}"
self.logger.info(verbose_log_msg)
self.logger.debug(verbose_log_msg)
if self.cq_enabled and content["color"].lower() != "green":
self.add_code_quality_finding(content)