blob: 21cf4524bc751d07db2d3a8d3bceee5f713ef8f9 [file] [log] [blame]
Anna Zaksf0c41162011-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
Ted Kremenek3a0678e2015-09-08 03:50:52 +00009Repository Directory will contain sources of the projects as well as the
10information on how to build them and the expected output.
Anna Zaksf0c41162011-10-06 23:26:27 +000011Repository Directory structure:
12 - ProjectMap file
13 - Historical Performance Data
14 - Project Dir1
15 - ReferenceOutput
16 - Project Dir2
17 - ReferenceOutput
18 ..
Gabor Horvathc3177f22015-07-08 18:39:31 +000019Note that the build tree must be inside the project dir.
Anna Zaksf0c41162011-10-06 23:26:27 +000020
21To test the build of the analyzer one would:
Ted Kremenek3a0678e2015-09-08 03:50:52 +000022 - Copy over a copy of the Repository Directory. (TODO: Prefer to ensure that
Anna Zaksf0c41162011-10-06 23:26:27 +000023 the build directory does not pollute the repository to min network traffic).
24 - Build all projects, until error. Produce logs to report errors.
Ted Kremenek3a0678e2015-09-08 03:50:52 +000025 - Compare results.
Anna Zaksf0c41162011-10-06 23:26:27 +000026
Ted Kremenek3a0678e2015-09-08 03:50:52 +000027The files which should be kept around for failure investigations:
Anna Zaksf0c41162011-10-06 23:26:27 +000028 RepositoryCopy/Project DirI/ScanBuildResults
Ted Kremenek3a0678e2015-09-08 03:50:52 +000029 RepositoryCopy/Project DirI/run_static_analyzer.log
30
31Assumptions (TODO: shouldn't need to assume these.):
Anna Zaksf0c41162011-10-06 23:26:27 +000032 The script is being run from the Repository Directory.
Anna Zaks42a44632011-11-02 20:46:50 +000033 The compiler for scan-build and scan-build are in the PATH.
Anna Zaksf0c41162011-10-06 23:26:27 +000034 export PATH=/Users/zaks/workspace/c2llvm/build/Release+Asserts/bin:$PATH
35
36For more logging, set the env variables:
37 zaks:TI zaks$ export CCC_ANALYZER_LOG=1
38 zaks:TI zaks$ export CCC_ANALYZER_VERBOSE=1
Ted Kremenek3a0678e2015-09-08 03:50:52 +000039
Gabor Horvathda32a862015-08-20 22:59:49 +000040The list of checkers tested are hardcoded in the Checkers variable.
41For testing additional checkers, use the SA_ADDITIONAL_CHECKERS environment
42variable. It should contain a comma separated list.
Anna Zaksf0c41162011-10-06 23:26:27 +000043"""
44import CmpRuns
45
46import os
47import csv
48import sys
49import glob
Ted Kremenekf9a539d2012-08-28 20:40:04 +000050import math
Anna Zaksf0c41162011-10-06 23:26:27 +000051import shutil
52import time
53import plistlib
Gabor Horvath93fde942015-06-30 15:31:17 +000054import argparse
Devin Coughlinbace0322015-09-14 21:22:24 +000055from subprocess import check_call, check_output, CalledProcessError
Anna Zaksf0c41162011-10-06 23:26:27 +000056
Ted Kremenek42c14422012-08-28 20:40:02 +000057#------------------------------------------------------------------------------
58# Helper functions.
59#------------------------------------------------------------------------------
Anna Zaksf0c41162011-10-06 23:26:27 +000060
Ted Kremenekf9a539d2012-08-28 20:40:04 +000061def detectCPUs():
62 """
63 Detects the number of CPUs on a system. Cribbed from pp.
64 """
65 # Linux, Unix and MacOS:
66 if hasattr(os, "sysconf"):
67 if os.sysconf_names.has_key("SC_NPROCESSORS_ONLN"):
68 # Linux & Unix:
69 ncpus = os.sysconf("SC_NPROCESSORS_ONLN")
70 if isinstance(ncpus, int) and ncpus > 0:
71 return ncpus
72 else: # OSX:
73 return int(capture(['sysctl', '-n', 'hw.ncpu']))
74 # Windows:
75 if os.environ.has_key("NUMBER_OF_PROCESSORS"):
76 ncpus = int(os.environ["NUMBER_OF_PROCESSORS"])
77 if ncpus > 0:
78 return ncpus
79 return 1 # Default
80
Ted Kremenek6cb20802012-08-28 20:20:52 +000081def which(command, paths = None):
82 """which(command, [paths]) - Look up the given command in the paths string
83 (or the PATH environment variable, if unspecified)."""
84
85 if paths is None:
86 paths = os.environ.get('PATH','')
87
88 # Check for absolute match first.
89 if os.path.exists(command):
90 return command
91
92 # Would be nice if Python had a lib function for this.
93 if not paths:
94 paths = os.defpath
95
96 # Get suffixes to search.
97 # On Cygwin, 'PATHEXT' may exist but it should not be used.
98 if os.pathsep == ';':
99 pathext = os.environ.get('PATHEXT', '').split(';')
100 else:
101 pathext = ['']
102
103 # Search the paths...
104 for path in paths.split(os.pathsep):
105 for ext in pathext:
106 p = os.path.join(path, command + ext)
107 if os.path.exists(p):
108 return p
109
110 return None
111
Anna Zaksde1f7f8b2012-01-10 18:10:25 +0000112# Make sure we flush the output after every print statement.
113class flushfile(object):
114 def __init__(self, f):
115 self.f = f
116 def write(self, x):
117 self.f.write(x)
118 self.f.flush()
119
120sys.stdout = flushfile(sys.stdout)
121
Anna Zaksf0c41162011-10-06 23:26:27 +0000122def getProjectMapPath():
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000123 ProjectMapPath = os.path.join(os.path.abspath(os.curdir),
Anna Zaksf0c41162011-10-06 23:26:27 +0000124 ProjectMapFile)
125 if not os.path.exists(ProjectMapPath):
126 print "Error: Cannot find the Project Map file " + ProjectMapPath +\
127 "\nRunning script for the wrong directory?"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000128 sys.exit(-1)
129 return ProjectMapPath
Anna Zaksf0c41162011-10-06 23:26:27 +0000130
131def getProjectDir(ID):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000132 return os.path.join(os.path.abspath(os.curdir), ID)
Anna Zaksf0c41162011-10-06 23:26:27 +0000133
Jordan Rose01ac5722012-06-01 16:24:38 +0000134def getSBOutputDirName(IsReferenceBuild) :
Anna Zaks4720a732011-11-05 05:20:48 +0000135 if IsReferenceBuild == True :
136 return SBOutputDirReferencePrefix + SBOutputDirName
137 else :
138 return SBOutputDirName
139
Ted Kremenek42c14422012-08-28 20:40:02 +0000140#------------------------------------------------------------------------------
141# Configuration setup.
142#------------------------------------------------------------------------------
143
144# Find Clang for static analysis.
145Clang = which("clang", os.environ['PATH'])
146if not Clang:
147 print "Error: cannot find 'clang' in PATH"
148 sys.exit(-1)
149
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000150# Number of jobs.
Jordan Rose88329242012-11-16 17:41:21 +0000151Jobs = int(math.ceil(detectCPUs() * 0.75))
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000152
Ted Kremenek42c14422012-08-28 20:40:02 +0000153# Project map stores info about all the "registered" projects.
154ProjectMapFile = "projectMap.csv"
155
156# Names of the project specific scripts.
157# The script that needs to be executed before the build can start.
158CleanupScript = "cleanup_run_static_analyzer.sh"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000159# This is a file containing commands for scan-build.
Ted Kremenek42c14422012-08-28 20:40:02 +0000160BuildScript = "run_static_analyzer.cmd"
161
162# The log file name.
163LogFolderName = "Logs"
164BuildLogName = "run_static_analyzer.log"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000165# Summary file - contains the summary of the failures. Ex: This info can be be
Ted Kremenek42c14422012-08-28 20:40:02 +0000166# displayed when buildbot detects a build failure.
167NumOfFailuresInSummary = 10
168FailuresSummaryFileName = "failures.txt"
169# Summary of the result diffs.
170DiffsSummaryFileName = "diffs.txt"
171
172# The scan-build result directory.
173SBOutputDirName = "ScanBuildResults"
174SBOutputDirReferencePrefix = "Ref"
175
176# The list of checkers used during analyzes.
Alp Tokerd4733632013-12-05 04:47:09 +0000177# Currently, consists of all the non-experimental checkers, plus a few alpha
Jordan Rose10ad0812013-04-05 17:55:07 +0000178# checkers we don't want to regress on.
Anna Zaks8a020312014-10-31 17:40:14 +0000179Checkers="alpha.unix.SimpleStream,alpha.security.taint,cplusplus.NewDeleteLeaks,core,cplusplus,deadcode,security,unix,osx"
Ted Kremenek42c14422012-08-28 20:40:02 +0000180
181Verbose = 1
182
183#------------------------------------------------------------------------------
184# Test harness logic.
185#------------------------------------------------------------------------------
186
Anna Zaksf0c41162011-10-06 23:26:27 +0000187# Run pre-processing script if any.
Anna Zaks42a44632011-11-02 20:46:50 +0000188def runCleanupScript(Dir, PBuildLogFile):
189 ScriptPath = os.path.join(Dir, CleanupScript)
Anna Zaksf0c41162011-10-06 23:26:27 +0000190 if os.path.exists(ScriptPath):
191 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000192 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000193 print " Executing: %s" % (ScriptPath,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000194 check_call("chmod +x %s" % ScriptPath, cwd = Dir,
Anna Zaksf0c41162011-10-06 23:26:27 +0000195 stderr=PBuildLogFile,
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000196 stdout=PBuildLogFile,
197 shell=True)
Anna Zaksf0c41162011-10-06 23:26:27 +0000198 check_call(ScriptPath, cwd = Dir, stderr=PBuildLogFile,
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000199 stdout=PBuildLogFile,
Anna Zaksf0c41162011-10-06 23:26:27 +0000200 shell=True)
201 except:
202 print "Error: The pre-processing step failed. See ", \
203 PBuildLogFile.name, " for details."
204 sys.exit(-1)
205
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000206# Build the project with scan-build by reading in the commands and
Anna Zaksf0c41162011-10-06 23:26:27 +0000207# prefixing them with the scan-build options.
208def runScanBuild(Dir, SBOutputDir, PBuildLogFile):
209 BuildScriptPath = os.path.join(Dir, BuildScript)
210 if not os.path.exists(BuildScriptPath):
211 print "Error: build script is not defined: %s" % BuildScriptPath
Ted Kremenek6cb20802012-08-28 20:20:52 +0000212 sys.exit(-1)
Devin Coughlinf3695c82015-09-16 01:52:32 +0000213
214 AllCheckers = Checkers
215 if os.environ.has_key('SA_ADDITIONAL_CHECKERS'):
216 AllCheckers = AllCheckers + ',' + os.environ['SA_ADDITIONAL_CHECKERS']
217
Ted Kremenek6cb20802012-08-28 20:20:52 +0000218 SBOptions = "--use-analyzer " + Clang + " "
219 SBOptions += "-plist-html -o " + SBOutputDir + " "
Devin Coughlinf3695c82015-09-16 01:52:32 +0000220 SBOptions += "-enable-checker " + AllCheckers + " "
Jordan Roseb18179d2013-01-24 23:07:59 +0000221 SBOptions += "--keep-empty "
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000222 # Always use ccc-analyze to ensure that we can locate the failures
Anna Zaks7b4f8a42013-05-31 02:31:09 +0000223 # directory.
224 SBOptions += "--override-compiler "
Anna Zaksf0c41162011-10-06 23:26:27 +0000225 try:
226 SBCommandFile = open(BuildScriptPath, "r")
227 SBPrefix = "scan-build " + SBOptions + " "
228 for Command in SBCommandFile:
Jordan Rose7bd91862013-09-06 16:12:41 +0000229 Command = Command.strip()
Gabor Horvath93fde942015-06-30 15:31:17 +0000230 if len(Command) == 0:
231 continue;
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000232 # If using 'make', auto imply a -jX argument
233 # to speed up analysis. xcodebuild will
234 # automatically use the maximum number of cores.
Jordan Rose64e4cf02012-11-26 19:59:57 +0000235 if (Command.startswith("make ") or Command == "make") and \
236 "-j" not in Command:
Jordan Rose88329242012-11-16 17:41:21 +0000237 Command += " -j%d" % Jobs
Anna Zaksf0c41162011-10-06 23:26:27 +0000238 SBCommand = SBPrefix + Command
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000239 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000240 print " Executing: %s" % (SBCommand,)
241 check_call(SBCommand, cwd = Dir, stderr=PBuildLogFile,
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000242 stdout=PBuildLogFile,
Anna Zaksf0c41162011-10-06 23:26:27 +0000243 shell=True)
244 except:
245 print "Error: scan-build failed. See ",PBuildLogFile.name,\
246 " for details."
Anna Zaks4720a732011-11-05 05:20:48 +0000247 raise
Anna Zaksf0c41162011-10-06 23:26:27 +0000248
Anna Zaks4720a732011-11-05 05:20:48 +0000249def hasNoExtension(FileName):
250 (Root, Ext) = os.path.splitext(FileName)
251 if ((Ext == "")) :
252 return True
253 return False
254
255def isValidSingleInputFile(FileName):
256 (Root, Ext) = os.path.splitext(FileName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000257 if ((Ext == ".i") | (Ext == ".ii") |
258 (Ext == ".c") | (Ext == ".cpp") |
Anna Zaks4720a732011-11-05 05:20:48 +0000259 (Ext == ".m") | (Ext == "")) :
260 return True
261 return False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000262
Devin Coughlinbace0322015-09-14 21:22:24 +0000263# Get the path to the SDK for the given SDK name. Returns None if
264# the path cannot be determined.
265def getSDKPath(SDKName):
266 if which("xcrun") is None:
267 return None
268
269 Cmd = "xcrun --sdk " + SDKName + " --show-sdk-path"
270 return check_output(Cmd, shell=True).rstrip()
271
Anna Zaks4720a732011-11-05 05:20:48 +0000272# Run analysis on a set of preprocessed files.
Anna Zaksa2f970b2012-09-06 23:30:27 +0000273def runAnalyzePreprocessed(Dir, SBOutputDir, Mode):
Anna Zaks4720a732011-11-05 05:20:48 +0000274 if os.path.exists(os.path.join(Dir, BuildScript)):
275 print "Error: The preprocessed files project should not contain %s" % \
276 BuildScript
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000277 raise Exception()
Anna Zaks4720a732011-11-05 05:20:48 +0000278
Devin Coughlinbace0322015-09-14 21:22:24 +0000279 CmdPrefix = Clang + " -cc1 "
280
281 # For now, we assume the preprocessed files should be analyzed
282 # with the OS X SDK.
283 SDKPath = getSDKPath("macosx")
284 if SDKPath is not None:
285 CmdPrefix += "-isysroot " + SDKPath + " "
286
287 CmdPrefix += "-analyze -analyzer-output=plist -w "
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000288 CmdPrefix += "-analyzer-checker=" + Checkers +" -fcxx-exceptions -fblocks "
289
Anna Zaksa2f970b2012-09-06 23:30:27 +0000290 if (Mode == 2) :
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000291 CmdPrefix += "-std=c++11 "
292
Anna Zaks4720a732011-11-05 05:20:48 +0000293 PlistPath = os.path.join(Dir, SBOutputDir, "date")
294 FailPath = os.path.join(PlistPath, "failures");
295 os.makedirs(FailPath);
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000296
Anna Zaks4720a732011-11-05 05:20:48 +0000297 for FullFileName in glob.glob(Dir + "/*"):
298 FileName = os.path.basename(FullFileName)
299 Failed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000300
Anna Zaks4720a732011-11-05 05:20:48 +0000301 # Only run the analyzes on supported files.
302 if (hasNoExtension(FileName)):
303 continue
304 if (isValidSingleInputFile(FileName) == False):
305 print "Error: Invalid single input file %s." % (FullFileName,)
306 raise Exception()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000307
Anna Zaks4720a732011-11-05 05:20:48 +0000308 # Build and call the analyzer command.
309 OutputOption = "-o " + os.path.join(PlistPath, FileName) + ".plist "
Gabor Horvath2ed94562015-07-02 19:20:46 +0000310 Command = CmdPrefix + OutputOption + FileName
Anna Zaks4720a732011-11-05 05:20:48 +0000311 LogFile = open(os.path.join(FailPath, FileName + ".stderr.txt"), "w+b")
312 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000313 if Verbose == 1:
Anna Zaks4720a732011-11-05 05:20:48 +0000314 print " Executing: %s" % (Command,)
315 check_call(Command, cwd = Dir, stderr=LogFile,
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000316 stdout=LogFile,
Anna Zaks4720a732011-11-05 05:20:48 +0000317 shell=True)
318 except CalledProcessError, e:
319 print "Error: Analyzes of %s failed. See %s for details." \
320 "Error code %d." % \
321 (FullFileName, LogFile.name, e.returncode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000322 Failed = True
Anna Zaks4720a732011-11-05 05:20:48 +0000323 finally:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000324 LogFile.close()
325
Anna Zaks4720a732011-11-05 05:20:48 +0000326 # If command did not fail, erase the log file.
327 if Failed == False:
328 os.remove(LogFile.name);
329
Anna Zaksa2f970b2012-09-06 23:30:27 +0000330def buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000331 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000332
Anna Zaks4720a732011-11-05 05:20:48 +0000333 BuildLogPath = os.path.join(SBOutputDir, LogFolderName, BuildLogName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000334 print "Log file: %s" % (BuildLogPath,)
Anna Zaks4720a732011-11-05 05:20:48 +0000335 print "Output directory: %s" %(SBOutputDir, )
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000336
Anna Zaksf0c41162011-10-06 23:26:27 +0000337 # Clean up the log file.
338 if (os.path.exists(BuildLogPath)) :
339 RmCommand = "rm " + BuildLogPath
340 if Verbose == 1:
Anna Zaks42a44632011-11-02 20:46:50 +0000341 print " Executing: %s" % (RmCommand,)
Anna Zaksf0c41162011-10-06 23:26:27 +0000342 check_call(RmCommand, shell=True)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000343
Anna Zaks4720a732011-11-05 05:20:48 +0000344 # Clean up scan build results.
345 if (os.path.exists(SBOutputDir)) :
346 RmCommand = "rm -r " + SBOutputDir
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000347 if Verbose == 1:
Anna Zaks4720a732011-11-05 05:20:48 +0000348 print " Executing: %s" % (RmCommand,)
349 check_call(RmCommand, shell=True)
350 assert(not os.path.exists(SBOutputDir))
351 os.makedirs(os.path.join(SBOutputDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000352
Anna Zaksf0c41162011-10-06 23:26:27 +0000353 # Open the log file.
354 PBuildLogFile = open(BuildLogPath, "wb+")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000355
Anna Zaks4720a732011-11-05 05:20:48 +0000356 # Build and analyze the project.
357 try:
Anna Zaks42a44632011-11-02 20:46:50 +0000358 runCleanupScript(Dir, PBuildLogFile)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000359
Anna Zaksa2f970b2012-09-06 23:30:27 +0000360 if (ProjectBuildMode == 1):
Anna Zaks4720a732011-11-05 05:20:48 +0000361 runScanBuild(Dir, SBOutputDir, PBuildLogFile)
362 else:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000363 runAnalyzePreprocessed(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000364
Anna Zaks4720a732011-11-05 05:20:48 +0000365 if IsReferenceBuild :
Anna Zaks42a44632011-11-02 20:46:50 +0000366 runCleanupScript(Dir, PBuildLogFile)
Gabor Horvathc3177f22015-07-08 18:39:31 +0000367
368 # Make the absolute paths relative in the reference results.
369 for (DirPath, Dirnames, Filenames) in os.walk(SBOutputDir):
370 for F in Filenames:
371 if (not F.endswith('plist')):
372 continue
373 Plist = os.path.join(DirPath, F)
374 Data = plistlib.readPlist(Plist)
375 Paths = [SourceFile[len(Dir)+1:] if SourceFile.startswith(Dir)\
376 else SourceFile for SourceFile in Data['files']]
377 Data['files'] = Paths
378 plistlib.writePlist(Data, Plist)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000379
Anna Zaksf0c41162011-10-06 23:26:27 +0000380 finally:
381 PBuildLogFile.close()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000382
Anna Zaksf0c41162011-10-06 23:26:27 +0000383 print "Build complete (time: %.2f). See the log for more details: %s" % \
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000384 ((time.time()-TBegin), BuildLogPath)
385
Anna Zaksf0c41162011-10-06 23:26:27 +0000386# A plist file is created for each call to the analyzer(each source file).
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000387# We are only interested on the once that have bug reports, so delete the rest.
Anna Zaksf0c41162011-10-06 23:26:27 +0000388def CleanUpEmptyPlists(SBOutputDir):
389 for F in glob.glob(SBOutputDir + "/*/*.plist"):
390 P = os.path.join(SBOutputDir, F)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000391
Anna Zaksf0c41162011-10-06 23:26:27 +0000392 Data = plistlib.readPlist(P)
393 # Delete empty reports.
394 if not Data['files']:
395 os.remove(P)
396 continue
397
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000398# Given the scan-build output directory, checks if the build failed
399# (by searching for the failures directories). If there are failures, it
400# creates a summary file in the output directory.
Anna Zaksf0c41162011-10-06 23:26:27 +0000401def checkBuild(SBOutputDir):
402 # Check if there are failures.
403 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt")
404 TotalFailed = len(Failures);
405 if TotalFailed == 0:
Jordan Rose9858b122012-08-31 00:36:30 +0000406 CleanUpEmptyPlists(SBOutputDir)
407 Plists = glob.glob(SBOutputDir + "/*/*.plist")
Alp Tokerd4733632013-12-05 04:47:09 +0000408 print "Number of bug reports (non-empty plist files) produced: %d" %\
Jordan Rose9858b122012-08-31 00:36:30 +0000409 len(Plists)
Anna Zaksf0c41162011-10-06 23:26:27 +0000410 return;
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000411
Anna Zaksf0c41162011-10-06 23:26:27 +0000412 # Create summary file to display when the build fails.
Anna Zaks4720a732011-11-05 05:20:48 +0000413 SummaryPath = os.path.join(SBOutputDir, LogFolderName, FailuresSummaryFileName)
Anna Zaksf0c41162011-10-06 23:26:27 +0000414 if (Verbose > 0):
Anna Zaks4720a732011-11-05 05:20:48 +0000415 print " Creating the failures summary file %s" % (SummaryPath,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000416
Anna Zaksf0c41162011-10-06 23:26:27 +0000417 SummaryLog = open(SummaryPath, "w+")
418 try:
419 SummaryLog.write("Total of %d failures discovered.\n" % (TotalFailed,))
420 if TotalFailed > NumOfFailuresInSummary:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000421 SummaryLog.write("See the first %d below.\n"
Anna Zaksf0c41162011-10-06 23:26:27 +0000422 % (NumOfFailuresInSummary,))
423 # TODO: Add a line "See the results folder for more."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000424
Anna Zaksf0c41162011-10-06 23:26:27 +0000425 FailuresCopied = NumOfFailuresInSummary
426 Idx = 0
Jordan Rosedc191a12012-06-01 16:24:43 +0000427 for FailLogPathI in Failures:
Anna Zaksf0c41162011-10-06 23:26:27 +0000428 if Idx >= NumOfFailuresInSummary:
429 break;
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000430 Idx += 1
Anna Zaksf0c41162011-10-06 23:26:27 +0000431 SummaryLog.write("\n-- Error #%d -----------\n" % (Idx,));
432 FailLogI = open(FailLogPathI, "r");
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000433 try:
Anna Zaksf0c41162011-10-06 23:26:27 +0000434 shutil.copyfileobj(FailLogI, SummaryLog);
435 finally:
436 FailLogI.close()
437 finally:
438 SummaryLog.close()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000439
Anna Zaks5acd9602012-01-04 23:53:50 +0000440 print "Error: analysis failed. See ", SummaryPath
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000441 sys.exit(-1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000442
443# Auxiliary object to discard stdout.
444class Discarder(object):
445 def write(self, text):
446 pass # do nothing
447
448# Compare the warnings produced by scan-build.
Gabor Horvath93fde942015-06-30 15:31:17 +0000449# Strictness defines the success criteria for the test:
450# 0 - success if there are no crashes or analyzer failure.
451# 1 - success if there are no difference in the number of reported bugs.
452# 2 - success if all the bug reports are identical.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000453def runCmpResults(Dir, Strictness = 0):
454 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000455
456 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName)
457 NewDir = os.path.join(Dir, SBOutputDirName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000458
Anna Zaksf0c41162011-10-06 23:26:27 +0000459 # We have to go one level down the directory tree.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000460 RefList = glob.glob(RefDir + "/*")
Anna Zaksf0c41162011-10-06 23:26:27 +0000461 NewList = glob.glob(NewDir + "/*")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000462
Jordan Rosec7b992e2013-06-10 19:34:30 +0000463 # Log folders are also located in the results dir, so ignore them.
464 RefLogDir = os.path.join(RefDir, LogFolderName)
465 if RefLogDir in RefList:
466 RefList.remove(RefLogDir)
Anna Zaks4720a732011-11-05 05:20:48 +0000467 NewList.remove(os.path.join(NewDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000468
Anna Zaksf0c41162011-10-06 23:26:27 +0000469 if len(RefList) == 0 or len(NewList) == 0:
470 return False
471 assert(len(RefList) == len(NewList))
472
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000473 # There might be more then one folder underneath - one per each scan-build
Anna Zaksf0c41162011-10-06 23:26:27 +0000474 # command (Ex: one for configure and one for make).
475 if (len(RefList) > 1):
476 # Assume that the corresponding folders have the same names.
477 RefList.sort()
478 NewList.sort()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000479
Anna Zaksf0c41162011-10-06 23:26:27 +0000480 # Iterate and find the differences.
Anna Zaks767d3562011-11-08 19:56:31 +0000481 NumDiffs = 0
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000482 PairList = zip(RefList, NewList)
483 for P in PairList:
484 RefDir = P[0]
Anna Zaksf0c41162011-10-06 23:26:27 +0000485 NewDir = P[1]
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000486
487 assert(RefDir != NewDir)
488 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000489 print " Comparing Results: %s %s" % (RefDir, NewDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000490
Anna Zaksf0c41162011-10-06 23:26:27 +0000491 DiffsPath = os.path.join(NewDir, DiffsSummaryFileName)
Gabor Horvathc3177f22015-07-08 18:39:31 +0000492 Opts = CmpRuns.CmpOptions(DiffsPath, "", Dir)
Anna Zaksf0c41162011-10-06 23:26:27 +0000493 # Discard everything coming out of stdout (CmpRun produces a lot of them).
494 OLD_STDOUT = sys.stdout
495 sys.stdout = Discarder()
496 # Scan the results, delete empty plist files.
Gabor Horvath93fde942015-06-30 15:31:17 +0000497 NumDiffs, ReportsInRef, ReportsInNew = \
498 CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts, False)
Anna Zaksf0c41162011-10-06 23:26:27 +0000499 sys.stdout = OLD_STDOUT
Anna Zaks767d3562011-11-08 19:56:31 +0000500 if (NumDiffs > 0) :
501 print "Warning: %r differences in diagnostics. See %s" % \
502 (NumDiffs, DiffsPath,)
Gabor Horvath93fde942015-06-30 15:31:17 +0000503 if Strictness >= 2 and NumDiffs > 0:
504 print "Error: Diffs found in strict mode (2)."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000505 sys.exit(-1)
Gabor Horvath93fde942015-06-30 15:31:17 +0000506 elif Strictness >= 1 and ReportsInRef != ReportsInNew:
507 print "Error: The number of results are different in strict mode (1)."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000508 sys.exit(-1)
509
510 print "Diagnostic comparison complete (time: %.2f)." % (time.time()-TBegin)
Anna Zaks767d3562011-11-08 19:56:31 +0000511 return (NumDiffs > 0)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000512
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000513def updateSVN(Mode, ProjectsMap):
514 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000515 ProjectsMap.seek(0)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000516 for I in csv.reader(ProjectsMap):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000517 ProjName = I[0]
Jordan Rose01ac5722012-06-01 16:24:38 +0000518 Path = os.path.join(ProjName, getSBOutputDirName(True))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000519
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000520 if Mode == "delete":
521 Command = "svn delete %s" % (Path,)
522 else:
523 Command = "svn add %s" % (Path,)
Anna Zaksf0c41162011-10-06 23:26:27 +0000524
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000525 if Verbose == 1:
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000526 print " Executing: %s" % (Command,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000527 check_call(Command, shell=True)
528
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000529 if Mode == "delete":
530 CommitCommand = "svn commit -m \"[analyzer tests] Remove " \
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000531 "reference results.\""
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000532 else:
533 CommitCommand = "svn commit -m \"[analyzer tests] Add new " \
534 "reference results.\""
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000535 if Verbose == 1:
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000536 print " Executing: %s" % (CommitCommand,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000537 check_call(CommitCommand, shell=True)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000538 except:
539 print "Error: SVN update failed."
540 sys.exit(-1)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000541
Gabor Horvath93fde942015-06-30 15:31:17 +0000542def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Dir=None, Strictness = 0):
Anna Zaks4720a732011-11-05 05:20:48 +0000543 print " \n\n--- Building project %s" % (ID,)
544
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000545 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000546
547 if Dir is None :
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000548 Dir = getProjectDir(ID)
549 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000550 print " Build directory: %s." % (Dir,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000551
Anna Zaksf0c41162011-10-06 23:26:27 +0000552 # Set the build results directory.
Jordan Rose01ac5722012-06-01 16:24:38 +0000553 RelOutputDir = getSBOutputDirName(IsReferenceBuild)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000554 SBOutputDir = os.path.join(Dir, RelOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000555
Anna Zaksa2f970b2012-09-06 23:30:27 +0000556 buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild)
Anna Zaksf0c41162011-10-06 23:26:27 +0000557
558 checkBuild(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000559
Jordan Rose9858b122012-08-31 00:36:30 +0000560 if IsReferenceBuild == False:
Gabor Horvath93fde942015-06-30 15:31:17 +0000561 runCmpResults(Dir, Strictness)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000562
Anna Zaksf0c41162011-10-06 23:26:27 +0000563 print "Completed tests for project %s (time: %.2f)." % \
564 (ID, (time.time()-TBegin))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000565
Gabor Horvath93fde942015-06-30 15:31:17 +0000566def testAll(IsReferenceBuild = False, UpdateSVN = False, Strictness = 0):
Anna Zaksf0c41162011-10-06 23:26:27 +0000567 PMapFile = open(getProjectMapPath(), "rb")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000568 try:
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000569 # Validate the input.
570 for I in csv.reader(PMapFile):
Anna Zaks4720a732011-11-05 05:20:48 +0000571 if (len(I) != 2) :
572 print "Error: Rows in the ProjectMapFile should have 3 entries."
573 raise Exception()
Anna Zaksa2f970b2012-09-06 23:30:27 +0000574 if (not ((I[1] == "0") | (I[1] == "1") | (I[1] == "2"))):
575 print "Error: Second entry in the ProjectMapFile should be 0" \
576 " (single file), 1 (project), or 2(single file c++11)."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000577 raise Exception()
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000578
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000579 # When we are regenerating the reference results, we might need to
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000580 # update svn. Remove reference results from SVN.
581 if UpdateSVN == True:
Jordan Rose01ac5722012-06-01 16:24:38 +0000582 assert(IsReferenceBuild == True);
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000583 updateSVN("delete", PMapFile);
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000584
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000585 # Test the projects.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000586 PMapFile.seek(0)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000587 for I in csv.reader(PMapFile):
Gabor Horvath93fde942015-06-30 15:31:17 +0000588 testProject(I[0], int(I[1]), IsReferenceBuild, None, Strictness)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000589
590 # Add reference results to SVN.
591 if UpdateSVN == True:
592 updateSVN("add", PMapFile);
593
Anna Zaks4720a732011-11-05 05:20:48 +0000594 except:
595 print "Error occurred. Premature termination."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000596 raise
Anna Zaksf0c41162011-10-06 23:26:27 +0000597 finally:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000598 PMapFile.close()
599
Anna Zaksf0c41162011-10-06 23:26:27 +0000600if __name__ == '__main__':
Gabor Horvath93fde942015-06-30 15:31:17 +0000601 # Parse command line arguments.
602 Parser = argparse.ArgumentParser(description='Test the Clang Static Analyzer.')
603 Parser.add_argument('--strictness', dest='strictness', type=int, default=0,
604 help='0 to fail on runtime errors, 1 to fail when the number\
605 of found bugs are different from the reference, 2 to \
606 fail on any difference from the reference. Default is 0.')
607 Parser.add_argument('-r', dest='regenerate', action='store_true', default=False,
608 help='Regenerate reference output.')
609 Parser.add_argument('-rs', dest='update_reference', action='store_true',
610 default=False, help='Regenerate reference output and update svn.')
611 Args = Parser.parse_args()
612
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000613 IsReference = False
614 UpdateSVN = False
Gabor Horvath93fde942015-06-30 15:31:17 +0000615 Strictness = Args.strictness
616 if Args.regenerate:
617 IsReference = True
618 elif Args.update_reference:
619 IsReference = True
620 UpdateSVN = True
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000621
Gabor Horvath93fde942015-06-30 15:31:17 +0000622 testAll(IsReference, UpdateSVN, Strictness)