blob: a1a1a761aed85d83e3ffcd6d6a34aab68d049c4b [file] [log] [blame]
Cindy Chen2f75cfb2020-06-18 17:25:33 -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
5
6import argparse
7import logging as log
8import re
9from pathlib import Path
10from collections import OrderedDict
11import sys
12
13import hjson
14
15
16def extract_messages(str_buffer, patterns):
17 '''Extract messages matching patterns from str_buffer as a dictionary.
18
19 The patterns argument is a list of pairs, (key, pattern). Each pattern is a regex
20 and all matches in str_buffer are stored in a dictionary under the paired key.
21 '''
22 results = OrderedDict()
23 for key, pattern in patterns:
24 val = results.setdefault(key, [])
25 val += re.findall(pattern, str_buffer, flags=re.MULTILINE)
26
27 return results
28
29
30def extract_messages_count(str_buffer, patterns):
31 '''Extract messages matching patterns from full_file as a dictionary.
32
33 The patterns argument is a list of pairs, (key, pattern). Each pattern is a regex
34 and the total count of all matches in str_buffer are stored in a dictionary under
35 the paired key.
36 '''
37 results = OrderedDict()
38 for key, pattern in patterns:
39 results.setdefault(key, 0)
40 results[key] += len(re.findall(pattern, str_buffer, flags=re.MULTILINE))
41
42 return results
43
44
45def format_percentage(good, bad):
46 '''Return a percetange of good / (good + bad) with a format `100.00 %`.'''
47 denom = good + bad
48 pc = 100 * good / denom if denom else 0
49
50 return '{0:.2f} %'.format(round(pc, 2))
51
52
53def parse_fpv_message(str_buffer):
54 '''Parse error, warnings, and failed properties from the log file'''
55 err_warn_patterns = [("errors", r"^ERROR: .*"),
56 ("errors", r"^\[ERROR.*"),
57 ("warnings", r"^WARNING: .*"),
58 ("warnings", r"^\[WARN.*"),
59 ("cex", r"^\[\d+\]\s+(\S*)\s+cex.*"),
60 ("undetermined", r"^\[\d+\]\s+(\S*)\s+undetermined.*"),
61 ("unreachable", r"^\[\d+\]\s+(\S*)\s+unreachable.*")]
62 return extract_messages(str_buffer, err_warn_patterns)
63
64
65def parse_fpv_summary(str_buffer):
66 '''Count errors, warnings, and property status from the log file'''
67 message_patterns = [("errors", r"^ERROR: .*"),
68 ("errors", r"^\[ERROR.*"),
69 ("warnings", r"^WARNING: .*"),
70 ("warnings", r"^\[WARN.*"),
71 ("proven", r"^\[\d+\].*proven.*"),
72 ("cex", r"^\[\d+\].*cex.*"),
73 ("covered", r"^\[\d+\].*covered.*"),
74 ("undetermined", r"^\[\d+\].*undetermined.*"),
75 ("unreachable", r"^\[\d+\].*unreachable.*")]
76 fpv_summary = extract_messages_count(str_buffer, message_patterns)
77
78 fpv_summary["pass_rate"] = format_percentage(fpv_summary["proven"],
79 fpv_summary["cex"] + fpv_summary["undetermined"])
80 fpv_summary["cov_rate"] = format_percentage(fpv_summary["covered"], fpv_summary["unreachable"])
81
82 return fpv_summary
83
84
85def get_results(logpath):
86 '''Parse FPV log file and extract info to a dictionary'''
87 try:
88 with Path(logpath).open() as f:
89 results = OrderedDict()
90 full_file = f.read()
91 results["messages"] = parse_fpv_message(full_file)
92 fpv_summary = parse_fpv_summary(full_file)
93 if fpv_summary:
94 results["fpv_summary"] = fpv_summary
95 return results
96
97 except IOError as err:
98 err_msg = 'IOError {}'.format(err)
99 log.error("[get_results] IOError %s", err)
100 return {'messages': {'errors': err_msg}}
101
102 return results
103
104
Cindy Chen1690ca02020-11-23 11:25:29 -0800105def get_cov_results(logpath, dut_name):
Cindy Chen2f75cfb2020-06-18 17:25:33 -0700106 '''Parse FPV coverage information from the log file'''
107 try:
108 with Path(logpath).open() as f:
109 full_file = f.read()
Cindy Chen1690ca02020-11-23 11:25:29 -0800110 cov_pattern = r"\s*\|\d*.\d\d%\s\(\d*\/\d*\)" # cov pattern: 100.00% (5/5)
111 pattern_match = r"\s*\|(\d*.\d\d)%\s\(\d*\/\d*\)" # extract percentage in cov_pattern
112 coverage_patterns = \
113 [("stimuli", r"^\|" + dut_name + pattern_match + cov_pattern + cov_pattern),
114 ("coi", r"^\|" + dut_name + cov_pattern + pattern_match + cov_pattern),
115 ("proof", r"^\|" + dut_name + cov_pattern + cov_pattern + pattern_match)]
Cindy Chen2f75cfb2020-06-18 17:25:33 -0700116 cov_results = extract_messages(full_file, coverage_patterns)
117 for key, item in cov_results.items():
118 if len(item) == 1:
119 cov_results[key] = item[0] + " %"
120 else:
121 cov_results[key] = "N/A"
122 log.error("Parse %s coverage error. Expect one matching value, get %s",
123 key, item)
124 return cov_results
125
126 except IOError as err:
127 log.error("[get_cov_results] IOError %s", err)
128 return None
129
130
131def main():
132 parser = argparse.ArgumentParser(
133 description=
134 '''This script parses the JasperGold FPV log to extract below information.
135
136 "messages": {
137 "errors" : []
138 "warnings" : []
139 "cex" : ["property1", "property2"...],
140 "undetermined": [],
141 "unreachable" : [],
142 },
143 "fpv_summary": {
144 "errors" : 0
145 "warnings" : 2
146 "proven" : 20,
147 "cex" : 5,
148 "covered" : 18,
149 "undetermined": 7,
150 "unreachable" : 2,
151 "pass_rate" : "90 %",
152 "cover_rate" : "90 %"
153 },
154 If coverage is enabled, this script will also parse the coverage result:
155 "fpv_coverage": {
156 stimuli: "90 %",
157 coi : "90 %",
158 proof : "80 %"
159 }
160 The script returns nonzero status if any errors or property failures including
161 "cex, undetermined, unreachable" are presented.
Cindy Chen1690ca02020-11-23 11:25:29 -0800162
163 Note this script is capable of parsing jaspergold 2020.09 version.
Cindy Chen2f75cfb2020-06-18 17:25:33 -0700164 ''')
165 parser.add_argument('--logpath',
166 type=str,
167 default="fpv.log",
168 help=('FPV log file path. Defaults to `fpv.log` '
169 'under the current script directory.'))
170
171 parser.add_argument('--reppath',
172 type=str,
173 default="results.hjson",
174 help=('Parsed output hjson file path. Defaults to '
175 '`results.hjson` under the current script directory.'))
176
177 parser.add_argument('--cov',
178 type=int,
179 default=0,
180 help=('Enable parsing coverage data. '
Cindy Chen1690ca02020-11-23 11:25:29 -0800181 'By default, coverage parsing is disabled.'))
Cindy Chen2f75cfb2020-06-18 17:25:33 -0700182
Cindy Chen1690ca02020-11-23 11:25:29 -0800183 parser.add_argument('--dut',
184 type=str,
185 default=None,
186 help=('Tesbench name. '
187 'By default is empty, used for coverage parsing.'))
Cindy Chen2f75cfb2020-06-18 17:25:33 -0700188 args = parser.parse_args()
189
190 results = get_results(args.logpath)
191
192 if args.cov:
Cindy Chen1690ca02020-11-23 11:25:29 -0800193 results["fpv_coverage"] = get_cov_results(args.logpath, args.dut)
Cindy Chen2f75cfb2020-06-18 17:25:33 -0700194
195 with Path(args.reppath).open("w") as results_file:
196 hjson.dump(results,
197 results_file,
198 ensure_ascii=False,
199 for_json=True,
200 use_decimal=True)
201
202 # return nonzero status if any errors or property failures are present
203 # TODO: currently allow FPV warnings
204 err_msgs = results["messages"]
205 n_errors = len(err_msgs["errors"])
206 n_failures = (len(err_msgs.get("cex")) + len(err_msgs.get("undetermined")) +
207 len(err_msgs.get("unreachable")))
208 if n_errors > 0 or n_failures > 0:
209 log.info("Found %d FPV errors, %d FPV property failures", n_errors, n_failures)
210 return 1
211
212 log.info("FPV logfile parsed succesfully")
213 return 0
214
215
216if __name__ == "__main__":
217 sys.exit(main())