Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/dvsim/sim/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,9 @@ def _create_objects(self) -> None:
self.regressions.extend(self.testplan.get_stage_regressions())
else:
# Create a dummy testplan with no entries.
self.testplan = Testplan(None, name=self.name)
self.testplan = Testplan(
"<dummy testplan>", repo_top=Path(self.proj_root), name=self.name
)

# Create regressions
self.regressions = Regression.create_regressions(self.regressions, self, self.tests)
Expand Down
74 changes: 32 additions & 42 deletions src/dvsim/testplan.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@

"""Testpoint and Testplan classes for maintaining the testplan."""

import os
import re
import sys
from collections import defaultdict
from pathlib import Path
from typing import TextIO
from typing import Any, TextIO

import hjson
from tabulate import tabulate
Expand Down Expand Up @@ -65,7 +64,7 @@ class Element:
def __init__(self, raw_dict) -> None:
"""Initialize the testplan element.

raw_dict is the dictionary parsed from the HJSon file.
raw_dict is the dictionary parsed from the Hjson file.
"""
# 'tags' is an optional field in addition to the mandatory self.fields.
self.tags = []
Expand Down Expand Up @@ -260,22 +259,16 @@ class Testplan:
element_cls = {"testpoint": Testpoint, "covergroup": Covergroup}

@staticmethod
def _parse_hjson(filename):
"""Parses an input file with HJson and returns a dict."""
try:
return hjson.load(Path(filename).open())
except OSError:
pass
except hjson.scanner.HjsonDecodeError:
pass
sys.exit(1)
def _parse_hjson(filename: Path) -> dict[str, Any]:
"""Parse an Hjson file at the given path and return it as a dict."""
return hjson.loads(Path(filename).read_text(encoding="utf-8"))

@staticmethod
def _create_testplan_elements(kind: str, raw_dicts_list: list, tags: set) -> list[Element]:
"""Create testplan elements from the list of raw dicts.

kind is either 'testpoint' or 'covergroup'.
raw_dicts_list is a list of dictionaries extracted from the HJson file.
raw_dicts_list is a list of dictionaries extracted from the Hjson file.
"""
items = []
item_names = set()
Expand Down Expand Up @@ -311,36 +304,37 @@ def _get_percentage(value, total) -> str:
perc = value / total * 100 * 1.0
return f"{round(perc, 2):.2f} %"

def __init__(self, filename, repo_top=None, name=None) -> None:
def __init__(self, tagged_filename: str, repo_top: Path, name: str) -> None:
"""Initialize the testplan.

filename is the HJson file that captures the testplan. It may be
suffixed with tags separated with a colon delimiter to filter the
testpoints. For example: path/too/foo_testplan.hjson:bar:baz
repo_top is an optional argument indicating the path to top level repo
/ project directory. It is used with filename arg.
name is an optional argument indicating the name of the testplan / DUT.
It overrides the name set in the testplan HJson.
Args:
tagged_filename: Describes the Hjson file that captures the testplan.
This is a string, rather than a Path object, because
it may be suffixed with tags separated with a colon
delimiter to filter the testpoints.

For example: "path/to/foo_testplan.hjson:bar:baz"

repo_top: The path to the top level repo / project directory.
This is combined with the filename argument.

name: The name of the testplan / DUT. It overrides any
name set in the testplan Hjson.

"""
self.name = None
self.name = name
self.testpoints = []
self.covergroups = []
self.test_results_mapped = False

# Split the filename into filename and tags, if provided.
split = str(filename).split(":")
split = tagged_filename.split(":")
filename = Path(split[0])
tags = set(split[1:])

if filename.exists():
self._parse_testplan(filename, tags, repo_top)

if name:
self.name = name

if not self.name:
sys.exit(1)

# Represents current progress towards each stage. Stage = N.A.
# is used to indicate the unmapped tests.
self.progress = {}
Expand Down Expand Up @@ -402,19 +396,15 @@ def _get_imported_testplan_paths(

return result

def _parse_testplan(self, filename: Path, tags: set, repo_top=None) -> None:
def _parse_testplan(self, filename: Path, tags: set[str], repo_top: Path) -> None:
"""Parse testplan Hjson file and create the testplan elements.

It creates the list of testpoints and covergroups extracted from the
file.

filename is the path to the testplan file written in HJson format.
filename is the path to the testplan file written in Hjson format.
repo_top is an optional argument indicating the path to repo top.
"""
if repo_top is None:
# Assume dvsim's original location: $REPO_TOP/util/dvsim.
repo_top = Path(__file__).parent.parent.parent.resolve()

obj = Testplan._parse_hjson(filename)

parsed = set()
Expand All @@ -430,7 +420,7 @@ def _parse_testplan(self, filename: Path, tags: set, repo_top=None) -> None:
if testplan in parsed:
sys.exit(1)
parsed.add(testplan)
data = self._parse_hjson(os.path.join(repo_top, testplan))
data = self._parse_hjson(repo_top / testplan)
imported_testplans.extend(
self._get_imported_testplan_paths(
testplan,
Expand All @@ -451,7 +441,7 @@ def _parse_testplan(self, filename: Path, tags: set, repo_top=None) -> None:
if not testpoints and not covergroups:
sys.exit(1)

# Any variable in the testplan that is not a recognized HJson field can
# Any variable in the testplan that is not a recognized Hjson field can
# be used as a substitution variable.
substitutions = {k: v for k, v in obj.items() if k not in self.rsvd_keywords}
for tp in self.testpoints:
Expand All @@ -472,11 +462,11 @@ def get_stage_regressions(self):
if tp.stage in tp.stages[1:]:
regressions[tp.stage].update({t for t in tp.tests if t})

# Build regressions dict into a hjson like data structure
# Build regressions dict into a Hjson-like data structure
return [{"name": ms, "tests": list(regressions[ms])} for ms in regressions]

def write_testplan_doc(self, output: TextIO) -> None:
"""Write testplan documentation in markdown from the hjson testplan."""
"""Write testplan documentation in markdown from the Hjson testplan."""
stages = {}
for tp in self.testpoints:
stages.setdefault(tp.stage, []).append(tp)
Expand Down Expand Up @@ -725,16 +715,16 @@ def get_test_results_summary(self):
result["Pass Rate"] = self._get_percentage(tr.passing, tr.total)
return result

def get_sim_results(self, sim_results_file, fmt="md"):
def get_sim_results(self, sim_results_file: str, fmt="md"):
"""Returns the mapped sim result tables in HTML formatted text.

The data extracted from the sim_results table HJson file is mapped into
The data extracted from the sim_results table Hjson file is mapped into
a test results, test progress, covergroup progress and coverage tables.

fmt is either 'md' (markdown) or 'html'.
"""
assert fmt in ["md", "html"]
sim_results = Testplan._parse_hjson(sim_results_file)
sim_results = Testplan._parse_hjson(Path(sim_results_file))
test_results_ = sim_results.get("test_results", None)

test_results = []
Expand Down
Loading