[analyzer] Make CmpRuns external-user friendly.

CmpRuns can be used for static analyzer bug report comparison. However,
we want to make sure external users do not rely on the way bugs are
represented (plist files). Make sure that we have a user
friendly/documented API for CmpRuns script.

llvm-svn: 160314
This commit is contained in:
Anna Zaks 2012-07-16 20:21:42 +00:00
parent 42bff962b6
commit 9b7d7140ef
2 changed files with 63 additions and 45 deletions

View File

@ -11,12 +11,63 @@ two perspectives:
2. For use by end users who want to integrate regular static analyzer testing
into a buildbot like environment.
Usage:
# Load the results of both runs, to obtain lists of the corresponding
# AnalysisDiagnostic objects.
resultsA = loadResults(dirA, opts, deleteEmpty)
resultsB = loadResults(dirB, opts, deleteEmpty)
# Generate a relation from diagnostics in run A to diagnostics in run B
# to obtain a list of triples (a, b, confidence).
diff = compareResults(resultsA, resultsB)
"""
import os
import plistlib
#
class AnalysisDiagnostic:
def __init__(self, data, report, htmlReport):
self._data = data
self._loc = self._data['location']
self._report = report
self._htmlReport = htmlReport
def getFileName(self):
return self._report.run.getSourceName(self._report.files[self._loc['file']])
def getLine(self):
return self._loc['line']
def getColumn(self):
return self._loc['col']
def getCategory(self):
return self._data['category']
def getDescription(self):
return self._data['description']
def getIssueIdentifier(self) :
id = ''
if 'issue_context' in self._data :
id += self._data['issue_context']
if 'issue_hash' in self._data :
id += str(self._data['issue_hash'])
return id
def getReport(self):
if self._htmlReport is None:
return " "
return os.path.join(self._report.run.path, self._htmlReport)
def getReadableName(self):
return '%s:%d:%d, %s: %s' % (self.getFileName(), self.getLine(),
self.getColumn(), self.getCategory(),
self.getDescription())
class multidict:
def __init__(self, elts=()):
@ -54,34 +105,6 @@ class AnalysisReport:
self.run = run
self.files = files
class AnalysisDiagnostic:
def __init__(self, data, report, htmlReport):
self.data = data
self.report = report
self.htmlReport = htmlReport
def getReadableName(self):
loc = self.data['location']
filename = self.report.run.getSourceName(self.report.files[loc['file']])
line = loc['line']
column = loc['col']
category = self.data['category']
description = self.data['description']
# FIXME: Get a report number based on this key, to 'distinguish'
# reports, or something.
return '%s:%d:%d, %s: %s' % (filename, line, column, category,
description)
def getReportData(self):
if self.htmlReport is None:
return " "
return os.path.join(self.report.run.path, self.htmlReport)
# We could also dump the report with:
# return open(os.path.join(self.report.run.path,
# self.htmlReport), "rb").read()
class AnalysisRun:
def __init__(self, path, opts):
self.path = path
@ -134,13 +157,8 @@ def loadResults(path, opts, deleteEmpty=True):
return run
def getIssueIdentifier(d) :
id = ''
if 'issue_context' in d.data :
id += d.data['issue_context']
if 'issue_hash' in d.data :
id += str(d.data['issue_hash'])
return id
def cmpAnalysisDiagnostic(d) :
return d.getIssueIdentifier()
def compareResults(A, B):
"""
@ -160,14 +178,14 @@ def compareResults(A, B):
neqB = []
eltsA = list(A.diagnostics)
eltsB = list(B.diagnostics)
eltsA.sort(key = getIssueIdentifier)
eltsB.sort(key = getIssueIdentifier)
eltsA.sort(key = cmpAnalysisDiagnostic)
eltsB.sort(key = cmpAnalysisDiagnostic)
while eltsA and eltsB:
a = eltsA.pop()
b = eltsB.pop()
if (getIssueIdentifier(a) == getIssueIdentifier(b)) :
if (a.getIssueIdentifier() == b.getIssueIdentifier()) :
res.append((a, b, 0))
elif a.data > b.data:
elif a._data > b._data:
neqA.append(a)
eltsB.append(b)
else:
@ -189,7 +207,7 @@ def compareResults(A, B):
return res
def cmpScanBuildResults(dirA, dirB, opts, deleteEmpty=True):
def dumpScanBuildResultsDiff(dirA, dirB, opts, deleteEmpty=True):
# Load the run results.
resultsA = loadResults(dirA, opts, deleteEmpty)
resultsB = loadResults(dirB, opts, deleteEmpty)
@ -209,13 +227,13 @@ def cmpScanBuildResults(dirA, dirB, opts, deleteEmpty=True):
foundDiffs += 1
if auxLog:
print >>auxLog, ("('ADDED', %r, %r)" % (b.getReadableName(),
b.getReportData()))
b.getReport()))
elif b is None:
print "REMOVED: %r" % a.getReadableName()
foundDiffs += 1
if auxLog:
print >>auxLog, ("('REMOVED', %r, %r)" % (a.getReadableName(),
a.getReportData()))
a.getReport()))
elif confidence:
print "CHANGED: %r to %r" % (a.getReadableName(),
b.getReadableName())
@ -224,8 +242,8 @@ def cmpScanBuildResults(dirA, dirB, opts, deleteEmpty=True):
print >>auxLog, ("('CHANGED', %r, %r, %r, %r)"
% (a.getReadableName(),
b.getReadableName(),
a.getReportData(),
b.getReportData()))
a.getReport(),
b.getReport()))
else:
pass

View File

@ -357,7 +357,7 @@ def runCmpResults(Dir):
OLD_STDOUT = sys.stdout
sys.stdout = Discarder()
# Scan the results, delete empty plist files.
NumDiffs = CmpRuns.cmpScanBuildResults(RefDir, NewDir, Opts, False)
NumDiffs = CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts, False)
sys.stdout = OLD_STDOUT
if (NumDiffs > 0) :
print "Warning: %r differences in diagnostics. See %s" % \