Skip to content

Commit 950f046

Browse files
Add summarize.py for cbmc proofs
1 parent 4a8d69f commit 950f046

File tree

1 file changed

+154
-0
lines changed

1 file changed

+154
-0
lines changed

test/cbmc/lib/summarize.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
# SPDX-License-Identifier: MIT-0
3+
4+
import argparse
5+
import json
6+
import logging
7+
import os
8+
import sys
9+
10+
11+
DESCRIPTION = """Print 2 tables in GitHub-flavored Markdown that summarize
12+
an execution of CBMC proofs."""
13+
14+
15+
def get_args():
16+
"""Parse arguments for summarize script."""
17+
parser = argparse.ArgumentParser(description=DESCRIPTION)
18+
for arg in [{
19+
"flags": ["--run-file"],
20+
"help": "path to the Litani run.json file",
21+
"required": True,
22+
}]:
23+
flags = arg.pop("flags")
24+
parser.add_argument(*flags, **arg)
25+
return parser.parse_args()
26+
27+
28+
def _get_max_length_per_column_list(data):
29+
ret = [len(item) + 1 for item in data[0]]
30+
for row in data[1:]:
31+
for idx, item in enumerate(row):
32+
ret[idx] = max(ret[idx], len(item) + 1)
33+
return ret
34+
35+
36+
def _get_table_header_separator(max_length_per_column_list):
37+
line_sep = ""
38+
for max_length_of_word_in_col in max_length_per_column_list:
39+
line_sep += "|" + "-" * (max_length_of_word_in_col + 1)
40+
line_sep += "|\n"
41+
return line_sep
42+
43+
44+
def _get_entries(max_length_per_column_list, row_data):
45+
entries = []
46+
for row in row_data:
47+
entry = ""
48+
for idx, word in enumerate(row):
49+
max_length_of_word_in_col = max_length_per_column_list[idx]
50+
space_formatted_word = (max_length_of_word_in_col - len(word)) * " "
51+
entry += "| " + word + space_formatted_word
52+
entry += "|\n"
53+
entries.append(entry)
54+
return entries
55+
56+
57+
def _get_rendered_table(data):
58+
table = []
59+
max_length_per_column_list = _get_max_length_per_column_list(data)
60+
entries = _get_entries(max_length_per_column_list, data)
61+
for idx, entry in enumerate(entries):
62+
if idx == 1:
63+
line_sep = _get_table_header_separator(max_length_per_column_list)
64+
table.append(line_sep)
65+
table.append(entry)
66+
table.append("\n")
67+
return "".join(table)
68+
69+
70+
def _get_status_and_proof_summaries(run_dict):
71+
"""Parse a dict representing a Litani run and create lists summarizing the
72+
proof results.
73+
74+
Parameters
75+
----------
76+
run_dict
77+
A dictionary representing a Litani run.
78+
79+
80+
Returns
81+
-------
82+
A list of 2 lists.
83+
The first sub-list maps a status to the number of proofs with that status.
84+
The second sub-list maps each proof to its function and status.
85+
"""
86+
count_statuses = {}
87+
proofs = [["Function","Proof", "Status"]]
88+
for proof_pipeline in run_dict:
89+
status_pretty_name = proof_pipeline['status']
90+
try:
91+
count_statuses[status_pretty_name] += 1
92+
except KeyError:
93+
count_statuses[status_pretty_name] = 1
94+
proofs.append([proof_pipeline["sourceLocation"]["function"], proof_pipeline["property"], status_pretty_name])
95+
statuses = [["Status", "Count"]]
96+
for status, count in count_statuses.items():
97+
statuses.append([status, str(count)])
98+
return [statuses, proofs]
99+
100+
101+
def print_proof_results(out_file):
102+
"""
103+
Print 2 strings that summarize the proof results.
104+
When printing, each string will render as a GitHub flavored Markdown table.
105+
"""
106+
output = "## Summary of CBMC proof results\n\n"
107+
with open(out_file, encoding='utf-8') as run_json:
108+
run_dict = json.load(run_json)
109+
110+
"""
111+
Iterate through the json output until we get to the proof results
112+
Before this is info about loop unwinding, etc, which is not relevant
113+
to the summary page
114+
"""
115+
for proof_pipeline in run_dict:
116+
if ('result' not in proof_pipeline):
117+
continue
118+
else:
119+
#Set the run_dict to be only the proof results.
120+
run_dict = proof_pipeline['result']
121+
122+
status_table, proof_table = _get_status_and_proof_summaries(run_dict)
123+
for summary in (status_table, proof_table):
124+
output += _get_rendered_table(summary)
125+
126+
print(output)
127+
sys.stdout.flush()
128+
129+
github_summary_file = os.getenv("GITHUB_STEP_SUMMARY")
130+
if github_summary_file:
131+
with open(github_summary_file, "a") as handle:
132+
print(output, file=handle)
133+
handle.flush()
134+
else:
135+
logging.warning(
136+
"$GITHUB_STEP_SUMMARY not set, not writing summary file")
137+
138+
msg = (
139+
"Click the 'Summary' button to view a Markdown table "
140+
"summarizing all proof results")
141+
if run_dict["status"] != "success":
142+
logging.error("Not all proofs passed.")
143+
logging.error(msg)
144+
sys.exit(1)
145+
logging.info(msg)
146+
147+
148+
if __name__ == '__main__':
149+
args = get_args()
150+
logging.basicConfig(format="%(levelname)s: %(message)s")
151+
try:
152+
print_proof_results(args.run_file)
153+
except Exception as ex: # pylint: disable=broad-except
154+
logging.critical("Could not print results. Exception: %s", str(ex))

0 commit comments

Comments
 (0)