blob: 8ff832684b5092e44a16fdfd8c1e47212ec7b0ac [file] [log] [blame]
Srikrishna Iyerd61724d2019-10-11 10:47:49 -07001#!/usr/bin/env python3
2# Copyright lowRISC contributors.
3# Licensed under the Apache License, Version 2.0, see LICENSE for details.
4# SPDX-License-Identifier: Apache-2.0
Philipp Wagner14a3fee2019-11-21 10:07:02 +00005r"""Command-line tool to parse and process testplan Hjson into a data structure
Srikrishna Iyerd61724d2019-10-11 10:47:49 -07006 The data structure is used for expansion inline within DV plan documentation
7 as well as for annotating the regression results.
8"""
9import logging as log
10import os
11import sys
12from pathlib import PurePath
13
14import hjson
15import mistletoe
16
17from .class_defs import *
18
19
20def parse_testplan(filename):
Philipp Wagner14a3fee2019-11-21 10:07:02 +000021 '''Parse testplan Hjson file into a datastructure'''
Srikrishna Iyerd61724d2019-10-11 10:47:49 -070022 self_path = os.path.dirname(os.path.realpath(__file__))
23 repo_root = os.path.abspath(os.path.join(self_path, os.pardir, os.pardir))
24
25 name = ""
26 imported_testplans = []
27 substitutions = []
28 obj = parse_hjson(filename)
29 for key in obj.keys():
30 if key == "import_testplans":
31 imported_testplans = obj[key]
32 elif key != "entries":
33 if key == "name": name = obj[key]
34 substitutions.append({key: obj[key]})
35 for imported_testplan in imported_testplans:
36 obj = merge_dicts(
37 obj, parse_hjson(os.path.join(repo_root, imported_testplan)))
38
39 testplan = Testplan(name=name)
40 for entry in obj["entries"]:
41 if not TestplanEntry.is_valid_entry(entry): sys.exit(1)
Weicai Yang1130f5b2019-11-01 18:36:18 -070042 testplan_entry = TestplanEntry(name=entry["name"],
43 desc=entry["desc"],
44 milestone=entry["milestone"],
45 tests=entry["tests"],
46 substitutions=substitutions)
Srikrishna Iyerd61724d2019-10-11 10:47:49 -070047 testplan.add_entry(testplan_entry)
48 testplan.sort()
49 return testplan
50
51
52def gen_html_indent(lvl):
53 return " " * lvl
54
55
56def gen_html_write_style(outbuf):
57 outbuf.write("<style>\n")
58 outbuf.write("table.dv {\n")
59 outbuf.write(" border: 1px solid black;\n")
60 outbuf.write(" border-collapse: collapse;\n")
61 outbuf.write(" width: 100%;\n")
62 outbuf.write(" text-align: center;\n")
63 outbuf.write(" vertical-align: middle;\n")
64 outbuf.write(" margin-left: auto;;\n")
65 outbuf.write(" margin-right: auto;;\n")
66 outbuf.write(" display: table;\n")
67 outbuf.write("}\n")
68 outbuf.write("th, td {\n")
69 outbuf.write(" border: 1px solid black;\n")
70 outbuf.write("}\n")
71 outbuf.write("</style>\n")
72
73
74def gen_html_testplan_table(testplan, outbuf):
Philipp Wagner14a3fee2019-11-21 10:07:02 +000075 '''generate HTML table from testplan with the following fields
Srikrishna Iyerd61724d2019-10-11 10:47:49 -070076 milestone, planned test name, description
77 '''
Srikrishna Iyerd61724d2019-10-11 10:47:49 -070078 def print_row(ms, name, desc, tests, cell, outbuf):
79 cellb = "<" + cell + ">"
80 celle = "</" + cell + ">"
81 tests_str = ""
82 if cell == "td":
83 # remove leading and trailing whitespaces
84 desc = mistletoe.markdown(desc.strip())
Weicai Yang1130f5b2019-11-01 18:36:18 -070085 tests_str = "<ul>\n"
Srikrishna Iyerd61724d2019-10-11 10:47:49 -070086 for test in tests:
Weicai Yang1130f5b2019-11-01 18:36:18 -070087 tests_str += "<li>" + str(test) + "</li>\n"
88 tests_str += "</ul>"
Srikrishna Iyerd61724d2019-10-11 10:47:49 -070089 else:
90 tests_str = tests
91
92 outbuf.write(gen_html_indent(1) + "<tr>\n")
93 outbuf.write(gen_html_indent(2) + cellb + ms + celle + "\n")
94 outbuf.write(gen_html_indent(2) + cellb + name + celle + "\n")
95
96 # make description text left aligned
97 cellb_desc = cellb
98 if cell == "td":
99 cellb_desc = cellb_desc[:-1] + " style=\"text-align: left\"" + ">"
100 outbuf.write(gen_html_indent(2) + cellb_desc + desc + celle + "\n")
101 outbuf.write(gen_html_indent(2) + cellb + tests_str + celle + "\n")
102 outbuf.write(gen_html_indent(1) + "</tr>\n")
103
104 gen_html_write_style(outbuf)
105 outbuf.write("<table class=\"dv\">\n")
106 print_row("Milestone", "Name", "Description", "Tests", "th", outbuf)
107 for entry in testplan.entries:
108 print_row(entry.milestone, entry.name, entry.desc, entry.tests, "td",
109 outbuf)
110 outbuf.write("</table>\n")
111 return
112
113
114def gen_html_regr_results_table(testplan, regr_results, outbuf):
115 '''map regr results to testplan and create a table with the following fields
116 milestone, planned test name, actual written tests, pass/total
117 '''
Srikrishna Iyerd61724d2019-10-11 10:47:49 -0700118 def print_row(ms, name, tests, results, cell, outbuf):
119 cellb = "<" + cell + ">"
120 celle = "</" + cell + ">"
121 tests_str = ""
122 results_str = ""
123 if cell == "td":
124 for test in tests:
125 if tests_str != "": tests_str += "<br>"
126 if results_str != "": results_str += "<br>"
127 tests_str += str(test["name"])
128 results_str += str(test["passing"]) + "/" + str(test["total"])
129 else:
130 tests_str = tests
131 results_str = results
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800132 if ms == "N.A.": ms = ""
133 if name == "N.A.":
Srikrishna Iyerd61724d2019-10-11 10:47:49 -0700134 name = ""
135 tests_str = "<strong>" + tests_str + "</strong>"
136 results_str = "<strong>" + results_str + "</strong>"
137
138 outbuf.write(gen_html_indent(1) + "<tr>\n")
139 outbuf.write(gen_html_indent(2) + cellb + ms + celle + "\n")
140 outbuf.write(gen_html_indent(2) + cellb + name + celle + "\n")
141 outbuf.write(gen_html_indent(2) + cellb + tests_str + celle + "\n")
142 outbuf.write(gen_html_indent(2) + cellb + results_str + celle + "\n")
143 outbuf.write(gen_html_indent(1) + "</tr>\n")
144
145 testplan.map_regr_results(regr_results["test_results"])
146
147 gen_html_write_style(outbuf)
148 outbuf.write("<h1 style=\"text-align: center\" " + "id=\"" +
149 testplan.name + "-regression-results\">" +
150 testplan.name.upper() + " Regression Results</h1>\n")
151
152 outbuf.write("<h2 style=\"text-align: center\">" + "Run on " +
153 regr_results["timestamp"] + "</h2>\n")
154
155 # test results
156 outbuf.write("<h3 style=\"text-align: center\">Test Results</h2>\n")
157 outbuf.write("<table class=\"dv\">\n")
158 print_row("Milestone", "Name", "Tests", "Results", "th", outbuf)
159 for entry in testplan.entries:
160 print_row(entry.milestone, entry.name, entry.tests, None, "td", outbuf)
161 outbuf.write("</table>\n")
162
163 # coverage results
164 outbuf.write("<h3 style=\"text-align: center\">Coverage Results</h2>\n")
165 outbuf.write("<table class=\"dv\">\n")
166 outbuf.write(gen_html_indent(1) + "<tr>\n")
167 # title
168 outbuf.write(gen_html_indent(1) + "<tr>\n")
169 for cov in regr_results["cov_results"]:
170 outbuf.write(
171 gen_html_indent(2) + "<th>" + cov["name"].capitalize() + "</th>\n")
172 outbuf.write(gen_html_indent(1) + "</tr>\n")
173 # result
174 outbuf.write(gen_html_indent(1) + "<tr>\n")
175 for cov in regr_results["cov_results"]:
176 outbuf.write(
177 gen_html_indent(2) + "<td>" + str(cov["result"]) + "</td>\n")
178 outbuf.write(gen_html_indent(1) + "</tr>\n")
179 outbuf.write("</table>\n")
180 return
181
182
183def parse_regr_results(filename):
184 obj = parse_hjson(filename)
185 # TODO need additional syntax checks
186 if not "test_results" in obj.keys():
187 print("Error: key \'test_results\' not found")
188 sys, exit(1)
189 return obj
190
191
192def parse_hjson(filename):
193 try:
Philipp Wagner57df62c2019-11-04 14:49:24 +0000194 f = open(str(filename), 'rU')
Srikrishna Iyerd61724d2019-10-11 10:47:49 -0700195 text = f.read()
196 odict = hjson.loads(text)
197 return odict
198 except IOError:
199 print('IO Error:', filename)
200 raise SystemExit(sys.exc_info()[1])
Philipp Wagnere9dcfbb2019-11-14 13:29:43 +0000201 except hjson.scanner.HjsonDecodeError as e:
202 print("Error: Unable to decode HJSON file %s: %s" % (str(filename), str(e)))
203 sys.exit(1)
Srikrishna Iyerd61724d2019-10-11 10:47:49 -0700204
205
206def merge_dicts(list1, list2):
207 '''merge 2 dicts into one
208
209 This funciton takes 2 dicts as args list1 and list2. It recursively merges list2 into
210 list1 and returns list1. The recursion happens when the the value of a key in both lists
211 is a dict. If the values of the same key in both lists (at the same tree level) are of
212 type str (or of dissimilar type) then there is a conflict, and and error is thrown.
213 '''
214 for key in list2.keys():
215 if key in list1:
216 if type(list1[key]) is list and type(list2[key]) is list:
217 list1[key].extend(list2[key])
218 elif type(list1[key]) is dict and type(list2[key]) is dict:
219 list1[key] = merge_dicts(list1[key], list2[key])
220 else:
221 print("The type of value of key ", key, "in list1: ", str(type(list1[key])), \
222 " does not match the type of value in list2: ", str(type(list2[key])), \
223 " or they are not of type list or dict. The two lists cannot be merged.")
224 sys.exit(1)
225 else:
226 list1[key] = list2[key]
227 return list1
228
229
230def gen_html(testplan_file, regr_results_file, outbuf):
231 testplan = parse_testplan(os.path.abspath(testplan_file))
232 if regr_results_file:
233 regr_results = parse_regr_results(os.path.abspath(regr_results_file))
234 gen_html_regr_results_table(testplan, regr_results, outbuf)
235 else:
236 gen_html_testplan_table(testplan, outbuf)
237 outbuf.write('\n')