blob: f2d2df6a67b3c86d4ff77eeac4117a38042390e6 [file] [log] [blame]
Anna Zaksf0c41162011-10-06 23:26:27 +00001#!/usr/bin/env python
2
3"""
4Static Analyzer qualification infrastructure.
5
George Karpenkova8076602017-10-02 17:59:12 +00006The goal is to test the analyzer against different projects,
7check for failures, compare results, and measure performance.
Anna Zaksf0c41162011-10-06 23:26:27 +00008
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
George Karpenkova8076602017-10-02 17:59:12 +000023 the build directory does not pollute the repository to min network
24 traffic).
Anna Zaksf0c41162011-10-06 23:26:27 +000025 - Build all projects, until error. Produce logs to report errors.
Ted Kremenek3a0678e2015-09-08 03:50:52 +000026 - Compare results.
Anna Zaksf0c41162011-10-06 23:26:27 +000027
Ted Kremenek3a0678e2015-09-08 03:50:52 +000028The files which should be kept around for failure investigations:
Anna Zaksf0c41162011-10-06 23:26:27 +000029 RepositoryCopy/Project DirI/ScanBuildResults
Ted Kremenek3a0678e2015-09-08 03:50:52 +000030 RepositoryCopy/Project DirI/run_static_analyzer.log
31
32Assumptions (TODO: shouldn't need to assume these.):
Anna Zaksf0c41162011-10-06 23:26:27 +000033 The script is being run from the Repository Directory.
Anna Zaks42a44632011-11-02 20:46:50 +000034 The compiler for scan-build and scan-build are in the PATH.
Anna Zaksf0c41162011-10-06 23:26:27 +000035 export PATH=/Users/zaks/workspace/c2llvm/build/Release+Asserts/bin:$PATH
36
37For more logging, set the env variables:
38 zaks:TI zaks$ export CCC_ANALYZER_LOG=1
39 zaks:TI zaks$ export CCC_ANALYZER_VERBOSE=1
Ted Kremenek3a0678e2015-09-08 03:50:52 +000040
Gabor Horvathda32a862015-08-20 22:59:49 +000041The list of checkers tested are hardcoded in the Checkers variable.
42For testing additional checkers, use the SA_ADDITIONAL_CHECKERS environment
43variable. It should contain a comma separated list.
Anna Zaksf0c41162011-10-06 23:26:27 +000044"""
45import CmpRuns
46
47import os
48import csv
49import sys
50import glob
Ted Kremenekf9a539d2012-08-28 20:40:04 +000051import math
Anna Zaksf0c41162011-10-06 23:26:27 +000052import shutil
53import time
54import plistlib
Gabor Horvath93fde942015-06-30 15:31:17 +000055import argparse
Devin Coughlinbace0322015-09-14 21:22:24 +000056from subprocess import check_call, check_output, CalledProcessError
George Karpenkov3abfc3b2017-09-22 01:41:16 +000057import multiprocessing
Anna Zaksf0c41162011-10-06 23:26:27 +000058
Ted Kremenek42c14422012-08-28 20:40:02 +000059#------------------------------------------------------------------------------
60# Helper functions.
61#------------------------------------------------------------------------------
Anna Zaksf0c41162011-10-06 23:26:27 +000062
Ted Kremenekf9a539d2012-08-28 20:40:04 +000063
George Karpenkova8076602017-10-02 17:59:12 +000064def which(command, paths=None):
65 """which(command, [paths]) - Look up the given command in the paths string
66 (or the PATH environment variable, if unspecified)."""
Ted Kremenek6cb20802012-08-28 20:20:52 +000067
George Karpenkova8076602017-10-02 17:59:12 +000068 if paths is None:
69 paths = os.environ.get('PATH', '')
Ted Kremenek6cb20802012-08-28 20:20:52 +000070
George Karpenkova8076602017-10-02 17:59:12 +000071 # Check for absolute match first.
72 if os.path.exists(command):
73 return command
Ted Kremenek6cb20802012-08-28 20:20:52 +000074
George Karpenkova8076602017-10-02 17:59:12 +000075 # Would be nice if Python had a lib function for this.
76 if not paths:
77 paths = os.defpath
Ted Kremenek6cb20802012-08-28 20:20:52 +000078
George Karpenkova8076602017-10-02 17:59:12 +000079 # Get suffixes to search.
80 # On Cygwin, 'PATHEXT' may exist but it should not be used.
81 if os.pathsep == ';':
82 pathext = os.environ.get('PATHEXT', '').split(';')
83 else:
84 pathext = ['']
Ted Kremenek6cb20802012-08-28 20:20:52 +000085
George Karpenkova8076602017-10-02 17:59:12 +000086 # Search the paths...
87 for path in paths.split(os.pathsep):
88 for ext in pathext:
89 p = os.path.join(path, command + ext)
90 if os.path.exists(p):
91 return p
Ted Kremenek6cb20802012-08-28 20:20:52 +000092
George Karpenkova8076602017-10-02 17:59:12 +000093 return None
Ted Kremenek6cb20802012-08-28 20:20:52 +000094
George Karpenkova8076602017-10-02 17:59:12 +000095
Anna Zaksde1f7f8b2012-01-10 18:10:25 +000096class flushfile(object):
George Karpenkova8076602017-10-02 17:59:12 +000097 """
98 Wrapper to flush the output after every print statement.
99 """
Anna Zaksde1f7f8b2012-01-10 18:10:25 +0000100 def __init__(self, f):
101 self.f = f
George Karpenkova8076602017-10-02 17:59:12 +0000102
Anna Zaksde1f7f8b2012-01-10 18:10:25 +0000103 def write(self, x):
104 self.f.write(x)
105 self.f.flush()
106
George Karpenkova8076602017-10-02 17:59:12 +0000107
Anna Zaksde1f7f8b2012-01-10 18:10:25 +0000108sys.stdout = flushfile(sys.stdout)
109
George Karpenkova8076602017-10-02 17:59:12 +0000110
Anna Zaksf0c41162011-10-06 23:26:27 +0000111def getProjectMapPath():
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000112 ProjectMapPath = os.path.join(os.path.abspath(os.curdir),
Anna Zaksf0c41162011-10-06 23:26:27 +0000113 ProjectMapFile)
114 if not os.path.exists(ProjectMapPath):
115 print "Error: Cannot find the Project Map file " + ProjectMapPath +\
George Karpenkova8076602017-10-02 17:59:12 +0000116 "\nRunning script for the wrong directory?"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000117 sys.exit(-1)
118 return ProjectMapPath
Anna Zaksf0c41162011-10-06 23:26:27 +0000119
George Karpenkova8076602017-10-02 17:59:12 +0000120
Anna Zaksf0c41162011-10-06 23:26:27 +0000121def getProjectDir(ID):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000122 return os.path.join(os.path.abspath(os.curdir), ID)
Anna Zaksf0c41162011-10-06 23:26:27 +0000123
George Karpenkova8076602017-10-02 17:59:12 +0000124
125def getSBOutputDirName(IsReferenceBuild):
126 if IsReferenceBuild:
Anna Zaks4720a732011-11-05 05:20:48 +0000127 return SBOutputDirReferencePrefix + SBOutputDirName
George Karpenkova8076602017-10-02 17:59:12 +0000128 else:
Anna Zaks4720a732011-11-05 05:20:48 +0000129 return SBOutputDirName
130
Ted Kremenek42c14422012-08-28 20:40:02 +0000131#------------------------------------------------------------------------------
132# Configuration setup.
133#------------------------------------------------------------------------------
134
George Karpenkova8076602017-10-02 17:59:12 +0000135
Ted Kremenek42c14422012-08-28 20:40:02 +0000136# Find Clang for static analysis.
George Karpenkovbe6c3292017-09-21 22:12:49 +0000137if 'CC' in os.environ:
138 Clang = os.environ['CC']
139else:
140 Clang = which("clang", os.environ['PATH'])
Ted Kremenek42c14422012-08-28 20:40:02 +0000141if not Clang:
142 print "Error: cannot find 'clang' in PATH"
143 sys.exit(-1)
144
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000145# Number of jobs.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000146Jobs = int(math.ceil(multiprocessing.cpu_count() * 0.75))
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000147
Ted Kremenek42c14422012-08-28 20:40:02 +0000148# Project map stores info about all the "registered" projects.
149ProjectMapFile = "projectMap.csv"
150
151# Names of the project specific scripts.
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000152# The script that downloads the project.
153DownloadScript = "download_project.sh"
Ted Kremenek42c14422012-08-28 20:40:02 +0000154# The script that needs to be executed before the build can start.
155CleanupScript = "cleanup_run_static_analyzer.sh"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000156# This is a file containing commands for scan-build.
Ted Kremenek42c14422012-08-28 20:40:02 +0000157BuildScript = "run_static_analyzer.cmd"
158
159# The log file name.
160LogFolderName = "Logs"
161BuildLogName = "run_static_analyzer.log"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000162# Summary file - contains the summary of the failures. Ex: This info can be be
Ted Kremenek42c14422012-08-28 20:40:02 +0000163# displayed when buildbot detects a build failure.
164NumOfFailuresInSummary = 10
165FailuresSummaryFileName = "failures.txt"
166# Summary of the result diffs.
167DiffsSummaryFileName = "diffs.txt"
168
169# The scan-build result directory.
170SBOutputDirName = "ScanBuildResults"
171SBOutputDirReferencePrefix = "Ref"
172
George Karpenkova8076602017-10-02 17:59:12 +0000173# The name of the directory storing the cached project source. If this
174# directory does not exist, the download script will be executed.
175# That script should create the "CachedSource" directory and download the
176# project source into it.
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000177CachedSourceDirName = "CachedSource"
178
179# The name of the directory containing the source code that will be analyzed.
180# Each time a project is analyzed, a fresh copy of its CachedSource directory
181# will be copied to the PatchedSource directory and then the local patches
182# in PatchfileName will be applied (if PatchfileName exists).
183PatchedSourceDirName = "PatchedSource"
184
185# The name of the patchfile specifying any changes that should be applied
186# to the CachedSource before analyzing.
187PatchfileName = "changes_for_analyzer.patch"
188
Ted Kremenek42c14422012-08-28 20:40:02 +0000189# The list of checkers used during analyzes.
Alp Tokerd4733632013-12-05 04:47:09 +0000190# Currently, consists of all the non-experimental checkers, plus a few alpha
Jordan Rose10ad0812013-04-05 17:55:07 +0000191# checkers we don't want to regress on.
George Karpenkova8076602017-10-02 17:59:12 +0000192Checkers = ",".join([
George Karpenkovaf76b4a2017-09-30 00:05:24 +0000193 "alpha.unix.SimpleStream",
194 "alpha.security.taint",
195 "cplusplus.NewDeleteLeaks",
196 "core",
197 "cplusplus",
198 "deadcode",
199 "security",
200 "unix",
201 "osx",
202 "nullability"
203])
Ted Kremenek42c14422012-08-28 20:40:02 +0000204
205Verbose = 1
206
207#------------------------------------------------------------------------------
208# Test harness logic.
209#------------------------------------------------------------------------------
210
George Karpenkova8076602017-10-02 17:59:12 +0000211
Anna Zaks42a44632011-11-02 20:46:50 +0000212def runCleanupScript(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000213 """
214 Run pre-processing script if any.
215 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000216 Cwd = os.path.join(Dir, PatchedSourceDirName)
Anna Zaks42a44632011-11-02 20:46:50 +0000217 ScriptPath = os.path.join(Dir, CleanupScript)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000218 runScript(ScriptPath, PBuildLogFile, Cwd)
219
George Karpenkova8076602017-10-02 17:59:12 +0000220
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000221def runDownloadScript(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000222 """
223 Run the script to download the project, if it exists.
224 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000225 ScriptPath = os.path.join(Dir, DownloadScript)
226 runScript(ScriptPath, PBuildLogFile, Dir)
227
George Karpenkova8076602017-10-02 17:59:12 +0000228
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000229def runScript(ScriptPath, PBuildLogFile, Cwd):
George Karpenkova8076602017-10-02 17:59:12 +0000230 """
231 Run the provided script if it exists.
232 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000233 if os.path.exists(ScriptPath):
234 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000235 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000236 print " Executing: %s" % (ScriptPath,)
George Karpenkova8076602017-10-02 17:59:12 +0000237 check_call("chmod +x '%s'" % ScriptPath, cwd=Cwd,
238 stderr=PBuildLogFile,
239 stdout=PBuildLogFile,
240 shell=True)
241 check_call("'%s'" % ScriptPath, cwd=Cwd,
242 stderr=PBuildLogFile,
243 stdout=PBuildLogFile,
244 shell=True)
Anna Zaksf0c41162011-10-06 23:26:27 +0000245 except:
George Karpenkova8076602017-10-02 17:59:12 +0000246 print "Error: Running %s failed. See %s for details." % (
247 ScriptPath, PBuildLogFile.name)
Anna Zaksf0c41162011-10-06 23:26:27 +0000248 sys.exit(-1)
249
George Karpenkova8076602017-10-02 17:59:12 +0000250
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000251def downloadAndPatch(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000252 """
253 Download the project and apply the local patchfile if it exists.
254 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000255 CachedSourceDirPath = os.path.join(Dir, CachedSourceDirName)
256
257 # If the we don't already have the cached source, run the project's
258 # download script to download it.
259 if not os.path.exists(CachedSourceDirPath):
George Karpenkova8076602017-10-02 17:59:12 +0000260 runDownloadScript(Dir, PBuildLogFile)
261 if not os.path.exists(CachedSourceDirPath):
262 print "Error: '%s' not found after download." % (
263 CachedSourceDirPath)
264 exit(-1)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000265
266 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
267
268 # Remove potentially stale patched source.
269 if os.path.exists(PatchedSourceDirPath):
270 shutil.rmtree(PatchedSourceDirPath)
271
272 # Copy the cached source and apply any patches to the copy.
273 shutil.copytree(CachedSourceDirPath, PatchedSourceDirPath, symlinks=True)
274 applyPatch(Dir, PBuildLogFile)
275
George Karpenkova8076602017-10-02 17:59:12 +0000276
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000277def applyPatch(Dir, PBuildLogFile):
278 PatchfilePath = os.path.join(Dir, PatchfileName)
279 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
280 if not os.path.exists(PatchfilePath):
281 print " No local patches."
282 return
283
284 print " Applying patch."
285 try:
Devin Coughlinab95cd22016-01-22 07:08:06 +0000286 check_call("patch -p1 < '%s'" % (PatchfilePath),
George Karpenkova8076602017-10-02 17:59:12 +0000287 cwd=PatchedSourceDirPath,
288 stderr=PBuildLogFile,
289 stdout=PBuildLogFile,
290 shell=True)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000291 except:
292 print "Error: Patch failed. See %s for details." % (PBuildLogFile.name)
293 sys.exit(-1)
294
George Karpenkova8076602017-10-02 17:59:12 +0000295
Anna Zaksf0c41162011-10-06 23:26:27 +0000296def runScanBuild(Dir, SBOutputDir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000297 """
298 Build the project with scan-build by reading in the commands and
299 prefixing them with the scan-build options.
300 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000301 BuildScriptPath = os.path.join(Dir, BuildScript)
302 if not os.path.exists(BuildScriptPath):
303 print "Error: build script is not defined: %s" % BuildScriptPath
Ted Kremenek6cb20802012-08-28 20:20:52 +0000304 sys.exit(-1)
Devin Coughlinf3695c82015-09-16 01:52:32 +0000305
306 AllCheckers = Checkers
George Karpenkova8076602017-10-02 17:59:12 +0000307 if 'SA_ADDITIONAL_CHECKERS' in os.environ:
Devin Coughlinf3695c82015-09-16 01:52:32 +0000308 AllCheckers = AllCheckers + ',' + os.environ['SA_ADDITIONAL_CHECKERS']
309
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000310 # Run scan-build from within the patched source directory.
311 SBCwd = os.path.join(Dir, PatchedSourceDirName)
312
George Karpenkova8076602017-10-02 17:59:12 +0000313 SBOptions = "--use-analyzer '%s' " % Clang
Devin Coughlin86f61a92016-01-22 18:45:22 +0000314 SBOptions += "-plist-html -o '%s' " % SBOutputDir
Devin Coughlinf3695c82015-09-16 01:52:32 +0000315 SBOptions += "-enable-checker " + AllCheckers + " "
Jordan Roseb18179d2013-01-24 23:07:59 +0000316 SBOptions += "--keep-empty "
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000317 # Always use ccc-analyze to ensure that we can locate the failures
Anna Zaks7b4f8a42013-05-31 02:31:09 +0000318 # directory.
319 SBOptions += "--override-compiler "
Anna Zaksf0c41162011-10-06 23:26:27 +0000320 try:
321 SBCommandFile = open(BuildScriptPath, "r")
322 SBPrefix = "scan-build " + SBOptions + " "
323 for Command in SBCommandFile:
Jordan Rose7bd91862013-09-06 16:12:41 +0000324 Command = Command.strip()
Gabor Horvath93fde942015-06-30 15:31:17 +0000325 if len(Command) == 0:
George Karpenkova8076602017-10-02 17:59:12 +0000326 continue
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000327 # If using 'make', auto imply a -jX argument
328 # to speed up analysis. xcodebuild will
329 # automatically use the maximum number of cores.
Jordan Rose64e4cf02012-11-26 19:59:57 +0000330 if (Command.startswith("make ") or Command == "make") and \
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000331 "-j" not in Command:
Jordan Rose88329242012-11-16 17:41:21 +0000332 Command += " -j%d" % Jobs
Anna Zaksf0c41162011-10-06 23:26:27 +0000333 SBCommand = SBPrefix + Command
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000334 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000335 print " Executing: %s" % (SBCommand,)
George Karpenkova8076602017-10-02 17:59:12 +0000336 check_call(SBCommand, cwd=SBCwd,
337 stderr=PBuildLogFile,
338 stdout=PBuildLogFile,
339 shell=True)
Anna Zaksf0c41162011-10-06 23:26:27 +0000340 except:
George Karpenkova8076602017-10-02 17:59:12 +0000341 print "Error: scan-build failed. See ", PBuildLogFile.name,\
Anna Zaksf0c41162011-10-06 23:26:27 +0000342 " for details."
Anna Zaks4720a732011-11-05 05:20:48 +0000343 raise
Anna Zaksf0c41162011-10-06 23:26:27 +0000344
George Karpenkova8076602017-10-02 17:59:12 +0000345
Anna Zaks4720a732011-11-05 05:20:48 +0000346def hasNoExtension(FileName):
347 (Root, Ext) = os.path.splitext(FileName)
George Karpenkova8076602017-10-02 17:59:12 +0000348 return (Ext == "")
349
Anna Zaks4720a732011-11-05 05:20:48 +0000350
351def isValidSingleInputFile(FileName):
352 (Root, Ext) = os.path.splitext(FileName)
George Karpenkova8076602017-10-02 17:59:12 +0000353 return Ext in (".i", ".ii", ".c", ".cpp", ".m", "")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000354
George Karpenkova8076602017-10-02 17:59:12 +0000355
Devin Coughlinbace0322015-09-14 21:22:24 +0000356def getSDKPath(SDKName):
George Karpenkova8076602017-10-02 17:59:12 +0000357 """
358 Get the path to the SDK for the given SDK name. Returns None if
359 the path cannot be determined.
360 """
Devin Coughlinbace0322015-09-14 21:22:24 +0000361 if which("xcrun") is None:
362 return None
363
364 Cmd = "xcrun --sdk " + SDKName + " --show-sdk-path"
365 return check_output(Cmd, shell=True).rstrip()
366
George Karpenkova8076602017-10-02 17:59:12 +0000367
Anna Zaksa2f970b2012-09-06 23:30:27 +0000368def runAnalyzePreprocessed(Dir, SBOutputDir, Mode):
George Karpenkova8076602017-10-02 17:59:12 +0000369 """
370 Run analysis on a set of preprocessed files.
371 """
Anna Zaks4720a732011-11-05 05:20:48 +0000372 if os.path.exists(os.path.join(Dir, BuildScript)):
373 print "Error: The preprocessed files project should not contain %s" % \
George Karpenkova8076602017-10-02 17:59:12 +0000374 BuildScript
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000375 raise Exception()
Anna Zaks4720a732011-11-05 05:20:48 +0000376
Devin Coughlinbace0322015-09-14 21:22:24 +0000377 CmdPrefix = Clang + " -cc1 "
378
379 # For now, we assume the preprocessed files should be analyzed
380 # with the OS X SDK.
381 SDKPath = getSDKPath("macosx")
382 if SDKPath is not None:
George Karpenkova8076602017-10-02 17:59:12 +0000383 CmdPrefix += "-isysroot " + SDKPath + " "
Devin Coughlinbace0322015-09-14 21:22:24 +0000384
385 CmdPrefix += "-analyze -analyzer-output=plist -w "
George Karpenkova8076602017-10-02 17:59:12 +0000386 CmdPrefix += "-analyzer-checker=" + Checkers
387 CmdPrefix += " -fcxx-exceptions -fblocks "
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000388
George Karpenkova8076602017-10-02 17:59:12 +0000389 if (Mode == 2):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000390 CmdPrefix += "-std=c++11 "
391
Anna Zaks4720a732011-11-05 05:20:48 +0000392 PlistPath = os.path.join(Dir, SBOutputDir, "date")
George Karpenkova8076602017-10-02 17:59:12 +0000393 FailPath = os.path.join(PlistPath, "failures")
394 os.makedirs(FailPath)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000395
Anna Zaks4720a732011-11-05 05:20:48 +0000396 for FullFileName in glob.glob(Dir + "/*"):
397 FileName = os.path.basename(FullFileName)
398 Failed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000399
Anna Zaks4720a732011-11-05 05:20:48 +0000400 # Only run the analyzes on supported files.
401 if (hasNoExtension(FileName)):
402 continue
George Karpenkova8076602017-10-02 17:59:12 +0000403 if (not isValidSingleInputFile(FileName)):
Anna Zaks4720a732011-11-05 05:20:48 +0000404 print "Error: Invalid single input file %s." % (FullFileName,)
405 raise Exception()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000406
Anna Zaks4720a732011-11-05 05:20:48 +0000407 # Build and call the analyzer command.
Devin Coughlinab95cd22016-01-22 07:08:06 +0000408 OutputOption = "-o '%s.plist' " % os.path.join(PlistPath, FileName)
409 Command = CmdPrefix + OutputOption + ("'%s'" % FileName)
Anna Zaks4720a732011-11-05 05:20:48 +0000410 LogFile = open(os.path.join(FailPath, FileName + ".stderr.txt"), "w+b")
411 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000412 if Verbose == 1:
Anna Zaks4720a732011-11-05 05:20:48 +0000413 print " Executing: %s" % (Command,)
George Karpenkova8076602017-10-02 17:59:12 +0000414 check_call(Command, cwd=Dir, stderr=LogFile,
415 stdout=LogFile,
416 shell=True)
Anna Zaks4720a732011-11-05 05:20:48 +0000417 except CalledProcessError, e:
418 print "Error: Analyzes of %s failed. See %s for details." \
George Karpenkova8076602017-10-02 17:59:12 +0000419 "Error code %d." % (
420 FullFileName, LogFile.name, e.returncode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000421 Failed = True
Anna Zaks4720a732011-11-05 05:20:48 +0000422 finally:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000423 LogFile.close()
424
Anna Zaks4720a732011-11-05 05:20:48 +0000425 # If command did not fail, erase the log file.
George Karpenkova8076602017-10-02 17:59:12 +0000426 if not Failed:
427 os.remove(LogFile.name)
428
Anna Zaks4720a732011-11-05 05:20:48 +0000429
Devin Coughlin9ea80332016-01-23 01:09:07 +0000430def getBuildLogPath(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000431 return os.path.join(SBOutputDir, LogFolderName, BuildLogName)
432
Devin Coughlin9ea80332016-01-23 01:09:07 +0000433
434def removeLogFile(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000435 BuildLogPath = getBuildLogPath(SBOutputDir)
436 # Clean up the log file.
437 if (os.path.exists(BuildLogPath)):
438 RmCommand = "rm '%s'" % BuildLogPath
439 if Verbose == 1:
440 print " Executing: %s" % (RmCommand,)
441 check_call(RmCommand, shell=True)
442
Devin Coughlin9ea80332016-01-23 01:09:07 +0000443
Anna Zaksa2f970b2012-09-06 23:30:27 +0000444def buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000445 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000446
Devin Coughlin9ea80332016-01-23 01:09:07 +0000447 BuildLogPath = getBuildLogPath(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000448 print "Log file: %s" % (BuildLogPath,)
George Karpenkova8076602017-10-02 17:59:12 +0000449 print "Output directory: %s" % (SBOutputDir, )
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000450
Devin Coughlin9ea80332016-01-23 01:09:07 +0000451 removeLogFile(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000452
Anna Zaks4720a732011-11-05 05:20:48 +0000453 # Clean up scan build results.
George Karpenkova8076602017-10-02 17:59:12 +0000454 if (os.path.exists(SBOutputDir)):
Devin Coughlinab95cd22016-01-22 07:08:06 +0000455 RmCommand = "rm -r '%s'" % SBOutputDir
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000456 if Verbose == 1:
Anna Zaks4720a732011-11-05 05:20:48 +0000457 print " Executing: %s" % (RmCommand,)
458 check_call(RmCommand, shell=True)
459 assert(not os.path.exists(SBOutputDir))
460 os.makedirs(os.path.join(SBOutputDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000461
Anna Zaks4720a732011-11-05 05:20:48 +0000462 # Build and analyze the project.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000463 with open(BuildLogPath, "wb+") as PBuildLogFile:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000464 if (ProjectBuildMode == 1):
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000465 downloadAndPatch(Dir, PBuildLogFile)
466 runCleanupScript(Dir, PBuildLogFile)
Anna Zaks4720a732011-11-05 05:20:48 +0000467 runScanBuild(Dir, SBOutputDir, PBuildLogFile)
468 else:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000469 runAnalyzePreprocessed(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000470
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000471 if IsReferenceBuild:
Anna Zaks42a44632011-11-02 20:46:50 +0000472 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000473 normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000474
Anna Zaksf0c41162011-10-06 23:26:27 +0000475 print "Build complete (time: %.2f). See the log for more details: %s" % \
George Karpenkova8076602017-10-02 17:59:12 +0000476 ((time.time() - TBegin), BuildLogPath)
477
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000478
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000479def normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode):
480 """
481 Make the absolute paths relative in the reference results.
482 """
483 for (DirPath, Dirnames, Filenames) in os.walk(SBOutputDir):
484 for F in Filenames:
485 if (not F.endswith('plist')):
486 continue
487 Plist = os.path.join(DirPath, F)
488 Data = plistlib.readPlist(Plist)
489 PathPrefix = Dir
490 if (ProjectBuildMode == 1):
491 PathPrefix = os.path.join(Dir, PatchedSourceDirName)
George Karpenkova8076602017-10-02 17:59:12 +0000492 Paths = [SourceFile[len(PathPrefix) + 1:]
493 if SourceFile.startswith(PathPrefix)
494 else SourceFile for SourceFile in Data['files']]
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000495 Data['files'] = Paths
496 plistlib.writePlist(Data, Plist)
497
George Karpenkova8076602017-10-02 17:59:12 +0000498
Anna Zaksf0c41162011-10-06 23:26:27 +0000499def CleanUpEmptyPlists(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000500 """
501 A plist file is created for each call to the analyzer(each source file).
502 We are only interested on the once that have bug reports,
503 so delete the rest.
504 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000505 for F in glob.glob(SBOutputDir + "/*/*.plist"):
506 P = os.path.join(SBOutputDir, F)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000507
Anna Zaksf0c41162011-10-06 23:26:27 +0000508 Data = plistlib.readPlist(P)
509 # Delete empty reports.
510 if not Data['files']:
511 os.remove(P)
512 continue
513
George Karpenkova8076602017-10-02 17:59:12 +0000514
Anna Zaksf0c41162011-10-06 23:26:27 +0000515def checkBuild(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000516 """
517 Given the scan-build output directory, checks if the build failed
518 (by searching for the failures directories). If there are failures, it
519 creates a summary file in the output directory.
520
521 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000522 # Check if there are failures.
523 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt")
George Karpenkova8076602017-10-02 17:59:12 +0000524 TotalFailed = len(Failures)
Anna Zaksf0c41162011-10-06 23:26:27 +0000525 if TotalFailed == 0:
Jordan Rose9858b122012-08-31 00:36:30 +0000526 CleanUpEmptyPlists(SBOutputDir)
527 Plists = glob.glob(SBOutputDir + "/*/*.plist")
Alp Tokerd4733632013-12-05 04:47:09 +0000528 print "Number of bug reports (non-empty plist files) produced: %d" %\
George Karpenkova8076602017-10-02 17:59:12 +0000529 len(Plists)
530 return
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000531
Anna Zaksf0c41162011-10-06 23:26:27 +0000532 # Create summary file to display when the build fails.
George Karpenkova8076602017-10-02 17:59:12 +0000533 SummaryPath = os.path.join(
534 SBOutputDir, LogFolderName, FailuresSummaryFileName)
Anna Zaksf0c41162011-10-06 23:26:27 +0000535 if (Verbose > 0):
Anna Zaks4720a732011-11-05 05:20:48 +0000536 print " Creating the failures summary file %s" % (SummaryPath,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000537
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000538 with open(SummaryPath, "w+") as SummaryLog:
Anna Zaksf0c41162011-10-06 23:26:27 +0000539 SummaryLog.write("Total of %d failures discovered.\n" % (TotalFailed,))
540 if TotalFailed > NumOfFailuresInSummary:
George Karpenkova8076602017-10-02 17:59:12 +0000541 SummaryLog.write("See the first %d below.\n" % (
542 NumOfFailuresInSummary,))
Anna Zaksf0c41162011-10-06 23:26:27 +0000543 # TODO: Add a line "See the results folder for more."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000544
Anna Zaksf0c41162011-10-06 23:26:27 +0000545 Idx = 0
Jordan Rosedc191a12012-06-01 16:24:43 +0000546 for FailLogPathI in Failures:
Anna Zaksf0c41162011-10-06 23:26:27 +0000547 if Idx >= NumOfFailuresInSummary:
George Karpenkova8076602017-10-02 17:59:12 +0000548 break
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000549 Idx += 1
George Karpenkova8076602017-10-02 17:59:12 +0000550 SummaryLog.write("\n-- Error #%d -----------\n" % (Idx,))
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000551 with open(FailLogPathI, "r") as FailLogI:
George Karpenkova8076602017-10-02 17:59:12 +0000552 shutil.copyfileobj(FailLogI, SummaryLog)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000553
Anna Zaks5acd9602012-01-04 23:53:50 +0000554 print "Error: analysis failed. See ", SummaryPath
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000555 sys.exit(-1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000556
Anna Zaksf0c41162011-10-06 23:26:27 +0000557
George Karpenkova8076602017-10-02 17:59:12 +0000558class Discarder(object):
559 """
560 Auxiliary object to discard stdout.
561 """
562 def write(self, text):
563 pass # do nothing
564
565
566def runCmpResults(Dir, Strictness=0):
567 """
568 Compare the warnings produced by scan-build.
569 Strictness defines the success criteria for the test:
570 0 - success if there are no crashes or analyzer failure.
571 1 - success if there are no difference in the number of reported bugs.
572 2 - success if all the bug reports are identical.
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000573
574 :return success: Whether tests pass according to the Strictness
575 criteria.
George Karpenkova8076602017-10-02 17:59:12 +0000576 """
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000577 TestsPassed = True
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000578 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000579
580 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName)
581 NewDir = os.path.join(Dir, SBOutputDirName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000582
Anna Zaksf0c41162011-10-06 23:26:27 +0000583 # We have to go one level down the directory tree.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000584 RefList = glob.glob(RefDir + "/*")
Anna Zaksf0c41162011-10-06 23:26:27 +0000585 NewList = glob.glob(NewDir + "/*")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000586
Jordan Rosec7b992e2013-06-10 19:34:30 +0000587 # Log folders are also located in the results dir, so ignore them.
588 RefLogDir = os.path.join(RefDir, LogFolderName)
589 if RefLogDir in RefList:
590 RefList.remove(RefLogDir)
Anna Zaks4720a732011-11-05 05:20:48 +0000591 NewList.remove(os.path.join(NewDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000592
Anna Zaksf0c41162011-10-06 23:26:27 +0000593 assert(len(RefList) == len(NewList))
594
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000595 # There might be more then one folder underneath - one per each scan-build
Anna Zaksf0c41162011-10-06 23:26:27 +0000596 # command (Ex: one for configure and one for make).
597 if (len(RefList) > 1):
598 # Assume that the corresponding folders have the same names.
599 RefList.sort()
600 NewList.sort()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000601
Anna Zaksf0c41162011-10-06 23:26:27 +0000602 # Iterate and find the differences.
Anna Zaks767d3562011-11-08 19:56:31 +0000603 NumDiffs = 0
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000604 PairList = zip(RefList, NewList)
605 for P in PairList:
606 RefDir = P[0]
Anna Zaksf0c41162011-10-06 23:26:27 +0000607 NewDir = P[1]
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000608
609 assert(RefDir != NewDir)
610 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000611 print " Comparing Results: %s %s" % (RefDir, NewDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000612
Anna Zaksf0c41162011-10-06 23:26:27 +0000613 DiffsPath = os.path.join(NewDir, DiffsSummaryFileName)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000614 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
615 Opts = CmpRuns.CmpOptions(DiffsPath, "", PatchedSourceDirPath)
George Karpenkova8076602017-10-02 17:59:12 +0000616 # Discard everything coming out of stdout
617 # (CmpRun produces a lot of them).
Anna Zaksf0c41162011-10-06 23:26:27 +0000618 OLD_STDOUT = sys.stdout
619 sys.stdout = Discarder()
620 # Scan the results, delete empty plist files.
Gabor Horvath93fde942015-06-30 15:31:17 +0000621 NumDiffs, ReportsInRef, ReportsInNew = \
622 CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts, False)
Anna Zaksf0c41162011-10-06 23:26:27 +0000623 sys.stdout = OLD_STDOUT
George Karpenkova8076602017-10-02 17:59:12 +0000624 if (NumDiffs > 0):
Anna Zaks767d3562011-11-08 19:56:31 +0000625 print "Warning: %r differences in diagnostics. See %s" % \
626 (NumDiffs, DiffsPath,)
Gabor Horvath93fde942015-06-30 15:31:17 +0000627 if Strictness >= 2 and NumDiffs > 0:
628 print "Error: Diffs found in strict mode (2)."
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000629 TestsPassed = False
Gabor Horvath93fde942015-06-30 15:31:17 +0000630 elif Strictness >= 1 and ReportsInRef != ReportsInNew:
George Karpenkova8076602017-10-02 17:59:12 +0000631 print "Error: The number of results are different in "\
632 "strict mode (1)."
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000633 TestsPassed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000634
George Karpenkova8076602017-10-02 17:59:12 +0000635 print "Diagnostic comparison complete (time: %.2f)." % (
636 time.time() - TBegin)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000637 return TestsPassed
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000638
George Karpenkova8076602017-10-02 17:59:12 +0000639
Devin Coughlin9ea80332016-01-23 01:09:07 +0000640def cleanupReferenceResults(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000641 """
642 Delete html, css, and js files from reference results. These can
643 include multiple copies of the benchmark source and so get very large.
644 """
Devin Coughlin9ea80332016-01-23 01:09:07 +0000645 Extensions = ["html", "css", "js"]
646 for E in Extensions:
647 for F in glob.glob("%s/*/*.%s" % (SBOutputDir, E)):
648 P = os.path.join(SBOutputDir, F)
649 RmCommand = "rm '%s'" % P
650 check_call(RmCommand, shell=True)
651
652 # Remove the log file. It leaks absolute path names.
653 removeLogFile(SBOutputDir)
654
George Karpenkova8076602017-10-02 17:59:12 +0000655
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000656def updateSVN(Mode, PMapFile):
657 """
658 svn delete or svn add (depending on `Mode`) all folders defined in the file
659 handler `PMapFile`.
660 Commit the result to SVN.
661 """
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000662 try:
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000663 for I in iterateOverProjects(PMapFile):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000664 ProjName = I[0]
Jordan Rose01ac5722012-06-01 16:24:38 +0000665 Path = os.path.join(ProjName, getSBOutputDirName(True))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000666
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000667 if Mode == "delete":
Devin Coughlinab95cd22016-01-22 07:08:06 +0000668 Command = "svn delete '%s'" % (Path,)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000669 else:
Devin Coughlinab95cd22016-01-22 07:08:06 +0000670 Command = "svn add '%s'" % (Path,)
Anna Zaksf0c41162011-10-06 23:26:27 +0000671
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000672 if Verbose == 1:
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000673 print " Executing: %s" % (Command,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000674 check_call(Command, shell=True)
675
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000676 if Mode == "delete":
677 CommitCommand = "svn commit -m \"[analyzer tests] Remove " \
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000678 "reference results.\""
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000679 else:
680 CommitCommand = "svn commit -m \"[analyzer tests] Add new " \
681 "reference results.\""
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000682 if Verbose == 1:
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000683 print " Executing: %s" % (CommitCommand,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000684 check_call(CommitCommand, shell=True)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000685 except:
686 print "Error: SVN update failed."
687 sys.exit(-1)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000688
George Karpenkova8076602017-10-02 17:59:12 +0000689
690def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Strictness=0):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000691 """
692 Test a given project.
693 :return TestsPassed: Whether tests have passed according
694 to the :param Strictness: criteria.
695 """
Anna Zaks4720a732011-11-05 05:20:48 +0000696 print " \n\n--- Building project %s" % (ID,)
697
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000698 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000699
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000700 Dir = getProjectDir(ID)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000701 if Verbose == 1:
Anna Zaksf0c41162011-10-06 23:26:27 +0000702 print " Build directory: %s." % (Dir,)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000703
Anna Zaksf0c41162011-10-06 23:26:27 +0000704 # Set the build results directory.
Jordan Rose01ac5722012-06-01 16:24:38 +0000705 RelOutputDir = getSBOutputDirName(IsReferenceBuild)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000706 SBOutputDir = os.path.join(Dir, RelOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000707
Anna Zaksa2f970b2012-09-06 23:30:27 +0000708 buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild)
Anna Zaksf0c41162011-10-06 23:26:27 +0000709
710 checkBuild(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000711
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000712 if IsReferenceBuild:
Devin Coughlin9ea80332016-01-23 01:09:07 +0000713 cleanupReferenceResults(SBOutputDir)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000714 TestsPassed = True
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000715 else:
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000716 TestsPassed = runCmpResults(Dir, Strictness)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000717
Anna Zaksf0c41162011-10-06 23:26:27 +0000718 print "Completed tests for project %s (time: %.2f)." % \
George Karpenkova8076602017-10-02 17:59:12 +0000719 (ID, (time.time() - TBegin))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000720 return TestsPassed
George Karpenkova8076602017-10-02 17:59:12 +0000721
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000722
Devin Coughlin0dfc6f02016-09-19 01:36:40 +0000723def isCommentCSVLine(Entries):
George Karpenkova8076602017-10-02 17:59:12 +0000724 """
725 Treat CSV lines starting with a '#' as a comment.
726 """
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000727 return len(Entries) > 0 and Entries[0].startswith("#")
Devin Coughlin0dfc6f02016-09-19 01:36:40 +0000728
George Karpenkova8076602017-10-02 17:59:12 +0000729
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000730def projectFileHandler():
731 return open(getProjectMapPath(), "rb")
732
George Karpenkova8076602017-10-02 17:59:12 +0000733
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000734def iterateOverProjects(PMapFile):
735 """
736 Iterate over all projects defined in the project file handler `PMapFile`
737 from the start.
738 """
739 PMapFile.seek(0)
George Karpenkova8076602017-10-02 17:59:12 +0000740 for I in csv.reader(PMapFile):
741 if (isCommentCSVLine(I)):
742 continue
743 yield I
744
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000745
746def validateProjectFile(PMapFile):
747 """
748 Validate project file.
749 """
750 for I in iterateOverProjects(PMapFile):
George Karpenkova8076602017-10-02 17:59:12 +0000751 if (len(I) != 2):
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000752 print "Error: Rows in the ProjectMapFile should have 2 entries."
753 raise Exception()
754 if (not ((I[1] == "0") | (I[1] == "1") | (I[1] == "2"))):
755 print "Error: Second entry in the ProjectMapFile should be 0" \
George Karpenkova8076602017-10-02 17:59:12 +0000756 " (single file), 1 (project), or 2(single file c++11)."
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000757 raise Exception()
758
George Karpenkova8076602017-10-02 17:59:12 +0000759
760def testAll(IsReferenceBuild=False, UpdateSVN=False, Strictness=0):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000761 TestsPassed = True
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000762 with projectFileHandler() as PMapFile:
763 validateProjectFile(PMapFile)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000764
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000765 # When we are regenerating the reference results, we might need to
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000766 # update svn. Remove reference results from SVN.
George Karpenkova8076602017-10-02 17:59:12 +0000767 if UpdateSVN:
768 assert(IsReferenceBuild)
769 updateSVN("delete", PMapFile)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000770
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000771 # Test the projects.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000772 for (ProjName, ProjBuildMode) in iterateOverProjects(PMapFile):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000773 TestsPassed &= testProject(
George Karpenkova8076602017-10-02 17:59:12 +0000774 ProjName, int(ProjBuildMode), IsReferenceBuild, Strictness)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000775
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000776 # Re-add reference results to SVN.
George Karpenkova8076602017-10-02 17:59:12 +0000777 if UpdateSVN:
778 updateSVN("add", PMapFile)
779
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000780
Anna Zaksf0c41162011-10-06 23:26:27 +0000781if __name__ == '__main__':
Gabor Horvath93fde942015-06-30 15:31:17 +0000782 # Parse command line arguments.
George Karpenkova8076602017-10-02 17:59:12 +0000783 Parser = argparse.ArgumentParser(
784 description='Test the Clang Static Analyzer.')
Gabor Horvath93fde942015-06-30 15:31:17 +0000785 Parser.add_argument('--strictness', dest='strictness', type=int, default=0,
George Karpenkova8076602017-10-02 17:59:12 +0000786 help='0 to fail on runtime errors, 1 to fail when the \
787 number of found bugs are different from the \
788 reference, 2 to fail on any difference from the \
789 reference. Default is 0.')
790 Parser.add_argument('-r', dest='regenerate', action='store_true',
791 default=False, help='Regenerate reference output.')
Gabor Horvath93fde942015-06-30 15:31:17 +0000792 Parser.add_argument('-rs', dest='update_reference', action='store_true',
George Karpenkova8076602017-10-02 17:59:12 +0000793 default=False,
794 help='Regenerate reference output and update svn.')
Gabor Horvath93fde942015-06-30 15:31:17 +0000795 Args = Parser.parse_args()
796
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000797 IsReference = False
798 UpdateSVN = False
Gabor Horvath93fde942015-06-30 15:31:17 +0000799 Strictness = Args.strictness
800 if Args.regenerate:
801 IsReference = True
802 elif Args.update_reference:
803 IsReference = True
804 UpdateSVN = True
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000805
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000806 TestsPassed = testAll(IsReference, UpdateSVN, Strictness)
807 if not TestsPassed:
808 sys.exit(-1)