| # 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 os | |
| import sys | |
| import hjson | |
| import mistletoe | |
| from tabulate import tabulate | |
| from .class_defs import Testplan, TestplanEntry | |
| 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, 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 "test_results" not 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 function 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, item2 in list2.items(): | |
| item1 = list1.get(key) | |
| if item1 is None: | |
| list1[key] = item2 | |
| continue | |
| # Both dictionaries have an entry for this key. Are they both lists? If | |
| # so, append. | |
| if isinstance(item1, list) and isinstance(item2, list): | |
| list1[key] = item1 + item2 | |
| continue | |
| # Are they both dictionaries? If so, recurse. | |
| if isinstance(item1, dict) and isinstance(item2, dict): | |
| merge_dicts(item1, item2) | |
| continue | |
| # We treat other types as atoms. If the types of the two items are | |
| # equal pick one or the other (based on use_list1_for_defaults). | |
| if isinstance(item1, type(item2)) and isinstance(item2, type(item1)): | |
| list1[key] = item1 if use_list1_for_defaults else item2 | |
| continue | |
| # Oh no! We can't merge this. | |
| print("ERROR: Cannot merge dictionaries at key {!r} because items have " | |
| "conflicting types ({} in 1st; {} in 2nd)." | |
| .format(type(item1), type(item2))) | |
| sys.exit(1) | |
| 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') |