Skip to content

Commit

Permalink
Added coverage computation for TxtFaultReport & updated tests
Browse files Browse the repository at this point in the history
  • Loading branch information
NikosDelijohn committed Oct 19, 2024
1 parent 016f502 commit 0b8b83a
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 2 deletions.
80 changes: 79 additions & 1 deletion src/testcrush/zoix.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pathlib
import csv

from testcrush.utils import get_logger
from testcrush.utils import get_logger, to_snake_case
from typing import Any

log = get_logger()
Expand Down Expand Up @@ -140,10 +140,30 @@ class TxtFaultReport:
Manages the VC-Z01X text report.
"""

__slots__ = ["fault_report", "fault_list", "status_groups", "coverage"]

def __init__(self, fault_report: pathlib.Path) -> "TxtFaultReport":
with open(fault_report) as src:
self.fault_report: str = src.read()

# Lazy import to resolve avoid circular import.
from testcrush.grammars.transformers import FaultReportTransformerFactory

factory = FaultReportTransformerFactory()
for section in ["StatusGroups", "Coverage", "FaultList"]:

parser = factory(section)

try:
raw_section = self.extract(section)

except ValueError: # section doesn't exist
setattr(self, to_snake_case(section), None)
continue

log.debug(f"Parsing {section}")
setattr(self, to_snake_case(section), parser.parse(raw_section))

def extract(self, section: str) -> str:
"""
Extracts a section of the fault report.
Expand Down Expand Up @@ -195,6 +215,58 @@ def extract(self, section: str) -> str:

return '\n'.join(extracted_lines)

def compute_coverage(self, requested_formula: str = None, precision: int = 4) -> dict[str, float] | float:
"""
"""
retval = list()

fault_statusses = dict()

for fault in self.fault_list:

status = fault.fault_status

if status in fault_statusses:
fault_statusses[status] += 1
else:
fault_statusses[status] = 1

status_groups = dict()
if self.status_groups:

for group, statuses in self.status_groups.items():

status_groups[group] = 0

for status in statuses:

if status in fault_statusses:

status_groups[group] += fault_statusses[status]

# We expect that if a coverage formula is specified
# i.e., Coverage{} exists, then there may be some
# variables there which may not exist in statuses
# or groups. Hence we must set them to 0.
non_present = dict()
if self.coverage:

for formula_name, formula in self.coverage.items():

for status_or_group in re.findall(r"[A-Z]{2}", formula):

if status_or_group not in fault_statusses and status_or_group not in status_groups:

non_present[status_or_group] = 0

retval.append((formula_name,
round(eval(formula, {**fault_statusses, **status_groups, **non_present}),
precision)))

# Else: TODO: Implement default coverage computation according to manual
return dict(retval)[requested_formula] if requested_formula else dict(retval)


class CSVFaultReport:
"""
Expand Down Expand Up @@ -680,3 +752,9 @@ def fault_simulate(self, *instructions: str, **kwargs) -> FaultSimulation:
break

return fault_simulation_status


if __name__ == "__main__":

a = TxtFaultReport(pathlib.Path("../../sandbox/fsim_attr"))
print(a.compute_coverage())
65 changes: 64 additions & 1 deletion src/unit_tests/test_zoix.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,32 @@ class TxtFaultReportTest(unittest.TestCase):
# INSTR 4d818193 8
"""
def create_object(self):

real_open = open # Keep an alias to real open

with mock.patch("builtins.open", mock.mock_open(read_data=self._fault_report_excerp)) as mocked_open:

# Careful! the constructor invokes lark parsing which means it opens >1 files
# However, what we want to patch is just the fault report excerpt and not the
# rest, e.g., the grammars. Hence, we need to hack our way around this issue
# and only patch the open when the fault report is read. Hence this elaborate
# side effect trick. Note that i am also capturing real_open above, since at
# this point, it is already patched.

def open_side_effect(file, mode="r", *args, **kwargs):

if isinstance(file, pathlib.Path):
file = str(file)

if file == "mock_fault_report":
return mock.mock_open(read_data=self._fault_report_excerp)()
else:
# Use the real open function for grammar parsing
return real_open(file, mode, *args, **kwargs)

# Set the side_effect to handle multiple file paths
mocked_open.side_effect = open_side_effect

test_obj = zoix.TxtFaultReport(pathlib.Path("mock_fault_report"))

return test_obj
Expand All @@ -192,6 +217,36 @@ def test_constructor(self):

test_obj = self.create_object()
self.assertEqual(test_obj.fault_report, self._fault_report_excerp)
expected = [zoix.Fault(fault_status='ON', fault_type='1', fault_sites=['tb_top.wrapper_i.top_i.core_i.ex_stage_i.mult_i.U10.A1'], fault_attributes={'INSTR': '3cb3079a', 'INSTR_ADDR': '000009bc', 'PC_ID': '000009b2', 'PC_IF': '000009b6', 'sim_time': '2815ns'}),
zoix.Fault(fault_status='ON', fault_type='1', fault_sites=['tb_top.wrapper_i.top_i.core_i.ex_stage_i.mult_i.U333.Z']),
zoix.Fault(fault_status='ON', fault_type='1', fault_sites=['tb_top.wrapper_i.top_i.core_i.ex_stage_i.mult_i.U10.A2'], fault_attributes={'INSTR': '3cb3079a', 'INSTR_ADDR': '000009fc', 'PC_ID': '000009f2', 'PC_IF': '000009f6', 'sim_time': '6425ns'}),
zoix.Fault(fault_status='ON', fault_type='0', fault_sites=['tb_top.wrapper_i.top_i.core_i.ex_stage_i.mult_i.U10.ZN'], fault_attributes={'INSTR': '3cb3079a', 'INSTR_ADDR': '000009bc', 'PC_ID': '000009b2', 'PC_IF': '000009b6', 'sim_time': '2815ns'}),
zoix.Fault(fault_status='ON', fault_type='1', fault_sites=['tb_top.wrapper_i.top_i.core_i.ex_stage_i.mult_i.U10.ZN'], fault_attributes={'INSTR': '3cb3079a', 'INSTR_ADDR': '000009fc', 'PC_ID': '000009f2', 'PC_IF': '000009f6', 'sim_time': '18745ns'}),
zoix.Fault(fault_status='ON', fault_type='0', fault_sites=['tb_top.wrapper_i.top_i.core_i.ex_stage_i.mult_i.U10.A1']),
zoix.Fault(fault_status='ON', fault_type='0', fault_sites=['tb_top.wrapper_i.top_i.core_i.ex_stage_i.mult_i.U10.A2']),
zoix.Fault(fault_status='ON', fault_type='0', fault_sites=['tb_top.wrapper_i.top_i.core_i.ex_stage_i.mult_i.U333.Z']),
zoix.Fault(fault_status='ON', fault_type='1', fault_sites=['tb_top.wrapper_i.top_i.core_i.ex_stage_i.mult_i.U100.A1'], fault_attributes={'INSTR': '3cb3079a', 'INSTR_ADDR': '000009bc', 'PC_ID': '000009b2', 'PC_IF': '000009b6', 'sim_time': '7455ns'}),
zoix.Fault(fault_status='ON', fault_type='1', fault_sites=['tb_top.wrapper_i.top_i.core_i.ex_stage_i.mult_i.U100.A2'], fault_attributes={'INSTR': '3cb3079a', 'INSTR_ADDR': '000009bc', 'PC_ID': '000009b2', 'PC_IF': '000009b6', 'sim_time': '2815ns'}),
zoix.Fault(fault_status='ON', fault_type='0', fault_sites=['tb_top.wrapper_i.top_i.core_i.ex_stage_i.mult_i.U681.A'])]

expected[1].equivalent_to = expected[0]
expected[0].equivalent_faults = 2
expected[5].equivalent_to = expected[4]
expected[6].equivalent_to = expected[4]
expected[7].equivalent_to = expected[4]
expected[4].equivalent_faults = 4
expected[10].equivalent_to = expected[9]
expected[9].equivalent_faults = 2
self.assertEqual(test_obj.fault_list, expected)

self.assertEqual(test_obj.status_groups, {'SA': ['UT', 'UB', 'UR', 'UU'],
'SU': ['NN', 'NC', 'NO', 'NT'],
'DA': ['HA', 'HM', 'HT', 'OA', 'OZ', 'IA', 'IP', 'IF', 'IX'],
'DN': ['PN', 'ON', 'PP', 'OP', 'NP', 'AN', 'AP'],
'DD': ['PD', 'OD', 'ND', 'AD']})

self.assertEqual(test_obj.coverage, {"Diagnostic Coverage": "DD/(NA + DA + DN + DD)",
"Observational Coverage": "(DD + DN)/(NA + DA + DN + DD + SU)"})

def test_extract(self):

Expand Down Expand Up @@ -311,6 +366,14 @@ def test_extract(self):
-- 0 {PORT "tb_top.wrapper_i.top_i.core_i.ex_stage_i.mult_i.U681.A"}
}""")

def test_compute_coverage(self):
test_obj = self.create_object()

coverage = test_obj.compute_coverage()

self.assertEqual(coverage, {'Diagnostic Coverage': 0.0, 'Observational Coverage': 1.0})



class CSVFaultReportTest(unittest.TestCase):

Expand Down Expand Up @@ -409,7 +472,6 @@ def test_parse_fault_report(self):
3,"test1","yes","ON","1","","","","PORT","path_to_fault_3.portC"''')):

report = test_obj.parse_fault_report()
#print(report)
expected_report = [
zoix.Fault(**{"FID":"1", "Test Name":"test1", "Prime":"yes", "Status":"ON", "Model":"0", "Timing":"", "Cycle Injection":"", "Cycle End":"", "Class":"PORT", "Location":"path_to_fault_1.portA"}),
zoix.Fault(**{"FID":"2", "Test Name":"test1", "Prime":"1", "Status":"ON", "Model":"0", "Timing":"", "Cycle Injection":"", "Cycle End":"", "Class":"PORT", "Location":"path_to_fault_2.portB"}),
Expand All @@ -418,6 +480,7 @@ def test_parse_fault_report(self):

self.assertEqual(report, expected_report)


class ZoixInvokerTest(unittest.TestCase):

def test_execute(self):
Expand Down

0 comments on commit 0b8b83a

Please sign in to comment.