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