blob: 2becaa07a7dc81805ab120203266ccf3f8850ea1 [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
George Karpenkovbf92c442017-10-24 23:52:48 +000046import SATestUtils
Anna Zaksf0c41162011-10-06 23:26:27 +000047
George Karpenkovf37d3a52018-02-08 21:22:42 +000048from subprocess import CalledProcessError, check_call
Gabor Horvath93fde942015-06-30 15:31:17 +000049import argparse
George Karpenkovf37d3a52018-02-08 21:22:42 +000050import csv
51import glob
52import logging
53import math
George Karpenkov3abfc3b2017-09-22 01:41:16 +000054import multiprocessing
George Karpenkovf37d3a52018-02-08 21:22:42 +000055import os
56import plistlib
57import shutil
58import sys
59import threading
60import time
61import Queue
Anna Zaksf0c41162011-10-06 23:26:27 +000062
Ted Kremenek42c14422012-08-28 20:40:02 +000063#------------------------------------------------------------------------------
64# Helper functions.
65#------------------------------------------------------------------------------
Anna Zaksf0c41162011-10-06 23:26:27 +000066
George Karpenkovf37d3a52018-02-08 21:22:42 +000067Local = threading.local()
68Local.stdout = sys.stdout
69Local.stderr = sys.stderr
70logging.basicConfig(
71 level=logging.DEBUG,
72 format='%(asctime)s:%(levelname)s:%(name)s: %(message)s')
Ted Kremenekf9a539d2012-08-28 20:40:04 +000073
George Karpenkovf37d3a52018-02-08 21:22:42 +000074class StreamToLogger(object):
75 def __init__(self, logger, log_level=logging.INFO):
76 self.logger = logger
77 self.log_level = log_level
78
79 def write(self, buf):
80 # Rstrip in order not to write an extra newline.
81 self.logger.log(self.log_level, buf.rstrip())
82
83 def flush(self):
84 pass
85
86 def fileno(self):
87 return 0
Anna Zaksde1f7f8b2012-01-10 18:10:25 +000088
George Karpenkova8076602017-10-02 17:59:12 +000089
Anna Zaksf0c41162011-10-06 23:26:27 +000090def getProjectMapPath():
Ted Kremenek3a0678e2015-09-08 03:50:52 +000091 ProjectMapPath = os.path.join(os.path.abspath(os.curdir),
Anna Zaksf0c41162011-10-06 23:26:27 +000092 ProjectMapFile)
93 if not os.path.exists(ProjectMapPath):
George Karpenkovf37d3a52018-02-08 21:22:42 +000094 Local.stdout.write("Error: Cannot find the Project Map file "
95 + ProjectMapPath
96 + "\nRunning script for the wrong directory?\n")
George Karpenkov65839bd2017-10-26 01:13:22 +000097 sys.exit(1)
Ted Kremenek3a0678e2015-09-08 03:50:52 +000098 return ProjectMapPath
Anna Zaksf0c41162011-10-06 23:26:27 +000099
George Karpenkova8076602017-10-02 17:59:12 +0000100
Anna Zaksf0c41162011-10-06 23:26:27 +0000101def getProjectDir(ID):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000102 return os.path.join(os.path.abspath(os.curdir), ID)
Anna Zaksf0c41162011-10-06 23:26:27 +0000103
George Karpenkova8076602017-10-02 17:59:12 +0000104
105def getSBOutputDirName(IsReferenceBuild):
106 if IsReferenceBuild:
Anna Zaks4720a732011-11-05 05:20:48 +0000107 return SBOutputDirReferencePrefix + SBOutputDirName
George Karpenkova8076602017-10-02 17:59:12 +0000108 else:
Anna Zaks4720a732011-11-05 05:20:48 +0000109 return SBOutputDirName
110
Ted Kremenek42c14422012-08-28 20:40:02 +0000111#------------------------------------------------------------------------------
112# Configuration setup.
113#------------------------------------------------------------------------------
114
George Karpenkova8076602017-10-02 17:59:12 +0000115
Ted Kremenek42c14422012-08-28 20:40:02 +0000116# Find Clang for static analysis.
George Karpenkovbe6c3292017-09-21 22:12:49 +0000117if 'CC' in os.environ:
118 Clang = os.environ['CC']
119else:
George Karpenkovbf92c442017-10-24 23:52:48 +0000120 Clang = SATestUtils.which("clang", os.environ['PATH'])
Ted Kremenek42c14422012-08-28 20:40:02 +0000121if not Clang:
122 print "Error: cannot find 'clang' in PATH"
George Karpenkov65839bd2017-10-26 01:13:22 +0000123 sys.exit(1)
Ted Kremenek42c14422012-08-28 20:40:02 +0000124
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000125# Number of jobs.
George Karpenkovf37d3a52018-02-08 21:22:42 +0000126MaxJobs = int(math.ceil(multiprocessing.cpu_count() * 0.75))
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000127
Ted Kremenek42c14422012-08-28 20:40:02 +0000128# Project map stores info about all the "registered" projects.
129ProjectMapFile = "projectMap.csv"
130
131# Names of the project specific scripts.
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000132# The script that downloads the project.
133DownloadScript = "download_project.sh"
Ted Kremenek42c14422012-08-28 20:40:02 +0000134# The script that needs to be executed before the build can start.
135CleanupScript = "cleanup_run_static_analyzer.sh"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000136# This is a file containing commands for scan-build.
Ted Kremenek42c14422012-08-28 20:40:02 +0000137BuildScript = "run_static_analyzer.cmd"
138
139# The log file name.
140LogFolderName = "Logs"
141BuildLogName = "run_static_analyzer.log"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000142# Summary file - contains the summary of the failures. Ex: This info can be be
Ted Kremenek42c14422012-08-28 20:40:02 +0000143# displayed when buildbot detects a build failure.
144NumOfFailuresInSummary = 10
145FailuresSummaryFileName = "failures.txt"
Ted Kremenek42c14422012-08-28 20:40:02 +0000146
147# The scan-build result directory.
148SBOutputDirName = "ScanBuildResults"
149SBOutputDirReferencePrefix = "Ref"
150
George Karpenkova8076602017-10-02 17:59:12 +0000151# The name of the directory storing the cached project source. If this
152# directory does not exist, the download script will be executed.
153# That script should create the "CachedSource" directory and download the
154# project source into it.
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000155CachedSourceDirName = "CachedSource"
156
157# The name of the directory containing the source code that will be analyzed.
158# Each time a project is analyzed, a fresh copy of its CachedSource directory
159# will be copied to the PatchedSource directory and then the local patches
160# in PatchfileName will be applied (if PatchfileName exists).
161PatchedSourceDirName = "PatchedSource"
162
163# The name of the patchfile specifying any changes that should be applied
164# to the CachedSource before analyzing.
165PatchfileName = "changes_for_analyzer.patch"
166
Ted Kremenek42c14422012-08-28 20:40:02 +0000167# The list of checkers used during analyzes.
Alp Tokerd4733632013-12-05 04:47:09 +0000168# Currently, consists of all the non-experimental checkers, plus a few alpha
Jordan Rose10ad0812013-04-05 17:55:07 +0000169# checkers we don't want to regress on.
George Karpenkova8076602017-10-02 17:59:12 +0000170Checkers = ",".join([
George Karpenkovaf76b4a2017-09-30 00:05:24 +0000171 "alpha.unix.SimpleStream",
172 "alpha.security.taint",
173 "cplusplus.NewDeleteLeaks",
174 "core",
175 "cplusplus",
176 "deadcode",
177 "security",
178 "unix",
179 "osx",
180 "nullability"
181])
Ted Kremenek42c14422012-08-28 20:40:02 +0000182
183Verbose = 1
184
185#------------------------------------------------------------------------------
186# Test harness logic.
187#------------------------------------------------------------------------------
188
George Karpenkova8076602017-10-02 17:59:12 +0000189
Anna Zaks42a44632011-11-02 20:46:50 +0000190def runCleanupScript(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000191 """
192 Run pre-processing script if any.
193 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000194 Cwd = os.path.join(Dir, PatchedSourceDirName)
Anna Zaks42a44632011-11-02 20:46:50 +0000195 ScriptPath = os.path.join(Dir, CleanupScript)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000196 SATestUtils.runScript(ScriptPath, PBuildLogFile, Cwd,
197 Stdout=Local.stdout, Stderr=Local.stderr)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000198
George Karpenkova8076602017-10-02 17:59:12 +0000199
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000200def runDownloadScript(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000201 """
202 Run the script to download the project, if it exists.
203 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000204 ScriptPath = os.path.join(Dir, DownloadScript)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000205 SATestUtils.runScript(ScriptPath, PBuildLogFile, Dir,
206 Stdout=Local.stdout, Stderr=Local.stderr)
Anna Zaksf0c41162011-10-06 23:26:27 +0000207
George Karpenkova8076602017-10-02 17:59:12 +0000208
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000209def downloadAndPatch(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000210 """
211 Download the project and apply the local patchfile if it exists.
212 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000213 CachedSourceDirPath = os.path.join(Dir, CachedSourceDirName)
214
215 # If the we don't already have the cached source, run the project's
216 # download script to download it.
217 if not os.path.exists(CachedSourceDirPath):
George Karpenkova8076602017-10-02 17:59:12 +0000218 runDownloadScript(Dir, PBuildLogFile)
219 if not os.path.exists(CachedSourceDirPath):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000220 Local.stderr.write("Error: '%s' not found after download.\n" % (
221 CachedSourceDirPath))
George Karpenkov65839bd2017-10-26 01:13:22 +0000222 exit(1)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000223
224 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
225
226 # Remove potentially stale patched source.
227 if os.path.exists(PatchedSourceDirPath):
228 shutil.rmtree(PatchedSourceDirPath)
229
230 # Copy the cached source and apply any patches to the copy.
231 shutil.copytree(CachedSourceDirPath, PatchedSourceDirPath, symlinks=True)
232 applyPatch(Dir, PBuildLogFile)
233
George Karpenkova8076602017-10-02 17:59:12 +0000234
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000235def applyPatch(Dir, PBuildLogFile):
236 PatchfilePath = os.path.join(Dir, PatchfileName)
237 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
238 if not os.path.exists(PatchfilePath):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000239 Local.stdout.write(" No local patches.\n")
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000240 return
241
George Karpenkovf37d3a52018-02-08 21:22:42 +0000242 Local.stdout.write(" Applying patch.\n")
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000243 try:
Devin Coughlinab95cd22016-01-22 07:08:06 +0000244 check_call("patch -p1 < '%s'" % (PatchfilePath),
George Karpenkova8076602017-10-02 17:59:12 +0000245 cwd=PatchedSourceDirPath,
246 stderr=PBuildLogFile,
247 stdout=PBuildLogFile,
248 shell=True)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000249 except:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000250 Local.stderr.write("Error: Patch failed. See %s for details.\n" % (
251 PBuildLogFile.name))
George Karpenkov65839bd2017-10-26 01:13:22 +0000252 sys.exit(1)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000253
George Karpenkova8076602017-10-02 17:59:12 +0000254
Anna Zaksf0c41162011-10-06 23:26:27 +0000255def runScanBuild(Dir, SBOutputDir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000256 """
257 Build the project with scan-build by reading in the commands and
258 prefixing them with the scan-build options.
259 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000260 BuildScriptPath = os.path.join(Dir, BuildScript)
261 if not os.path.exists(BuildScriptPath):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000262 Local.stderr.write(
263 "Error: build script is not defined: %s\n" % BuildScriptPath)
George Karpenkov65839bd2017-10-26 01:13:22 +0000264 sys.exit(1)
Devin Coughlinf3695c82015-09-16 01:52:32 +0000265
266 AllCheckers = Checkers
George Karpenkova8076602017-10-02 17:59:12 +0000267 if 'SA_ADDITIONAL_CHECKERS' in os.environ:
Devin Coughlinf3695c82015-09-16 01:52:32 +0000268 AllCheckers = AllCheckers + ',' + os.environ['SA_ADDITIONAL_CHECKERS']
269
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000270 # Run scan-build from within the patched source directory.
271 SBCwd = os.path.join(Dir, PatchedSourceDirName)
272
George Karpenkova8076602017-10-02 17:59:12 +0000273 SBOptions = "--use-analyzer '%s' " % Clang
Devin Coughlin86f61a92016-01-22 18:45:22 +0000274 SBOptions += "-plist-html -o '%s' " % SBOutputDir
Devin Coughlinf3695c82015-09-16 01:52:32 +0000275 SBOptions += "-enable-checker " + AllCheckers + " "
Jordan Roseb18179d2013-01-24 23:07:59 +0000276 SBOptions += "--keep-empty "
George Karpenkov9ed25ed2018-01-29 21:45:07 +0000277 SBOptions += "-analyzer-config 'stable-report-filename=true' "
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000278 # Always use ccc-analyze to ensure that we can locate the failures
Anna Zaks7b4f8a42013-05-31 02:31:09 +0000279 # directory.
280 SBOptions += "--override-compiler "
Anna Zaksf0c41162011-10-06 23:26:27 +0000281 try:
282 SBCommandFile = open(BuildScriptPath, "r")
283 SBPrefix = "scan-build " + SBOptions + " "
284 for Command in SBCommandFile:
Jordan Rose7bd91862013-09-06 16:12:41 +0000285 Command = Command.strip()
Gabor Horvath93fde942015-06-30 15:31:17 +0000286 if len(Command) == 0:
George Karpenkova8076602017-10-02 17:59:12 +0000287 continue
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000288 # If using 'make', auto imply a -jX argument
289 # to speed up analysis. xcodebuild will
290 # automatically use the maximum number of cores.
Jordan Rose64e4cf02012-11-26 19:59:57 +0000291 if (Command.startswith("make ") or Command == "make") and \
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000292 "-j" not in Command:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000293 Command += " -j%d" % MaxJobs
Anna Zaksf0c41162011-10-06 23:26:27 +0000294 SBCommand = SBPrefix + Command
George Karpenkovf37d3a52018-02-08 21:22:42 +0000295
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000296 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000297 Local.stdout.write(" Executing: %s\n" % (SBCommand,))
George Karpenkova8076602017-10-02 17:59:12 +0000298 check_call(SBCommand, cwd=SBCwd,
299 stderr=PBuildLogFile,
300 stdout=PBuildLogFile,
301 shell=True)
George Karpenkove58044d2017-10-27 22:39:54 +0000302 except CalledProcessError:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000303 Local.stderr.write("Error: scan-build failed. Its output was: \n")
George Karpenkove58044d2017-10-27 22:39:54 +0000304 PBuildLogFile.seek(0)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000305 shutil.copyfileobj(PBuildLogFile, Local.stderr)
George Karpenkove58044d2017-10-27 22:39:54 +0000306 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000307
George Karpenkova8076602017-10-02 17:59:12 +0000308
Anna Zaksa2f970b2012-09-06 23:30:27 +0000309def runAnalyzePreprocessed(Dir, SBOutputDir, Mode):
George Karpenkova8076602017-10-02 17:59:12 +0000310 """
311 Run analysis on a set of preprocessed files.
312 """
Anna Zaks4720a732011-11-05 05:20:48 +0000313 if os.path.exists(os.path.join(Dir, BuildScript)):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000314 Local.stderr.write(
315 "Error: The preprocessed files project should not contain %s\n" % (
316 BuildScript))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000317 raise Exception()
Anna Zaks4720a732011-11-05 05:20:48 +0000318
Devin Coughlinbace0322015-09-14 21:22:24 +0000319 CmdPrefix = Clang + " -cc1 "
320
321 # For now, we assume the preprocessed files should be analyzed
322 # with the OS X SDK.
George Karpenkovbf92c442017-10-24 23:52:48 +0000323 SDKPath = SATestUtils.getSDKPath("macosx")
Devin Coughlinbace0322015-09-14 21:22:24 +0000324 if SDKPath is not None:
George Karpenkova8076602017-10-02 17:59:12 +0000325 CmdPrefix += "-isysroot " + SDKPath + " "
Devin Coughlinbace0322015-09-14 21:22:24 +0000326
327 CmdPrefix += "-analyze -analyzer-output=plist -w "
George Karpenkova8076602017-10-02 17:59:12 +0000328 CmdPrefix += "-analyzer-checker=" + Checkers
329 CmdPrefix += " -fcxx-exceptions -fblocks "
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000330
George Karpenkova8076602017-10-02 17:59:12 +0000331 if (Mode == 2):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000332 CmdPrefix += "-std=c++11 "
333
Anna Zaks4720a732011-11-05 05:20:48 +0000334 PlistPath = os.path.join(Dir, SBOutputDir, "date")
George Karpenkova8076602017-10-02 17:59:12 +0000335 FailPath = os.path.join(PlistPath, "failures")
336 os.makedirs(FailPath)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000337
Anna Zaks4720a732011-11-05 05:20:48 +0000338 for FullFileName in glob.glob(Dir + "/*"):
339 FileName = os.path.basename(FullFileName)
340 Failed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000341
Anna Zaks4720a732011-11-05 05:20:48 +0000342 # Only run the analyzes on supported files.
George Karpenkovbf92c442017-10-24 23:52:48 +0000343 if SATestUtils.hasNoExtension(FileName):
Anna Zaks4720a732011-11-05 05:20:48 +0000344 continue
George Karpenkovbf92c442017-10-24 23:52:48 +0000345 if not SATestUtils.isValidSingleInputFile(FileName):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000346 Local.stderr.write(
347 "Error: Invalid single input file %s.\n" % (FullFileName,))
Anna Zaks4720a732011-11-05 05:20:48 +0000348 raise Exception()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000349
Anna Zaks4720a732011-11-05 05:20:48 +0000350 # Build and call the analyzer command.
Devin Coughlinab95cd22016-01-22 07:08:06 +0000351 OutputOption = "-o '%s.plist' " % os.path.join(PlistPath, FileName)
352 Command = CmdPrefix + OutputOption + ("'%s'" % FileName)
Anna Zaks4720a732011-11-05 05:20:48 +0000353 LogFile = open(os.path.join(FailPath, FileName + ".stderr.txt"), "w+b")
354 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000355 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000356 Local.stdout.write(" Executing: %s\n" % (Command,))
George Karpenkova8076602017-10-02 17:59:12 +0000357 check_call(Command, cwd=Dir, stderr=LogFile,
358 stdout=LogFile,
359 shell=True)
Anna Zaks4720a732011-11-05 05:20:48 +0000360 except CalledProcessError, e:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000361 Local.stderr.write("Error: Analyzes of %s failed. "
362 "See %s for details."
363 "Error code %d.\n" % (
364 FullFileName, LogFile.name, e.returncode))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000365 Failed = True
Anna Zaks4720a732011-11-05 05:20:48 +0000366 finally:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000367 LogFile.close()
368
Anna Zaks4720a732011-11-05 05:20:48 +0000369 # If command did not fail, erase the log file.
George Karpenkova8076602017-10-02 17:59:12 +0000370 if not Failed:
371 os.remove(LogFile.name)
372
Anna Zaks4720a732011-11-05 05:20:48 +0000373
Devin Coughlin9ea80332016-01-23 01:09:07 +0000374def getBuildLogPath(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000375 return os.path.join(SBOutputDir, LogFolderName, BuildLogName)
376
Devin Coughlin9ea80332016-01-23 01:09:07 +0000377
378def removeLogFile(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000379 BuildLogPath = getBuildLogPath(SBOutputDir)
380 # Clean up the log file.
381 if (os.path.exists(BuildLogPath)):
382 RmCommand = "rm '%s'" % BuildLogPath
383 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000384 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
George Karpenkova8076602017-10-02 17:59:12 +0000385 check_call(RmCommand, shell=True)
386
Devin Coughlin9ea80332016-01-23 01:09:07 +0000387
Anna Zaksa2f970b2012-09-06 23:30:27 +0000388def buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000389 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000390
Devin Coughlin9ea80332016-01-23 01:09:07 +0000391 BuildLogPath = getBuildLogPath(SBOutputDir)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000392 Local.stdout.write("Log file: %s\n" % (BuildLogPath,))
393 Local.stdout.write("Output directory: %s\n" % (SBOutputDir, ))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000394
Devin Coughlin9ea80332016-01-23 01:09:07 +0000395 removeLogFile(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000396
Anna Zaks4720a732011-11-05 05:20:48 +0000397 # Clean up scan build results.
George Karpenkova8076602017-10-02 17:59:12 +0000398 if (os.path.exists(SBOutputDir)):
Devin Coughlinab95cd22016-01-22 07:08:06 +0000399 RmCommand = "rm -r '%s'" % SBOutputDir
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000400 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000401 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
402 check_call(RmCommand, shell=True, stdout=Local.stdout,
403 stderr=Local.stderr)
Anna Zaks4720a732011-11-05 05:20:48 +0000404 assert(not os.path.exists(SBOutputDir))
405 os.makedirs(os.path.join(SBOutputDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000406
Anna Zaks4720a732011-11-05 05:20:48 +0000407 # Build and analyze the project.
George Karpenkov0a6dba72017-10-27 22:52:36 +0000408 with open(BuildLogPath, "wb+") as PBuildLogFile:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000409 if (ProjectBuildMode == 1):
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000410 downloadAndPatch(Dir, PBuildLogFile)
411 runCleanupScript(Dir, PBuildLogFile)
Anna Zaks4720a732011-11-05 05:20:48 +0000412 runScanBuild(Dir, SBOutputDir, PBuildLogFile)
413 else:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000414 runAnalyzePreprocessed(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000415
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000416 if IsReferenceBuild:
Anna Zaks42a44632011-11-02 20:46:50 +0000417 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000418 normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000419
George Karpenkovf37d3a52018-02-08 21:22:42 +0000420 Local.stdout.write("Build complete (time: %.2f). "
421 "See the log for more details: %s\n" % (
422 (time.time() - TBegin), BuildLogPath))
George Karpenkova8076602017-10-02 17:59:12 +0000423
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000424
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000425def normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode):
426 """
427 Make the absolute paths relative in the reference results.
428 """
429 for (DirPath, Dirnames, Filenames) in os.walk(SBOutputDir):
430 for F in Filenames:
431 if (not F.endswith('plist')):
432 continue
433 Plist = os.path.join(DirPath, F)
434 Data = plistlib.readPlist(Plist)
435 PathPrefix = Dir
436 if (ProjectBuildMode == 1):
437 PathPrefix = os.path.join(Dir, PatchedSourceDirName)
George Karpenkova8076602017-10-02 17:59:12 +0000438 Paths = [SourceFile[len(PathPrefix) + 1:]
439 if SourceFile.startswith(PathPrefix)
440 else SourceFile for SourceFile in Data['files']]
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000441 Data['files'] = Paths
George Karpenkov318cd1f2017-10-24 23:52:46 +0000442
443 # Remove transient fields which change from run to run.
444 for Diag in Data['diagnostics']:
445 if 'HTMLDiagnostics_files' in Diag:
446 Diag.pop('HTMLDiagnostics_files')
447 if 'clang_version' in Data:
448 Data.pop('clang_version')
449
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000450 plistlib.writePlist(Data, Plist)
451
George Karpenkova8076602017-10-02 17:59:12 +0000452
Anna Zaksf0c41162011-10-06 23:26:27 +0000453def CleanUpEmptyPlists(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000454 """
455 A plist file is created for each call to the analyzer(each source file).
456 We are only interested on the once that have bug reports,
457 so delete the rest.
458 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000459 for F in glob.glob(SBOutputDir + "/*/*.plist"):
460 P = os.path.join(SBOutputDir, F)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000461
Anna Zaksf0c41162011-10-06 23:26:27 +0000462 Data = plistlib.readPlist(P)
463 # Delete empty reports.
464 if not Data['files']:
465 os.remove(P)
466 continue
467
George Karpenkova8076602017-10-02 17:59:12 +0000468
George Karpenkov3c128cb2017-10-30 19:40:33 +0000469def CleanUpEmptyFolders(SBOutputDir):
470 """
471 Remove empty folders from results, as git would not store them.
472 """
473 Subfolders = glob.glob(SBOutputDir + "/*")
474 for Folder in Subfolders:
475 if not os.listdir(Folder):
476 os.removedirs(Folder)
477
478
Anna Zaksf0c41162011-10-06 23:26:27 +0000479def checkBuild(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000480 """
481 Given the scan-build output directory, checks if the build failed
482 (by searching for the failures directories). If there are failures, it
483 creates a summary file in the output directory.
484
485 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000486 # Check if there are failures.
487 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt")
George Karpenkova8076602017-10-02 17:59:12 +0000488 TotalFailed = len(Failures)
Anna Zaksf0c41162011-10-06 23:26:27 +0000489 if TotalFailed == 0:
Jordan Rose9858b122012-08-31 00:36:30 +0000490 CleanUpEmptyPlists(SBOutputDir)
George Karpenkov3c128cb2017-10-30 19:40:33 +0000491 CleanUpEmptyFolders(SBOutputDir)
Jordan Rose9858b122012-08-31 00:36:30 +0000492 Plists = glob.glob(SBOutputDir + "/*/*.plist")
George Karpenkovf37d3a52018-02-08 21:22:42 +0000493 Local.stdout.write(
494 "Number of bug reports (non-empty plist files) produced: %d\n" %
495 len(Plists))
George Karpenkova8076602017-10-02 17:59:12 +0000496 return
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000497
George Karpenkovf37d3a52018-02-08 21:22:42 +0000498 Local.stderr.write("Error: analysis failed.\n")
499 Local.stderr.write("Total of %d failures discovered.\n" % TotalFailed)
George Karpenkovff555ce2017-10-26 19:00:22 +0000500 if TotalFailed > NumOfFailuresInSummary:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000501 Local.stderr.write(
502 "See the first %d below.\n" % NumOfFailuresInSummary)
Anna Zaksf0c41162011-10-06 23:26:27 +0000503 # TODO: Add a line "See the results folder for more."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000504
George Karpenkovff555ce2017-10-26 19:00:22 +0000505 Idx = 0
506 for FailLogPathI in Failures:
507 if Idx >= NumOfFailuresInSummary:
508 break
509 Idx += 1
George Karpenkovf37d3a52018-02-08 21:22:42 +0000510 Local.stderr.write("\n-- Error #%d -----------\n" % Idx)
George Karpenkovff555ce2017-10-26 19:00:22 +0000511 with open(FailLogPathI, "r") as FailLogI:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000512 shutil.copyfileobj(FailLogI, Local.stdout)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000513
George Karpenkov65839bd2017-10-26 01:13:22 +0000514 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000515
Anna Zaksf0c41162011-10-06 23:26:27 +0000516
George Karpenkova8076602017-10-02 17:59:12 +0000517def runCmpResults(Dir, Strictness=0):
518 """
519 Compare the warnings produced by scan-build.
520 Strictness defines the success criteria for the test:
521 0 - success if there are no crashes or analyzer failure.
522 1 - success if there are no difference in the number of reported bugs.
523 2 - success if all the bug reports are identical.
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000524
525 :return success: Whether tests pass according to the Strictness
526 criteria.
George Karpenkova8076602017-10-02 17:59:12 +0000527 """
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000528 TestsPassed = True
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000529 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000530
531 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName)
532 NewDir = os.path.join(Dir, SBOutputDirName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000533
Anna Zaksf0c41162011-10-06 23:26:27 +0000534 # We have to go one level down the directory tree.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000535 RefList = glob.glob(RefDir + "/*")
Anna Zaksf0c41162011-10-06 23:26:27 +0000536 NewList = glob.glob(NewDir + "/*")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000537
Jordan Rosec7b992e2013-06-10 19:34:30 +0000538 # Log folders are also located in the results dir, so ignore them.
539 RefLogDir = os.path.join(RefDir, LogFolderName)
540 if RefLogDir in RefList:
541 RefList.remove(RefLogDir)
Anna Zaks4720a732011-11-05 05:20:48 +0000542 NewList.remove(os.path.join(NewDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000543
George Karpenkov65839bd2017-10-26 01:13:22 +0000544 if len(RefList) != len(NewList):
545 print "Mismatch in number of results folders: %s vs %s" % (
546 RefList, NewList)
547 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000548
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000549 # There might be more then one folder underneath - one per each scan-build
Anna Zaksf0c41162011-10-06 23:26:27 +0000550 # command (Ex: one for configure and one for make).
551 if (len(RefList) > 1):
552 # Assume that the corresponding folders have the same names.
553 RefList.sort()
554 NewList.sort()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000555
Anna Zaksf0c41162011-10-06 23:26:27 +0000556 # Iterate and find the differences.
Anna Zaks767d3562011-11-08 19:56:31 +0000557 NumDiffs = 0
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000558 PairList = zip(RefList, NewList)
559 for P in PairList:
560 RefDir = P[0]
Anna Zaksf0c41162011-10-06 23:26:27 +0000561 NewDir = P[1]
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000562
563 assert(RefDir != NewDir)
564 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000565 Local.stdout.write(" Comparing Results: %s %s\n" % (
566 RefDir, NewDir))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000567
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000568 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
George Karpenkovfc782a32018-02-09 18:39:47 +0000569 Opts, Args = CmpRuns.generate_option_parser().parse_args(
570 ["", PatchedSourceDirPath])
Anna Zaksf0c41162011-10-06 23:26:27 +0000571 # Scan the results, delete empty plist files.
Gabor Horvath93fde942015-06-30 15:31:17 +0000572 NumDiffs, ReportsInRef, ReportsInNew = \
573 CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts, False)
George Karpenkova8076602017-10-02 17:59:12 +0000574 if (NumDiffs > 0):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000575 Local.stdout.write("Warning: %s differences in diagnostics.\n"
576 % NumDiffs)
Gabor Horvath93fde942015-06-30 15:31:17 +0000577 if Strictness >= 2 and NumDiffs > 0:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000578 Local.stdout.write("Error: Diffs found in strict mode (2).\n")
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000579 TestsPassed = False
Gabor Horvath93fde942015-06-30 15:31:17 +0000580 elif Strictness >= 1 and ReportsInRef != ReportsInNew:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000581 Local.stdout.write("Error: The number of results are different " +
582 " strict mode (1).\n")
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000583 TestsPassed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000584
George Karpenkovf37d3a52018-02-08 21:22:42 +0000585 Local.stdout.write("Diagnostic comparison complete (time: %.2f).\n" % (
586 time.time() - TBegin))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000587 return TestsPassed
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000588
George Karpenkova8076602017-10-02 17:59:12 +0000589
Devin Coughlin9ea80332016-01-23 01:09:07 +0000590def cleanupReferenceResults(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000591 """
592 Delete html, css, and js files from reference results. These can
593 include multiple copies of the benchmark source and so get very large.
594 """
Devin Coughlin9ea80332016-01-23 01:09:07 +0000595 Extensions = ["html", "css", "js"]
596 for E in Extensions:
597 for F in glob.glob("%s/*/*.%s" % (SBOutputDir, E)):
598 P = os.path.join(SBOutputDir, F)
599 RmCommand = "rm '%s'" % P
600 check_call(RmCommand, shell=True)
601
602 # Remove the log file. It leaks absolute path names.
603 removeLogFile(SBOutputDir)
604
George Karpenkova8076602017-10-02 17:59:12 +0000605
George Karpenkovf37d3a52018-02-08 21:22:42 +0000606class TestProjectThread(threading.Thread):
607 def __init__(self, TasksQueue, ResultsDiffer, FailureFlag):
608 """
609 :param ResultsDiffer: Used to signify that results differ from
610 the canonical ones.
611 :param FailureFlag: Used to signify a failure during the run.
612 """
613 self.TasksQueue = TasksQueue
614 self.ResultsDiffer = ResultsDiffer
615 self.FailureFlag = FailureFlag
616 super(TestProjectThread, self).__init__()
617
618 # Needed to gracefully handle interrupts with Ctrl-C
619 self.daemon = True
620
621 def run(self):
622 while not self.TasksQueue.empty():
623 try:
624 ProjArgs = self.TasksQueue.get()
625 Logger = logging.getLogger(ProjArgs[0])
626 Local.stdout = StreamToLogger(Logger, logging.INFO)
627 Local.stderr = StreamToLogger(Logger, logging.ERROR)
628 if not testProject(*ProjArgs):
629 self.ResultsDiffer.set()
630 self.TasksQueue.task_done()
631 except:
632 self.FailureFlag.set()
633 raise
634
635
George Karpenkova8076602017-10-02 17:59:12 +0000636def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Strictness=0):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000637 """
638 Test a given project.
639 :return TestsPassed: Whether tests have passed according
640 to the :param Strictness: criteria.
641 """
George Karpenkovf37d3a52018-02-08 21:22:42 +0000642 Local.stdout.write(" \n\n--- Building project %s\n" % (ID,))
Anna Zaks4720a732011-11-05 05:20:48 +0000643
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000644 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000645
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000646 Dir = getProjectDir(ID)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000647 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000648 Local.stdout.write(" Build directory: %s.\n" % (Dir,))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000649
Anna Zaksf0c41162011-10-06 23:26:27 +0000650 # Set the build results directory.
Jordan Rose01ac5722012-06-01 16:24:38 +0000651 RelOutputDir = getSBOutputDirName(IsReferenceBuild)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000652 SBOutputDir = os.path.join(Dir, RelOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000653
Anna Zaksa2f970b2012-09-06 23:30:27 +0000654 buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild)
Anna Zaksf0c41162011-10-06 23:26:27 +0000655
656 checkBuild(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000657
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000658 if IsReferenceBuild:
Devin Coughlin9ea80332016-01-23 01:09:07 +0000659 cleanupReferenceResults(SBOutputDir)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000660 TestsPassed = True
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000661 else:
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000662 TestsPassed = runCmpResults(Dir, Strictness)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000663
George Karpenkovf37d3a52018-02-08 21:22:42 +0000664 Local.stdout.write("Completed tests for project %s (time: %.2f).\n" % (
665 ID, (time.time() - TBegin)))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000666 return TestsPassed
George Karpenkova8076602017-10-02 17:59:12 +0000667
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000668
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000669def projectFileHandler():
670 return open(getProjectMapPath(), "rb")
671
George Karpenkova8076602017-10-02 17:59:12 +0000672
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000673def iterateOverProjects(PMapFile):
674 """
675 Iterate over all projects defined in the project file handler `PMapFile`
676 from the start.
677 """
678 PMapFile.seek(0)
George Karpenkova8076602017-10-02 17:59:12 +0000679 for I in csv.reader(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000680 if (SATestUtils.isCommentCSVLine(I)):
George Karpenkova8076602017-10-02 17:59:12 +0000681 continue
682 yield I
683
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000684
685def validateProjectFile(PMapFile):
686 """
687 Validate project file.
688 """
689 for I in iterateOverProjects(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000690 if len(I) != 2:
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000691 print "Error: Rows in the ProjectMapFile should have 2 entries."
692 raise Exception()
George Karpenkovbf92c442017-10-24 23:52:48 +0000693 if I[1] not in ('0', '1', '2'):
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000694 print "Error: Second entry in the ProjectMapFile should be 0" \
George Karpenkova8076602017-10-02 17:59:12 +0000695 " (single file), 1 (project), or 2(single file c++11)."
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000696 raise Exception()
697
George Karpenkovf37d3a52018-02-08 21:22:42 +0000698def singleThreadedTestAll(ProjectsToTest):
699 """
700 Run all projects.
701 :return: whether tests have passed.
702 """
703 Success = True
704 for ProjArgs in ProjectsToTest:
705 Success &= testProject(*ProjArgs)
706 return Success
George Karpenkova8076602017-10-02 17:59:12 +0000707
George Karpenkovf37d3a52018-02-08 21:22:42 +0000708def multiThreadedTestAll(ProjectsToTest, Jobs):
709 """
710 Run each project in a separate thread.
711
712 This is OK despite GIL, as testing is blocked
713 on launching external processes.
714
715 :return: whether tests have passed.
716 """
717 TasksQueue = Queue.Queue()
718
719 for ProjArgs in ProjectsToTest:
720 TasksQueue.put(ProjArgs)
721
722 ResultsDiffer = threading.Event()
723 FailureFlag = threading.Event()
724
725 for i in range(Jobs):
726 T = TestProjectThread(TasksQueue, ResultsDiffer, FailureFlag)
727 T.start()
728
729 # Required to handle Ctrl-C gracefully.
730 while TasksQueue.unfinished_tasks:
731 time.sleep(0.1) # Seconds.
732 if FailureFlag.is_set():
733 Local.stderr.write("Test runner crashed\n")
734 sys.exit(1)
735 return not ResultsDiffer.is_set()
736
737
738def testAll(Args):
739 ProjectsToTest = []
740
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000741 with projectFileHandler() as PMapFile:
742 validateProjectFile(PMapFile)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000743
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000744 # Test the projects.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000745 for (ProjName, ProjBuildMode) in iterateOverProjects(PMapFile):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000746 ProjectsToTest.append((ProjName,
747 int(ProjBuildMode),
748 Args.regenerate,
749 Args.strictness))
750 if Args.jobs <= 1:
751 return singleThreadedTestAll(ProjectsToTest)
752 else:
753 return multiThreadedTestAll(ProjectsToTest, Args.jobs)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000754
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000755
Anna Zaksf0c41162011-10-06 23:26:27 +0000756if __name__ == '__main__':
Gabor Horvath93fde942015-06-30 15:31:17 +0000757 # Parse command line arguments.
George Karpenkova8076602017-10-02 17:59:12 +0000758 Parser = argparse.ArgumentParser(
759 description='Test the Clang Static Analyzer.')
Gabor Horvath93fde942015-06-30 15:31:17 +0000760 Parser.add_argument('--strictness', dest='strictness', type=int, default=0,
George Karpenkova8076602017-10-02 17:59:12 +0000761 help='0 to fail on runtime errors, 1 to fail when the \
762 number of found bugs are different from the \
763 reference, 2 to fail on any difference from the \
764 reference. Default is 0.')
765 Parser.add_argument('-r', dest='regenerate', action='store_true',
766 default=False, help='Regenerate reference output.')
George Karpenkovf37d3a52018-02-08 21:22:42 +0000767 Parser.add_argument('-j', '--jobs', dest='jobs', type=int,
768 default=0,
769 help='Number of projects to test concurrently')
Gabor Horvath93fde942015-06-30 15:31:17 +0000770 Args = Parser.parse_args()
771
George Karpenkovf37d3a52018-02-08 21:22:42 +0000772 TestsPassed = testAll(Args)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000773 if not TestsPassed:
George Karpenkovbf92c442017-10-24 23:52:48 +0000774 print "ERROR: Tests failed."
George Karpenkov65839bd2017-10-26 01:13:22 +0000775 sys.exit(42)