Source code for fvm.toolchains.questa_pkg.parsers.parse_reachability
# Copyright 2024-2026 Universidad de Sevilla
# SPDX-License-Identifier: Apache-2.0
"""Parsers for reachability reports."""
import re
[docs]
def parse_single_table(html):
"""Parses a single coverage table from HTML and returns structured data."""
row_pattern = re.compile(r"<tr.*?>(.*?)</tr>", re.DOTALL)
cell_pattern = re.compile(r"<t[dh].*?>(.*?)</t[dh]>", re.DOTALL)
rows = row_pattern.findall(html)
if not rows:
return {"title": "Formal Coverage Summary", "data": []}
headers = [re.sub(r"<.*?>", "", cell).strip() for cell in cell_pattern.findall(rows[0])]
data = []
for row in rows[1:]:
cells = [re.sub(r"<.*?>", "", cell).strip() for cell in cell_pattern.findall(row)]
data.append({headers[i]: cells[i] for i in range(len(cells))})
return {"title": "Formal Coverage Summary", "data": data}
[docs]
def add_total_row(table):
"""Adds a total row to the table, summing numerical fields and computing percentages."""
if not table.get('data'):
return table
total_row = {key: 0 for key in table['data'][0].keys()
if key not in ['Coverage Type', 'Unreachable']}
total_covered = 0
total_possible = 0
for row in table['data']:
for key in total_row:
try:
total_row[key] += int(row[key])
except ValueError:
pass
# Extract Unreachable values
match = re.search(r'(\d+)', row['Unreachable'])
if match:
unreachable_value = int(match.group())
total_covered += unreachable_value
# Compute total possible cases (Active)
active_value = int(row.get('Active', 0))
total_possible += active_value
# Calculate total Unreachable percentage
total_percentage = (total_covered / total_possible * 100) if total_possible > 0 else 0
total_row['Coverage Type'] = 'Total'
total_row['Unreachable'] = f"{total_covered} ({total_percentage:.1f}%)"
table['data'].append(total_row)
return table
[docs]
def unified_format_table(table, goal=90.0):
"""Converts the table into a unified format."""
cleaned = []
for row in table['data']:
new_row = {}
for k, v in row.items():
if isinstance(v, str) and '(' in v and ')' in v:
match = re.search(r'\(\s*(.*?)\s*\)', v)
if match:
new_row[k] = v.split('(')[0].strip()
new_row['Percentage'] = match.group(1)
continue
new_row[k] = v
if 'Percentage' not in new_row:
new_row['Percentage'] = 'N/A'
cleaned.append(new_row)
new_cleaned = []
for row in cleaned:
new_row = {}
new_row['Coverage Type'] = row['Coverage Type']
new_row['Total'] = int(row['Active'])
new_row['Unreachable'] = int(row['Unreachable'])
new_row['Inconclusive'] = int(row['Inconclusive'])
new_row['Reachable'] = int(row['Witness'])
if new_row['Total'] > 0:
new_row['Percentage'] = f"{new_row['Reachable'] / new_row['Total'] * 100:.1f}%"
else:
new_row['Percentage'] = "N/A"
new_cleaned.append(new_row)
for row in new_cleaned:
perc_str = row['Percentage']
if perc_str == "N/A":
row['Status'] = "omit"
else:
perc_value = float(perc_str.strip('%'))
row['Status'] = "pass" if perc_value >= goal else "fail"
row['Goal'] = f"{goal:.1f}%"
final_data = []
for row in new_cleaned:
new_row = {
"Status": row["Status"],
"Coverage Type": row["Coverage Type"],
"Total": row["Total"],
"Unreachable": row["Unreachable"],
"Inconclusive": row["Inconclusive"],
"Reachable": row["Reachable"],
"Percentage": row["Percentage"],
"Goal": row["Goal"]
}
final_data.append(new_row)
return final_data