summaryrefslogtreecommitdiff
path: root/utils/analyzer/CmpRuns.py
diff options
context:
space:
mode:
Diffstat (limited to 'utils/analyzer/CmpRuns.py')
-rwxr-xr-xutils/analyzer/CmpRuns.py97
1 files changed, 44 insertions, 53 deletions
diff --git a/utils/analyzer/CmpRuns.py b/utils/analyzer/CmpRuns.py
index 2d1f44f6880c..2c0ed6aae3a2 100755
--- a/utils/analyzer/CmpRuns.py
+++ b/utils/analyzer/CmpRuns.py
@@ -6,8 +6,8 @@ which reports have been added, removed, or changed.
This is designed to support automated testing using the static analyzer, from
two perspectives:
- 1. To monitor changes in the static analyzer's reports on real code bases, for
- regression testing.
+ 1. To monitor changes in the static analyzer's reports on real code bases,
+ for regression testing.
2. For use by end users who want to integrate regular static analyzer testing
into a buildbot like environment.
@@ -28,7 +28,7 @@ Usage:
import os
import plistlib
-import CmpRuns
+
# Information about analysis run:
# path - the analysis output directory
@@ -40,6 +40,7 @@ class SingleRunInfo:
self.root = root.rstrip("/\\")
self.verboseLog = verboseLog
+
class AnalysisDiagnostic:
def __init__(self, data, report, htmlReport):
self._data = data
@@ -51,7 +52,7 @@ class AnalysisDiagnostic:
root = self._report.run.root
fileName = self._report.files[self._loc['file']]
if fileName.startswith(root) and len(root) > 0:
- return fileName[len(root)+1:]
+ return fileName[len(root) + 1:]
return fileName
def getLine(self):
@@ -66,12 +67,12 @@ class AnalysisDiagnostic:
def getDescription(self):
return self._data['description']
- def getIssueIdentifier(self) :
+ def getIssueIdentifier(self):
id = self.getFileName() + "+"
- if 'issue_context' in self._data :
- id += self._data['issue_context'] + "+"
- if 'issue_hash_content_of_line_in_context' in self._data :
- id += str(self._data['issue_hash_content_of_line_in_context'])
+ if 'issue_context' in self._data:
+ id += self._data['issue_context'] + "+"
+ if 'issue_hash_content_of_line_in_context' in self._data:
+ id += str(self._data['issue_hash_content_of_line_in_context'])
return id
def getReport(self):
@@ -89,29 +90,6 @@ class AnalysisDiagnostic:
def getRawData(self):
return self._data
-class multidict:
- def __init__(self, elts=()):
- self.data = {}
- for key,value in elts:
- self[key] = value
-
- def __getitem__(self, item):
- return self.data[item]
- def __setitem__(self, key, value):
- if key in self.data:
- self.data[key].append(value)
- else:
- self.data[key] = [value]
- def items(self):
- return self.data.items()
- def values(self):
- return self.data.values()
- def keys(self):
- return self.data.keys()
- def __len__(self):
- return len(self.data)
- def get(self, key, default=None):
- return self.data.get(key, default)
class CmpOptions:
def __init__(self, verboseLog=None, rootA="", rootB=""):
@@ -119,12 +97,14 @@ class CmpOptions:
self.rootB = rootB
self.verboseLog = verboseLog
+
class AnalysisReport:
def __init__(self, run, files):
self.run = run
self.files = files
self.diagnostics = []
+
class AnalysisRun:
def __init__(self, info):
self.path = info.path
@@ -145,14 +125,14 @@ class AnalysisRun:
# reports. Assume that all reports were created using the same
# clang version (this is always true and is more efficient).
if 'clang_version' in data:
- if self.clang_version == None:
+ if self.clang_version is None:
self.clang_version = data.pop('clang_version')
else:
data.pop('clang_version')
# Ignore/delete empty reports.
if not data['files']:
- if deleteEmpty == True:
+ if deleteEmpty:
os.remove(p)
return
@@ -169,8 +149,7 @@ class AnalysisRun:
report = AnalysisReport(self, data.pop('files'))
diagnostics = [AnalysisDiagnostic(d, report, h)
- for d,h in zip(data.pop('diagnostics'),
- htmlFiles)]
+ for d, h in zip(data.pop('diagnostics'), htmlFiles)]
assert not data
@@ -179,15 +158,21 @@ class AnalysisRun:
self.diagnostics.extend(diagnostics)
-# Backward compatibility API.
-def loadResults(path, opts, root = "", deleteEmpty=True):
+def loadResults(path, opts, root="", deleteEmpty=True):
+ """
+ Backwards compatibility API.
+ """
return loadResultsFromSingleRun(SingleRunInfo(path, root, opts.verboseLog),
deleteEmpty)
-# Load results of the analyzes from a given output folder.
-# - info is the SingleRunInfo object
-# - deleteEmpty specifies if the empty plist files should be deleted
+
def loadResultsFromSingleRun(info, deleteEmpty=True):
+ """
+ # Load results of the analyzes from a given output folder.
+ # - info is the SingleRunInfo object
+ # - deleteEmpty specifies if the empty plist files should be deleted
+
+ """
path = info.path
run = AnalysisRun(info)
@@ -203,9 +188,11 @@ def loadResultsFromSingleRun(info, deleteEmpty=True):
return run
-def cmpAnalysisDiagnostic(d) :
+
+def cmpAnalysisDiagnostic(d):
return d.getIssueIdentifier()
+
def compareResults(A, B):
"""
compareResults - Generate a relation from diagnostics in run A to
@@ -224,12 +211,12 @@ def compareResults(A, B):
neqB = []
eltsA = list(A.diagnostics)
eltsB = list(B.diagnostics)
- eltsA.sort(key = cmpAnalysisDiagnostic)
- eltsB.sort(key = cmpAnalysisDiagnostic)
+ eltsA.sort(key=cmpAnalysisDiagnostic)
+ eltsB.sort(key=cmpAnalysisDiagnostic)
while eltsA and eltsB:
a = eltsA.pop()
b = eltsB.pop()
- if (a.getIssueIdentifier() == b.getIssueIdentifier()) :
+ if (a.getIssueIdentifier() == b.getIssueIdentifier()):
res.append((a, b, 0))
elif a.getIssueIdentifier() > b.getIssueIdentifier():
eltsB.append(b)
@@ -240,11 +227,11 @@ def compareResults(A, B):
neqA.extend(eltsA)
neqB.extend(eltsB)
- # FIXME: Add fuzzy matching. One simple and possible effective idea would be
- # to bin the diagnostics, print them in a normalized form (based solely on
- # the structure of the diagnostic), compute the diff, then use that as the
- # basis for matching. This has the nice property that we don't depend in any
- # way on the diagnostic format.
+ # FIXME: Add fuzzy matching. One simple and possible effective idea would
+ # be to bin the diagnostics, print them in a normalized form (based solely
+ # on the structure of the diagnostic), compute the diff, then use that as
+ # the basis for matching. This has the nice property that we don't depend
+ # in any way on the diagnostic format.
for a in neqA:
res.append((a, None, None))
@@ -253,6 +240,7 @@ def compareResults(A, B):
return res
+
def dumpScanBuildResultsDiff(dirA, dirB, opts, deleteEmpty=True):
# Load the run results.
resultsA = loadResults(dirA, opts, opts.rootA, deleteEmpty)
@@ -267,7 +255,7 @@ def dumpScanBuildResultsDiff(dirA, dirB, opts, deleteEmpty=True):
diff = compareResults(resultsA, resultsB)
foundDiffs = 0
for res in diff:
- a,b,confidence = res
+ a, b, confidence = res
if a is None:
print "ADDED: %r" % b.getReadableName()
foundDiffs += 1
@@ -302,6 +290,7 @@ def dumpScanBuildResultsDiff(dirA, dirB, opts, deleteEmpty=True):
return foundDiffs, len(resultsA.diagnostics), len(resultsB.diagnostics)
+
def main():
from optparse import OptionParser
parser = OptionParser("usage: %prog [options] [dir A] [dir B]")
@@ -312,7 +301,8 @@ def main():
help="Prefix to ignore on source files for directory B",
action="store", type=str, default="")
parser.add_option("", "--verbose-log", dest="verboseLog",
- help="Write additional information to LOG [default=None]",
+ help="Write additional information to LOG \
+ [default=None]",
action="store", type=str, default=None,
metavar="LOG")
(opts, args) = parser.parse_args()
@@ -320,9 +310,10 @@ def main():
if len(args) != 2:
parser.error("invalid number of arguments")
- dirA,dirB = args
+ dirA, dirB = args
dumpScanBuildResultsDiff(dirA, dirB, opts)
+
if __name__ == '__main__':
main()