diff --git a/src/zoix.py b/src/zoix.py index 3487c8f..7a59406 100755 --- a/src/zoix.py +++ b/src/zoix.py @@ -158,6 +158,73 @@ def parse_fault_report(self) -> list[Fault]: return [ Fault(**dict(zip(attributes, csv_row))) for csv_row in reader ] + @staticmethod + def compute_flist_coverage(fault_list : list[Fault], sff_file: pathlib.Path, formula: str, precision : int = 2, status_attribute : str = "Status") -> float: + """Computes the test coverage percentage as described by `formula`, which must be comprised + of mathematical operations of Z01X fault classes (i.e., 2 letter strings). + + - Parameters: + - fault_list (list(Fault)): A fault-list generated after parssing Z01X fault report csv. + - sff_file (pathlib.Path): The fault format configuration file. + - formula (str): A formula which computes the coverage e.g., `"DD/(NA + DA + DN + DD)"`. + - precision (int): the number of decimals to consider for the coverage. Default is `2`. + - status_attribute (str): The attribute of the `Fault` object which represents its Z01X fault status/class. + By default this attribute is set to `"Status"`. + - Returns: + float: The coverage percentage i.e., the evaluated `formula`.""" + + # Gather fault statuses numbers. + fault_statuses = dict() + + for fault in fault_list: + + status = fault.get(status_attribute) + if status in fault_statuses: + fault_statuses[status] += 1 + else: + fault_statuses[status] = 1 + + + # Parse StatusGroups section. + with open(sff_file) as config: + + try: + status_groups_raw = re.search( + r"StatusGroups\n\s*{([^}]+)\s*}", + config.read(), + re.MULTILINE).group(1).splitlines() + + except AttributeError: + log.critical(f"Unable to extract StatusGroups segment from {sff_file}. Exiting...") + exit(1) + + # Remove empty lines if any. + status_groups_raw = list(filter(lambda x : len(x), map(str.strip, status_groups_raw))) + status_groups = dict() + + for line in status_groups_raw: + + group, *statuses = re.findall(r"([A-Z]{2})", line) + + status_groups[group] = 0 + + for status in statuses: + if status in fault_statuses: + status_groups[group] += fault_statuses[status] + + + # Finally get any group only present + # in the formula and set it to 0. + non_present_statuses = dict() + + for status in re.findall(r"[A-Z]{2}", formula): + + if status not in fault_statuses.keys() and \ + status not in status_groups.keys(): + non_present_statuses[status] = 0 + + return round(eval(formula, {**fault_statuses, **status_groups, **non_present_statuses}), precision) + class ZoixInvoker(): """A wrapper class to be used in handling calls to VCS-Z01X.""" def __init__(self) -> "ZoixInvoker":