Emit a table of warnings by project and severity.

* Use 't1' CSS class for main warning tables,
  and default table style for new warning count table.
* Count total skipped warnings.
* Append count of warnings in each severity header.
* Fix some skipped warning patterns and count/dump skipped warnings.
* Add dumpskippedanchors function to fix expand_collapse
  error when --byproject flag is used and severity.SKIP
  is not emitted.
* Minor coding style changes:
  * Loop through severity.kinds.
  * Remove unused import.
  * Add spaces around assignment operators.
  * Adjust indentation.

Bug: 31377083
Test: run warn.py with build.log
Change-Id: I68949edc4601b10ea2f7ac84e48e6c3da4ab1e4f
This commit is contained in:
Chih-Hung Hsieh
2016-09-12 16:20:49 -07:00
parent 56c2a18a50
commit e41c99b721

View File

@@ -2,7 +2,6 @@
# This file uses the following encoding: utf-8 # This file uses the following encoding: utf-8
import argparse import argparse
import sys
import re import re
parser = argparse.ArgumentParser(description='Convert a build log into HTML') parser = argparse.ArgumentParser(description='Convert a build log into HTML')
@@ -26,14 +25,15 @@ args = parser.parse_args()
# if you add another level, don't forget to give it a color below # if you add another level, don't forget to give it a color below
class severity: class severity:
UNKNOWN=0 UNKNOWN = 0
SKIP=100 FIXMENOW = 1
FIXMENOW=1 HIGH = 2
HIGH=2 MEDIUM = 3
MEDIUM=3 LOW = 4
LOW=4 TIDY = 5
TIDY=5 HARMLESS = 6
HARMLESS=6 SKIP = 100
kinds = [FIXMENOW, HIGH, MEDIUM, LOW, TIDY, HARMLESS, UNKNOWN, SKIP]
def colorforseverity(sev): def colorforseverity(sev):
if sev == severity.FIXMENOW: if sev == severity.FIXMENOW:
@@ -69,6 +69,23 @@ def headerforseverity(sev):
return 'Unknown warnings' return 'Unknown warnings'
return 'Unhandled warnings' return 'Unhandled warnings'
def columnheaderforseverity(sev):
if sev == severity.FIXMENOW:
return 'FixNow'
if sev == severity.HIGH:
return 'High'
if sev == severity.MEDIUM:
return 'Medium'
if sev == severity.LOW:
return 'Low'
if sev == severity.HARMLESS:
return 'Harmless'
if sev == severity.TIDY:
return 'Tidy'
if sev == severity.UNKNOWN:
return 'Unknown'
return 'Unhandled'
warnpatterns = [ warnpatterns = [
{ 'category':'make', 'severity':severity.MEDIUM, 'members':[], 'option':'', { 'category':'make', 'severity':severity.MEDIUM, 'members':[], 'option':'',
'description':'make: overriding commands/ignoring old commands', 'description':'make: overriding commands/ignoring old commands',
@@ -240,7 +257,8 @@ warnpatterns = [
'patterns':[r".*: warning: control reaches end of non-void function"] }, 'patterns':[r".*: warning: control reaches end of non-void function"] },
{ 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wimplicit-int', { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wimplicit-int',
'description':'Implicit int type', 'description':'Implicit int type',
'patterns':[r".*: warning: type specifier missing, defaults to 'int'"] }, 'patterns':[r".*: warning: type specifier missing, defaults to 'int'",
r".*: warning: type defaults to 'int' in declaration of '.+'"] },
{ 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wmain-return-type', { 'category':'C/C++', 'severity':severity.MEDIUM, 'members':[], 'option':'-Wmain-return-type',
'description':'Main function should return int', 'description':'Main function should return int',
'patterns':[r".*: warning: return type of 'main' is not 'int'"] }, 'patterns':[r".*: warning: return type of 'main' is not 'int'"] },
@@ -295,9 +313,6 @@ warnpatterns = [
r".*: warning: .+ results in a null pointer dereference", r".*: warning: .+ results in a null pointer dereference",
r".*: warning: Access to .+ results in a dereference of a null pointer", r".*: warning: Access to .+ results in a dereference of a null pointer",
r".*: warning: Null pointer argument in"] }, r".*: warning: Null pointer argument in"] },
{ 'category':'cont.', 'severity':severity.SKIP, 'members':[], 'option':'',
'description':'',
'patterns':[r".*: warning: type defaults to 'int' in declaration of '.+'"] },
{ 'category':'cont.', 'severity':severity.SKIP, 'members':[], 'option':'', { 'category':'cont.', 'severity':severity.SKIP, 'members':[], 'option':'',
'description':'', 'description':'',
'patterns':[r".*: warning: parameter names \(without types\) in function declaration"] }, 'patterns':[r".*: warning: parameter names \(without types\) in function declaration"] },
@@ -1909,6 +1924,8 @@ projectpatterns = []
for p in projectlist: for p in projectlist:
projectpatterns.append({'description':p[0], 'members':[], 'pattern':re.compile(p[1])}) projectpatterns.append({'description':p[0], 'members':[], 'pattern':re.compile(p[1])})
projectnames = [p[0] for p in projectlist]
# Each warning pattern has 3 dictionaries: # Each warning pattern has 3 dictionaries:
# (1) 'projects' maps a project name to number of warnings in that project. # (1) 'projects' maps a project name to number of warnings in that project.
# (2) 'projectanchor' maps a project name to its anchor number for HTML. # (2) 'projectanchor' maps a project name to its anchor number for HTML.
@@ -1953,12 +1970,13 @@ html_script_style = """\
}; };
</script> </script>
<style type="text/css"> <style type="text/css">
table,th,td{border-collapse:collapse; width:100%;} th,td{border-collapse:collapse; border:1px solid black;}
.button{color:blue;font-size:110%;font-weight:bolder;} .button{color:blue;font-size:110%;font-weight:bolder;}
.bt{color:black;background-color:transparent;border:none;outline:none; .bt{color:black;background-color:transparent;border:none;outline:none;
font-size:140%;font-weight:bolder;} font-size:140%;font-weight:bolder;}
.c0{background-color:#e0e0e0;} .c0{background-color:#e0e0e0;}
.c1{background-color:#d0d0d0;} .c1{background-color:#d0d0d0;}
.t1{border-collapse:collapse; width:100%; border:1px solid black;}
</style>\n""" </style>\n"""
@@ -1990,45 +2008,114 @@ def sortwarnings():
for i in warnpatterns: for i in warnpatterns:
i['members'] = sorted(set(i['members'])) i['members'] = sorted(set(i['members']))
# dump a table of warnings per project and severity
def dumpstatsbyproject():
projects = set(projectnames)
severities = set(severity.kinds)
# warnings[p][s] is number of warnings in project p of severity s.
warnings = {p:{s:0 for s in severity.kinds} for p in projectnames}
for i in warnpatterns:
s = i['severity']
for p in i['projects']:
warnings[p][s] += i['projects'][p]
# totalbyseverity[s] is number of warnings of severity s.
totalbyseverity = {s:0 for s in severity.kinds}
# emit table header
output('<blockquote><table border=1>\n<tr><th></th>\n')
for s in severity.kinds:
output('<th width="8%"><span style="background-color:{}">{}</span></th>'.
format(colorforseverity(s), columnheaderforseverity(s)))
output('<th>TOTAL</th></tr>\n')
# emit a row of warnings per project
totalallprojects = 0
for p in projectnames:
totalbyproject = 0
output('<tr><td align="left">{}</td>'.format(p))
for s in severity.kinds:
output('<td align="right">{}</td>'.format(warnings[p][s]))
totalbyproject += warnings[p][s]
totalbyseverity[s] += warnings[p][s]
output('<td align="right">{}</td>'.format(totalbyproject))
totalallprojects += totalbyproject
output('</tr>\n')
# emit a row of warning counts per severity
totalallseverities = 0
output('<tr><td align="right">TOTAL</td>')
for s in severity.kinds:
output('<td align="right">{}</td>'.format(totalbyseverity[s]))
totalallseverities += totalbyseverity[s]
output('<td align="right">{}</td></tr>\n'.format(totalallprojects))
# at the end of table, verify total counts
output('</table></blockquote><br>\n')
if totalallprojects != totalallseverities:
output('<h3>ERROR: Sum of warnings by project ' +
'!= Sum of warnings by severity.</h3>\n')
# dump some stats about total number of warnings and such # dump some stats about total number of warnings and such
def dumpstats(): def dumpstats():
known = 0 known = 0
skipped = 0
unknown = 0 unknown = 0
sortwarnings() sortwarnings()
for i in warnpatterns: for i in warnpatterns:
if i['severity'] == severity.UNKNOWN: if i['severity'] == severity.UNKNOWN:
unknown += len(i['members']) unknown += len(i['members'])
elif i['severity'] != severity.SKIP: elif i['severity'] == severity.SKIP:
skipped += len(i['members'])
else:
known += len(i['members']) known += len(i['members'])
output('\nNumber of classified warnings: <b>' + str(known) + '</b><br>' ) output('\nNumber of classified warnings: <b>' + str(known) + '</b><br>' )
output('\nNumber of skipped warnings: <b>' + str(skipped) + '</b><br>')
output('\nNumber of unclassified warnings: <b>' + str(unknown) + '</b><br>') output('\nNumber of unclassified warnings: <b>' + str(unknown) + '</b><br>')
total = unknown + known total = unknown + known + skipped
output('\nTotal number of warnings: <b>' + str(total) + '</b>') output('\nTotal number of warnings: <b>' + str(total) + '</b>')
if total < 1000: if total < 1000:
output('(low count may indicate incremental build)') output('(low count may indicate incremental build)')
output('<br><br>\n') output('<br><br>\n')
def emitbuttons():
output('<button class="button" onclick="expand_collapse(1);">' + output('<button class="button" onclick="expand_collapse(1);">' +
'Expand all warnings</button> ' + 'Expand all warnings</button>\n' +
'<button class="button" onclick="expand_collapse(0);">' + '<button class="button" onclick="expand_collapse(0);">' +
'Collapse all warnings</button>') 'Collapse all warnings</button><br>\n')
output('<br>\n')
# dump everything for a given severity # dump everything for a given severity
def dumpseverity(sev): def dumpseverity(sev):
global anchor global anchor
total = 0
for i in warnpatterns:
if i['severity'] == sev:
total = total + len(i['members'])
output('\n<br><span style="background-color:' + colorforseverity(sev) + '"><b>' + output('\n<br><span style="background-color:' + colorforseverity(sev) + '"><b>' +
headerforseverity(sev) + ':</b></span>\n') headerforseverity(sev) + ': ' + str(total) + '</b></span>\n')
output('<blockquote>\n') output('<blockquote>\n')
for i in warnpatterns: for i in warnpatterns:
if i['severity'] == sev and len(i['members']) > 0: if i['severity'] == sev and len(i['members']) > 0:
anchor += 1 anchor += 1
i['anchor'] = str(anchor) i['anchor'] = str(anchor)
if args.byproject: if args.byproject:
dumpcategorybyproject(sev, i) dumpcategorybyproject(sev, i)
else: else:
dumpcategory(sev, i) dumpcategory(sev, i)
output('</blockquote>\n') output('</blockquote>\n')
# emit all skipped project anchors for expand_collapse.
def dumpskippedanchors():
output('<div style="display:none;">\n') # hide these fake elements
for i in warnpatterns:
if i['severity'] == severity.SKIP and len(i['members']) > 0:
projects = i['projectwarning'].keys()
for p in projects:
output('<div id="' + i['projectanchor'][p] + '"></div>' +
'<div id="' + i['projectanchor'][p] + '_mark"></div>\n')
output('</div>\n')
def allpatterns(cat): def allpatterns(cat):
pats = '' pats = ''
for i in cat['patterns']: for i in cat['patterns']:
@@ -2057,7 +2144,7 @@ def dumpfixed():
output('<blockquote>\n') output('<blockquote>\n')
fixed_patterns = [] fixed_patterns = []
for i in warnpatterns: for i in warnpatterns:
if len(i['members']) == 0 and i['severity'] != severity.SKIP: if len(i['members']) == 0:
fixed_patterns.append(i['description'] + ' (' + fixed_patterns.append(i['description'] + ' (' +
allpatterns(i) + ') ' + i['option']) allpatterns(i) + ') ' + i['option'])
fixed_patterns.sort() fixed_patterns.sort()
@@ -2083,14 +2170,14 @@ def warningwithurl(line):
def dumpgroup(sev, anchor, description, warnings): def dumpgroup(sev, anchor, description, warnings):
mark = anchor + '_mark' mark = anchor + '_mark'
output('\n<table frame="box">\n') output('\n<table class="t1">\n')
output('<tr bgcolor="' + colorforseverity(sev) + '">' + output('<tr bgcolor="' + colorforseverity(sev) + '">' +
'<td><button class="bt" id="' + mark + '<td><button class="bt" id="' + mark +
'" onclick="expand(\'' + anchor + '\');">' + '" onclick="expand(\'' + anchor + '\');">' +
'&#x2295</button> ' + description + '</td></tr>\n') '&#x2295</button> ' + description + '</td></tr>\n')
output('</table>\n') output('</table>\n')
output('<div id="' + anchor + '" style="display:none;">') output('<div id="' + anchor + '" style="display:none;">')
output('<table>\n') output('<table class="t1">\n')
for i in warnings: for i in warnings:
tablerow(warningwithurl(i)) tablerow(warningwithurl(i))
output('</table></div>\n') output('</table></div>\n')
@@ -2191,16 +2278,15 @@ def parseinputfile():
def dumphtml(): def dumphtml():
dumphtmlprologue('Warnings for ' + platformversion + ' - ' + targetproduct + ' - ' + targetvariant) dumphtmlprologue('Warnings for ' + platformversion + ' - ' + targetproduct + ' - ' + targetvariant)
dumpstats() dumpstats()
dumpstatsbyproject()
emitbuttons()
# sort table based on number of members once dumpstats has deduplicated the # sort table based on number of members once dumpstats has deduplicated the
# members. # members.
warnpatterns.sort(reverse=True, key=lambda i: len(i['members'])) warnpatterns.sort(reverse=True, key=lambda i: len(i['members']))
dumpseverity(severity.FIXMENOW) # Dump warnings by severity. If severity.SKIP warnings are not dumpped,
dumpseverity(severity.HIGH) # the project anchors should be dumped through dumpskippedanchors.
dumpseverity(severity.MEDIUM) for s in severity.kinds:
dumpseverity(severity.LOW) dumpseverity(s)
dumpseverity(severity.TIDY)
dumpseverity(severity.HARMLESS)
dumpseverity(severity.UNKNOWN)
dumpfixed() dumpfixed()
dumphtmlepilogue() dumphtmlepilogue()
@@ -2237,13 +2323,8 @@ def countseverity(sev, kind):
def dumpcsv(): def dumpcsv():
sortwarnings() sortwarnings()
total = 0 total = 0
total += countseverity(severity.FIXMENOW, 'FixNow') for s in severity.kinds:
total += countseverity(severity.HIGH, 'High') total += countseverity(s, columnheaderforseverity(s))
total += countseverity(severity.MEDIUM, 'Medium')
total += countseverity(severity.LOW, 'Low')
total += countseverity(severity.TIDY, 'Tidy')
total += countseverity(severity.HARMLESS, 'Harmless')
total += countseverity(severity.UNKNOWN, 'Unknown')
print '{},,{}'.format(total, 'All warnings') print '{},,{}'.format(total, 'All warnings')