blob: e6527eedc5614de26d6a18a6768b16d1b3928aa4 [file] [log] [blame]
Anna Zaks1b417162011-10-06 23:26:27 +00001#!/usr/bin/env python
2
3"""
4Static Analyzer qualification infrastructure.
5
6The goal is to test the analyzer against different projects, check for failures,
7compare results, and measure performance.
8
9Repository Directory will contain sources of the projects as well as the
10information on how to build them and the expected output.
11Repository Directory structure:
12 - ProjectMap file
13 - Historical Performance Data
14 - Project Dir1
15 - ReferenceOutput
16 - Project Dir2
17 - ReferenceOutput
18 ..
19
20To test the build of the analyzer one would:
21 - Copy over a copy of the Repository Directory. (TODO: Prefer to ensure that
22 the build directory does not pollute the repository to min network traffic).
23 - Build all projects, until error. Produce logs to report errors.
24 - Compare results.
25
26The files which should be kept around for failure investigations:
27 RepositoryCopy/Project DirI/ScanBuildResults
28 RepositoryCopy/Project DirI/run_static_analyzer.log
29
30Assumptions (TODO: shouldn't need to assume these.):
31 The script is being run from the Repository Directory.
Anna Zaks5fa3f132011-11-02 20:46:50 +000032 The compiler for scan-build and scan-build are in the PATH.
Anna Zaks1b417162011-10-06 23:26:27 +000033 export PATH=/Users/zaks/workspace/c2llvm/build/Release+Asserts/bin:$PATH
34
35For more logging, set the env variables:
36 zaks:TI zaks$ export CCC_ANALYZER_LOG=1
37 zaks:TI zaks$ export CCC_ANALYZER_VERBOSE=1
38"""
39import CmpRuns
40
41import os
42import csv
43import sys
44import glob
45import shutil
46import time
47import plistlib
48from subprocess import check_call
49
50# Project map stores info about all the "registered" projects.
51ProjectMapFile = "projectMap.csv"
52
53# Names of the project specific scripts.
54# The script that needs to be executed before the build can start.
Anna Zaks5fa3f132011-11-02 20:46:50 +000055CleanupScript = "cleanup_run_static_analyzer.sh"
Anna Zaks1b417162011-10-06 23:26:27 +000056# This is a file containing commands for scan-build.
57BuildScript = "run_static_analyzer.cmd"
58
59# The log file name.
60BuildLogName = "run_static_analyzer.log"
61# Summary file - contains the summary of the failures. Ex: This info can be be
62# displayed when buildbot detects a build failure.
63NumOfFailuresInSummary = 10
64FailuresSummaryFileName = "failures.txt"
65# Summary of the result diffs.
66DiffsSummaryFileName = "diffs.txt"
67
68# The scan-build result directory.
69SBOutputDirName = "ScanBuildResults"
70SBOutputDirReferencePrefix = "Ref"
71
72Verbose = 1
73
74def getProjectMapPath():
75 ProjectMapPath = os.path.join(os.path.abspath(os.curdir),
76 ProjectMapFile)
77 if not os.path.exists(ProjectMapPath):
78 print "Error: Cannot find the Project Map file " + ProjectMapPath +\
79 "\nRunning script for the wrong directory?"
80 sys.exit(-1)
81 return ProjectMapPath
82
83def getProjectDir(ID):
84 return os.path.join(os.path.abspath(os.curdir), ID)
85
86# Run pre-processing script if any.
Anna Zaks5fa3f132011-11-02 20:46:50 +000087def runCleanupScript(Dir, PBuildLogFile):
88 ScriptPath = os.path.join(Dir, CleanupScript)
Anna Zaks1b417162011-10-06 23:26:27 +000089 if os.path.exists(ScriptPath):
90 try:
91 if Verbose == 1:
92 print " Executing: %s" % (ScriptPath,)
93 check_call("chmod +x %s" % ScriptPath, cwd = Dir,
94 stderr=PBuildLogFile,
95 stdout=PBuildLogFile,
96 shell=True)
97 check_call(ScriptPath, cwd = Dir, stderr=PBuildLogFile,
98 stdout=PBuildLogFile,
99 shell=True)
100 except:
101 print "Error: The pre-processing step failed. See ", \
102 PBuildLogFile.name, " for details."
103 sys.exit(-1)
104
105# Build the project with scan-build by reading in the commands and
106# prefixing them with the scan-build options.
107def runScanBuild(Dir, SBOutputDir, PBuildLogFile):
108 BuildScriptPath = os.path.join(Dir, BuildScript)
109 if not os.path.exists(BuildScriptPath):
110 print "Error: build script is not defined: %s" % BuildScriptPath
111 sys.exit(-1)
112 SBOptions = "-plist -o " + SBOutputDir + " "
113 SBOptions += "-enable-checker core,deadcode.DeadStores"
114 try:
115 SBCommandFile = open(BuildScriptPath, "r")
116 SBPrefix = "scan-build " + SBOptions + " "
117 for Command in SBCommandFile:
118 SBCommand = SBPrefix + Command
119 if Verbose == 1:
120 print " Executing: %s" % (SBCommand,)
121 check_call(SBCommand, cwd = Dir, stderr=PBuildLogFile,
122 stdout=PBuildLogFile,
123 shell=True)
124 except:
125 print "Error: scan-build failed. See ",PBuildLogFile.name,\
126 " for details."
127 sys.exit(-1)
128
Anna Zaks5fa3f132011-11-02 20:46:50 +0000129def buildProject(Dir, SBOutputDir, ClenupAfterBuild):
Anna Zaks1b417162011-10-06 23:26:27 +0000130 TBegin = time.time()
131
132 BuildLogPath = os.path.join(Dir, BuildLogName)
133 print "Log file: %s" % (BuildLogPath,)
134
135 # Clean up the log file.
136 if (os.path.exists(BuildLogPath)) :
137 RmCommand = "rm " + BuildLogPath
138 if Verbose == 1:
Anna Zaks5fa3f132011-11-02 20:46:50 +0000139 print " Executing: %s" % (RmCommand,)
Anna Zaks1b417162011-10-06 23:26:27 +0000140 check_call(RmCommand, shell=True)
141
142 # Open the log file.
143 PBuildLogFile = open(BuildLogPath, "wb+")
144 try:
145 # Clean up scan build results.
146 if (os.path.exists(SBOutputDir)) :
147 RmCommand = "rm -r " + SBOutputDir
148 if Verbose == 1:
149 print " Executing: %s" % (RmCommand,)
150 check_call(RmCommand, stderr=PBuildLogFile,
151 stdout=PBuildLogFile, shell=True)
152
Anna Zaks5fa3f132011-11-02 20:46:50 +0000153 runCleanupScript(Dir, PBuildLogFile)
154 runScanBuild(Dir, SBOutputDir, PBuildLogFile)
155
156 if ClenupAfterBuild :
157 runCleanupScript(Dir, PBuildLogFile)
158
Anna Zaks1b417162011-10-06 23:26:27 +0000159 finally:
160 PBuildLogFile.close()
161
162 print "Build complete (time: %.2f). See the log for more details: %s" % \
163 ((time.time()-TBegin), BuildLogPath)
164
165# A plist file is created for each call to the analyzer(each source file).
166# We are only interested on the once that have bug reports, so delete the rest.
167def CleanUpEmptyPlists(SBOutputDir):
168 for F in glob.glob(SBOutputDir + "/*/*.plist"):
169 P = os.path.join(SBOutputDir, F)
170
171 Data = plistlib.readPlist(P)
172 # Delete empty reports.
173 if not Data['files']:
174 os.remove(P)
175 continue
176
177# Given the scan-build output directory, checks if the build failed
178# (by searching for the failures directories). If there are failures, it
179# creates a summary file in the output directory.
180def checkBuild(SBOutputDir):
181 # Check if there are failures.
182 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt")
183 TotalFailed = len(Failures);
184 if TotalFailed == 0:
185 CleanUpEmptyPlists(SBOutputDir)
186 Plists = glob.glob(SBOutputDir + "/*/*.plist")
187 print "Number of bug reports (non empty plist files) produced: %d" %\
188 len(Plists)
189 return;
190
191 # Create summary file to display when the build fails.
192 SummaryPath = os.path.join(SBOutputDir, FailuresSummaryFileName);
193 if (Verbose > 0):
194 print " Creating the failures summary file %s." % (SummaryPath,)
195
196 SummaryLog = open(SummaryPath, "w+")
197 try:
198 SummaryLog.write("Total of %d failures discovered.\n" % (TotalFailed,))
199 if TotalFailed > NumOfFailuresInSummary:
200 SummaryLog.write("See the first %d below.\n"
201 % (NumOfFailuresInSummary,))
202 # TODO: Add a line "See the results folder for more."
203
204 FailuresCopied = NumOfFailuresInSummary
205 Idx = 0
206 for FailLogPathI in glob.glob(SBOutputDir + "/*/failures/*.stderr.txt"):
207 if Idx >= NumOfFailuresInSummary:
208 break;
209 Idx += 1
210 SummaryLog.write("\n-- Error #%d -----------\n" % (Idx,));
211 FailLogI = open(FailLogPathI, "r");
212 try:
213 shutil.copyfileobj(FailLogI, SummaryLog);
214 finally:
215 FailLogI.close()
216 finally:
217 SummaryLog.close()
218
219 print "Error: Scan-build failed. See ", \
220 os.path.join(SBOutputDir, FailuresSummaryFileName)
221 sys.exit(-1)
222
223# Auxiliary object to discard stdout.
224class Discarder(object):
225 def write(self, text):
226 pass # do nothing
227
228# Compare the warnings produced by scan-build.
229def runCmpResults(Dir):
230 TBegin = time.time()
231
232 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName)
233 NewDir = os.path.join(Dir, SBOutputDirName)
234
235 # We have to go one level down the directory tree.
236 RefList = glob.glob(RefDir + "/*")
237 NewList = glob.glob(NewDir + "/*")
238 if len(RefList) == 0 or len(NewList) == 0:
239 return False
240 assert(len(RefList) == len(NewList))
241
242 # There might be more then one folder underneath - one per each scan-build
243 # command (Ex: one for configure and one for make).
244 if (len(RefList) > 1):
245 # Assume that the corresponding folders have the same names.
246 RefList.sort()
247 NewList.sort()
248
249 # Iterate and find the differences.
250 HaveDiffs = False
251 PairList = zip(RefList, NewList)
252 for P in PairList:
253 RefDir = P[0]
254 NewDir = P[1]
255
256 assert(RefDir != NewDir)
257 if Verbose == 1:
258 print " Comparing Results: %s %s" % (RefDir, NewDir)
259
260 DiffsPath = os.path.join(NewDir, DiffsSummaryFileName)
261 Opts = CmpRuns.CmpOptions(DiffsPath)
262 # Discard everything coming out of stdout (CmpRun produces a lot of them).
263 OLD_STDOUT = sys.stdout
264 sys.stdout = Discarder()
265 # Scan the results, delete empty plist files.
266 HaveDiffs = CmpRuns.cmpScanBuildResults(RefDir, NewDir, Opts, False)
267 sys.stdout = OLD_STDOUT
268 if HaveDiffs:
269 print "Warning: difference in diagnostics. See %s" % (DiffsPath,)
270 HaveDiffs=True
271
272 print "Diagnostic comparison complete (time: %.2f)." % (time.time()-TBegin)
273 return HaveDiffs
274
275def testProject(ID, IsReferenceBuild, Dir=None):
276 TBegin = time.time()
277
278 if Dir is None :
279 Dir = getProjectDir(ID)
280 if Verbose == 1:
281 print " Build directory: %s." % (Dir,)
282
283 # Set the build results directory.
284 if IsReferenceBuild == True :
285 SBOutputDir = os.path.join(Dir, SBOutputDirReferencePrefix + \
286 SBOutputDirName)
287 else :
288 SBOutputDir = os.path.join(Dir, SBOutputDirName)
289
Anna Zaks5fa3f132011-11-02 20:46:50 +0000290 buildProject(Dir, SBOutputDir, IsReferenceBuild)
Anna Zaks1b417162011-10-06 23:26:27 +0000291
292 checkBuild(SBOutputDir)
293
294 if IsReferenceBuild == False:
295 runCmpResults(Dir)
296
297 print "Completed tests for project %s (time: %.2f)." % \
298 (ID, (time.time()-TBegin))
299
300def testAll(IsReferenceBuild=False):
301 PMapFile = open(getProjectMapPath(), "rb")
302 try:
303 PMapReader = csv.reader(PMapFile)
304 for I in PMapReader:
305 print " --- Building project %s" % (I[0],)
306 testProject(I[0], IsReferenceBuild)
307 finally:
308 PMapFile.close()
309
310if __name__ == '__main__':
311 testAll()