From 104332bc954da7301f0e82f48f9410375e90f667 Mon Sep 17 00:00:00 2001 From: Nick Deligiannis Date: Tue, 3 Sep 2024 16:40:09 +0300 Subject: [PATCH] Updated FaultReport class to handle the csv summary/report & updated unittests --- src/unit_tests/test_zoix.py | 62 ++++++++++++++++++++++++------- src/zoix.py | 74 +++++++++++++++++++++++++++++++------ 2 files changed, 112 insertions(+), 24 deletions(-) mode change 100644 => 100755 src/zoix.py diff --git a/src/unit_tests/test_zoix.py b/src/unit_tests/test_zoix.py index 8c7456e..716e59b 100644 --- a/src/unit_tests/test_zoix.py +++ b/src/unit_tests/test_zoix.py @@ -9,16 +9,33 @@ import subprocess import re -class FaultReportTest(unittest.TestCase): +class CSVFaultReportTest(unittest.TestCase): def test_constructor(self): - test_obj = zoix.FaultReport(pathlib.Path("mock_report")) - self.assertEqual(test_obj.fault_report, pathlib.Path("mock_report").absolute()) + test_obj = zoix.CSVFaultReport(pathlib.Path("mock_fault_summary"), pathlib.Path("mock_fault_report")) + self.assertEqual(test_obj.fault_summary, pathlib.Path("mock_fault_summary").absolute()) + self.assertEqual(test_obj.fault_report, pathlib.Path("mock_fault_report").absolute()) + + def test_set_fault_summary(self): + + test_obj = zoix.CSVFaultReport(pathlib.Path("mock_fault_summary"), pathlib.Path("mock_fault_report")) + + with self.assertRaises(FileExistsError) as cm: + + test_obj.set_fault_summary("new_mock_summary") + + self.assertEqual(str(cm.exception), f"Fault summary new_mock_summary does not exist!") + + with mock.patch("pathlib.Path.exists", return_value = True): + + test_obj.set_fault_summary("new_mock_summary") + + self.assertEqual(test_obj.fault_summary, pathlib.Path("new_mock_summary").absolute()) def test_set_fault_report(self): - test_obj = zoix.FaultReport(pathlib.Path("mock_report")) + test_obj = zoix.CSVFaultReport(pathlib.Path("mock_fault_summary"), pathlib.Path("mock_fault_report")) with self.assertRaises(FileExistsError) as cm: @@ -32,9 +49,9 @@ def test_set_fault_report(self): self.assertEqual(test_obj.fault_report, pathlib.Path("new_mock_report").absolute()) - def test_extract_fault_report_cell(self): + def test_extract_summary_cells_from_row(self): - test_obj = zoix.FaultReport(pathlib.Path("mock_report")) + test_obj = zoix.CSVFaultReport(pathlib.Path("mock_fault_summary"), pathlib.Path("mock_fault_report")) with mock.patch("builtins.open", mock.mock_open(read_data="""\ "Category","Name","Label","Prime Cnt","Prime Pct","Prime Sub Pct","Total Cnt","Total Pct","Total Sub Pct" @@ -54,9 +71,9 @@ def test_extract_fault_report_cell(self): "Coverage","Diagnostic Coverage","","","0.00%","","","0.00%","" "Coverage","Observational Coverage","","","67.58%","","","68.00%",""""")): - diagnostic_coverage = test_obj.extract_fault_report_cells(15,8) - observationL_coverage = test_obj.extract_fault_report_cells(16,8) - total_faults = test_obj.extract_fault_report_cells(2,2,4,7) + diagnostic_coverage = test_obj.extract_summary_cells_from_row(15,8) + observationL_coverage = test_obj.extract_summary_cells_from_row(16,8) + total_faults = test_obj.extract_summary_cells_from_row(2,2,4,7) self.assertEqual(diagnostic_coverage, ["0.00%"]) self.assertEqual(observationL_coverage, ["68.00%"]) @@ -65,16 +82,35 @@ def test_extract_fault_report_cell(self): # Row is out of bounds with self.assertRaises(IndexError) as cm: - test_obj.extract_fault_report_cells(20,1) + test_obj.extract_summary_cells_from_row(20,1) - self.assertEqual(str(cm.exception), f"Row 20 is out of bounds for fault report {test_obj.fault_report}.") + self.assertEqual(str(cm.exception), f"Row 20 is out of bounds for fault summary {test_obj.fault_summary}.") # Column is out of bounds with self.assertRaises(IndexError) as cm: - test_obj.extract_fault_report_cells(10,1,20) + test_obj.extract_summary_cells_from_row(10,1,20) + + self.assertEqual(str(cm.exception), f"A column in (1, 20) is out of bounds for row 10 of fault summary {test_obj.fault_summary}.") + + def test_parse_fault_report(self): + + test_obj = zoix.CSVFaultReport(pathlib.Path("mock_fault_summary"), pathlib.Path("mock_fault_report")) + + with mock.patch("builtins.open", mock.mock_open(read_data='''\ +"FID","Test Name","Prime","Status","Model","Timing","Cycle Injection","Cycle End","Class","Location" +1,"test1","yes","ON","0","","","","PORT","path_to_fault_1.portA" +2,"test1",1,"ON","0","","","","PORT","path_to_fault_2.portB" +3,"test1","yes","ON","1","","","","PORT","path_to_fault_3.portC"''')): + + report = test_obj.parse_fault_report() + expected_report = [ + zoix.SffFault("1", "test1", "yes", "ON", "0", "", "", "", "PORT", "path_to_fault_1.portA"), + zoix.SffFault("2", "test1", "1", "ON", "0", "", "", "", "PORT", "path_to_fault_2.portB"), + zoix.SffFault("3", "test1", "yes", "ON", "1", "", "", "", "PORT", "path_to_fault_3.portC"), + ] - self.assertEqual(str(cm.exception), f"A column in (1, 20) is out of bounds for row 10 of fault report {test_obj.fault_report}.") + self.assertEqual(report, expected_report) class ZoixInvokerTest(unittest.TestCase): diff --git a/src/zoix.py b/src/zoix.py old mode 100644 new mode 100755 index 52f3b14..2ec2e9f --- a/src/zoix.py +++ b/src/zoix.py @@ -8,6 +8,7 @@ import enum import pathlib import csv +from dataclasses import dataclass ######## TEMPORARY ######## log = logging.getLogger("testcrush logger") @@ -267,11 +268,40 @@ def fault_simulate(self, *instructions : str, **kwargs) -> FaultSimulation: return fault_simulation_status -class FaultReport(): - - def __init__(self, fault_report : pathlib.Path) -> "FaultReport": +@dataclass +class SffFault(): + """Represents a fault which is compliant with the standard fault format of Synopsys.""" + fid : str + test_name : str + prime : str + status : str + model : str + timing : str + cycle_injection : str + cycle_end : str + fault_class : str + location : str + +class CSVFaultReport(): + + def __init__(self, fault_summary : pathlib.Path, fault_report : pathlib.Path) -> "FaultReport": + self.fault_summary : pathlib.Path = fault_summary.absolute() self.fault_report : pathlib.Path = fault_report.absolute() + def set_fault_summary(self, fault_summary : str) -> None: + """Setter method for fault summary. + + - Parameters: + - fault_summary (str): The new fault summary filename. + + - Returns: + - None. Raises FileExistsError if the file does not exist.""" + + if not pathlib.Path(fault_summary).exists(): + raise FileExistsError(f"Fault summary {fault_summary} does not exist!") + + self.fault_summary = pathlib.Path(fault_summary).absolute() + def set_fault_report(self, fault_report : str) -> None: """Setter method for fault report. @@ -286,17 +316,17 @@ def set_fault_report(self, fault_report : str) -> None: self.fault_report = pathlib.Path(fault_report).absolute() - def extract_fault_report_cells(self, row : int, *cols : int) -> list[str]: - """Returns a sequence of cells from a row of the CSV fault report. + def extract_summary_cells_from_row(self, row : int, *cols : int) -> list[str]: + """Returns a sequence of cells from a row of the `self.fault_summary` **CSV** file. - Parameters: - row (int): the row number (1-based indexing). - *cols (ints): the columns' numbers (1-based indexing). - Returns: - list[str]: The cells of the fault report.""" + - list[str]: The cells of the fault summary.""" - with open(self.fault_report) as csv_source: + with open(self.fault_summary) as csv_source: reader = csv.reader(csv_source) @@ -308,9 +338,29 @@ def extract_fault_report_cells(self, row : int, *cols : int) -> list[str]: try: return [csv_row[col - 1] for col in cols] except: - raise IndexError(f"A column in {cols} is out of bounds for row {row} of fault report {self.fault_report}.") + raise IndexError(f"A column in {cols} is out of bounds for row {row} of fault summary {self.fault_summary}.") + + raise IndexError(f"Row {row} is out of bounds for fault summary {self.fault_summary}.") + + def parse_fault_report(self) -> list[SffFault]: + """Parses the `self.fault_report` **CSV** file and returns a dictionary with its + contents, ommiting any column if specified. + + - Parameters: + - None: + + - Returns: + - list[SffFault]: A list with synopys fault format objects. + """ + + with open(self.fault_report) as csv_source: + + reader = csv.reader(csv_source) + + # Skip header + next(reader) - raise IndexError(f"Row {row} is out of bounds for fault report {self.fault_report}.") + return [ SffFault(*csv_row) for csv_row in reader ] def main(): @@ -341,9 +391,11 @@ def main(): print(tat_dict['tat_value']) print(res) - B = FaultReport("summary.csv.log") - print(B.extract_csv_cell(16,8)) + B = CSVFaultReport(pathlib.Path("summary.csv.log"), pathlib.Path("sample.csv")) + print(B.extract_summary_cells_from_row(16,8)) + report = B.parse_fault_report() + print(report) if __name__ == "__main__": main()