| #!/usr/bin/env python3 |
| # Copyright lowRISC contributors. |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| # SPDX-License-Identifier: Apache-2.0 |
| r"""Command-line tool to parse and process testplan Hjson into a data structure |
| The data structure is used for expansion inline within DV plan documentation |
| as well as for annotating the regression results. |
| """ |
| import logging as log |
| import os |
| import sys |
| from pathlib import PurePath |
| |
| import hjson |
| import mistletoe |
| from tabulate import tabulate |
| |
| from .class_defs import * |
| |
| |
| def parse_testplan(filename): |
| '''Parse testplan Hjson file into a datastructure''' |
| self_path = os.path.dirname(os.path.realpath(__file__)) |
| repo_root = os.path.abspath(os.path.join(self_path, os.pardir, os.pardir)) |
| |
| name = "" |
| imported_testplans = [] |
| substitutions = [] |
| obj = parse_hjson(filename) |
| for key in obj.keys(): |
| if key == "import_testplans": |
| imported_testplans = obj[key] |
| elif key != "entries": |
| if key == "name": name = obj[key] |
| substitutions.append({key: obj[key]}) |
| for imported_testplan in imported_testplans: |
| obj = merge_dicts( |
| obj, parse_hjson(os.path.join(repo_root, imported_testplan))) |
| |
| testplan = Testplan(name=name) |
| for entry in obj["entries"]: |
| if not TestplanEntry.is_valid_entry(entry): sys.exit(1) |
| testplan_entry = TestplanEntry(name=entry["name"], |
| desc=entry["desc"], |
| milestone=entry["milestone"], |
| tests=entry["tests"], |
| substitutions=substitutions) |
| testplan.add_entry(testplan_entry) |
| testplan.sort() |
| return testplan |
| |
| |
| def gen_html_indent(lvl): |
| return " " * lvl |
| |
| |
| def gen_html_write_style(outbuf): |
| outbuf.write("<style>\n") |
| outbuf.write("table.dv {\n") |
| outbuf.write(" border: 1px solid black;\n") |
| outbuf.write(" border-collapse: collapse;\n") |
| outbuf.write(" text-align: left;\n") |
| outbuf.write(" vertical-align: middle;\n") |
| outbuf.write(" display: table;\n") |
| outbuf.write("}\n") |
| outbuf.write("th, td {\n") |
| outbuf.write(" border: 1px solid black;\n") |
| outbuf.write("}\n") |
| outbuf.write("</style>\n") |
| |
| |
| def gen_html_testplan_table(testplan, outbuf): |
| '''generate HTML table from testplan with the following fields |
| milestone, planned test name, description |
| ''' |
| |
| text = testplan.testplan_table(fmt="html") |
| text = text.replace("<table>", "<table class=\"dv\">") |
| gen_html_write_style(outbuf) |
| outbuf.write(text) |
| return |
| |
| |
| def gen_html_regr_results_table(testplan, regr_results, outbuf): |
| '''map regr results to testplan and create a table with the following fields |
| milestone, planned test name, actual written tests, pass/total |
| ''' |
| text = "# Regression Results\n" |
| text += "## Run on{}\n".format(regr_results["timestamp"]) |
| text += "### Test Results\n\n" |
| text += testplan.results_table(regr_results["test_results"]) |
| if "cov_results" in regr_results.keys(): |
| text += "\n### Coverage Results\n\n" |
| cov_header = [] |
| cov_values = [] |
| for cov in regr_results["cov_results"]: |
| cov_header.append(cov["name"]) |
| cov_values.append(str(cov["result"])) |
| colalign = (("center", ) * len(cov_header)) |
| text += tabulate([cov_header, cov_values], |
| headers="firstrow", |
| tablefmt="pipe", |
| colalign=colalign) |
| text = mistletoe.markdown(text) |
| text = text.replace("<table>", "<table class=\"dv\">") |
| gen_html_write_style(outbuf) |
| outbuf.write(text) |
| return |
| |
| |
| def parse_regr_results(filename): |
| obj = parse_hjson(filename) |
| # TODO need additional syntax checks |
| if not "test_results" in obj.keys(): |
| print("Error: key \'test_results\' not found") |
| sys, exit(1) |
| return obj |
| |
| |
| def parse_hjson(filename): |
| try: |
| f = open(str(filename), 'rU') |
| text = f.read() |
| odict = hjson.loads(text) |
| return odict |
| except IOError: |
| print('IO Error:', filename) |
| raise SystemExit(sys.exc_info()[1]) |
| except hjson.scanner.HjsonDecodeError as e: |
| print("Error: Unable to decode HJSON file %s: %s" % |
| (str(filename), str(e))) |
| sys.exit(1) |
| |
| |
| def merge_dicts(list1, list2, use_list1_for_defaults=True): |
| '''merge 2 dicts into one |
| |
| This funciton takes 2 dicts as args list1 and list2. It recursively merges list2 into |
| list1 and returns list1. The recursion happens when the the value of a key in both lists |
| is a dict. If the values of the same key in both lists (at the same tree level) are of |
| dissimilar type, then there is a conflict and an error is thrown. If they are of the same |
| scalar type, then the third arg "use_list1_for_defaults" is used to pick the final one. |
| ''' |
| for key in list2.keys(): |
| if key in list1: |
| if type(list1[key]) is list and type(list2[key]) is list: |
| list1[key].extend(list2[key]) |
| elif type(list1[key]) is dict and type(list2[key]) is dict: |
| list1[key] = merge_dicts(list1[key], list2[key]) |
| elif (type(list1[key]) == type(list2[key])): |
| if not use_list1_for_defaults: |
| list1[key] = list2[key] |
| else: |
| print("The type of value of key \"", key, "\" in list1: \"", \ |
| str(type(list1[key])), \ |
| "\" does not match the type of value in list2: \"", \ |
| str(type(list2[key])), \ |
| "\". The two lists cannot be merged.") |
| sys.exit(1) |
| else: |
| list1[key] = list2[key] |
| return list1 |
| |
| |
| def gen_html(testplan_file, regr_results_file, outbuf): |
| testplan = parse_testplan(os.path.abspath(testplan_file)) |
| if regr_results_file: |
| regr_results = parse_regr_results(os.path.abspath(regr_results_file)) |
| gen_html_regr_results_table(testplan, regr_results, outbuf) |
| else: |
| gen_html_testplan_table(testplan, outbuf) |
| outbuf.write('\n') |