blob: 3137b48a7870bf94d2804f64f07d83ecd98a894a [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
George Karpenkov71105812018-03-29 01:23:54 +0000183Verbose = 0
Ted Kremenek42c14422012-08-28 20:40:02 +0000184
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 Karpenkov30130b72018-06-26 23:17:35 +0000277 AnalyzerConfig = {
278 "stable-report-filename": "true",
279 "serialize-stats": "true"
280 }
281
282 SBOptions += "-analyzer-config '%s' " % (
Mikhail R. Gadelhaafc62b72018-06-27 14:39:41 +0000283 ",".join("%s=%s" % (key, value) for key, value in AnalyzerConfig.iteritems()))
284
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000285 # Always use ccc-analyze to ensure that we can locate the failures
Anna Zaks7b4f8a42013-05-31 02:31:09 +0000286 # directory.
287 SBOptions += "--override-compiler "
Anna Zaksf0c41162011-10-06 23:26:27 +0000288 try:
289 SBCommandFile = open(BuildScriptPath, "r")
290 SBPrefix = "scan-build " + SBOptions + " "
291 for Command in SBCommandFile:
Jordan Rose7bd91862013-09-06 16:12:41 +0000292 Command = Command.strip()
Gabor Horvath93fde942015-06-30 15:31:17 +0000293 if len(Command) == 0:
George Karpenkova8076602017-10-02 17:59:12 +0000294 continue
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000295 # If using 'make', auto imply a -jX argument
296 # to speed up analysis. xcodebuild will
297 # automatically use the maximum number of cores.
Jordan Rose64e4cf02012-11-26 19:59:57 +0000298 if (Command.startswith("make ") or Command == "make") and \
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000299 "-j" not in Command:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000300 Command += " -j%d" % MaxJobs
Anna Zaksf0c41162011-10-06 23:26:27 +0000301 SBCommand = SBPrefix + Command
George Karpenkovf37d3a52018-02-08 21:22:42 +0000302
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000303 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000304 Local.stdout.write(" Executing: %s\n" % (SBCommand,))
George Karpenkova8076602017-10-02 17:59:12 +0000305 check_call(SBCommand, cwd=SBCwd,
306 stderr=PBuildLogFile,
307 stdout=PBuildLogFile,
308 shell=True)
George Karpenkove58044d2017-10-27 22:39:54 +0000309 except CalledProcessError:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000310 Local.stderr.write("Error: scan-build failed. Its output was: \n")
George Karpenkove58044d2017-10-27 22:39:54 +0000311 PBuildLogFile.seek(0)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000312 shutil.copyfileobj(PBuildLogFile, Local.stderr)
George Karpenkove58044d2017-10-27 22:39:54 +0000313 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000314
George Karpenkova8076602017-10-02 17:59:12 +0000315
Anna Zaksa2f970b2012-09-06 23:30:27 +0000316def runAnalyzePreprocessed(Dir, SBOutputDir, Mode):
George Karpenkova8076602017-10-02 17:59:12 +0000317 """
318 Run analysis on a set of preprocessed files.
319 """
Anna Zaks4720a732011-11-05 05:20:48 +0000320 if os.path.exists(os.path.join(Dir, BuildScript)):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000321 Local.stderr.write(
322 "Error: The preprocessed files project should not contain %s\n" % (
323 BuildScript))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000324 raise Exception()
Anna Zaks4720a732011-11-05 05:20:48 +0000325
Devin Coughlinbace0322015-09-14 21:22:24 +0000326 CmdPrefix = Clang + " -cc1 "
327
328 # For now, we assume the preprocessed files should be analyzed
329 # with the OS X SDK.
George Karpenkovbf92c442017-10-24 23:52:48 +0000330 SDKPath = SATestUtils.getSDKPath("macosx")
Devin Coughlinbace0322015-09-14 21:22:24 +0000331 if SDKPath is not None:
George Karpenkova8076602017-10-02 17:59:12 +0000332 CmdPrefix += "-isysroot " + SDKPath + " "
Devin Coughlinbace0322015-09-14 21:22:24 +0000333
334 CmdPrefix += "-analyze -analyzer-output=plist -w "
George Karpenkova8076602017-10-02 17:59:12 +0000335 CmdPrefix += "-analyzer-checker=" + Checkers
336 CmdPrefix += " -fcxx-exceptions -fblocks "
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000337
George Karpenkova8076602017-10-02 17:59:12 +0000338 if (Mode == 2):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000339 CmdPrefix += "-std=c++11 "
340
Anna Zaks4720a732011-11-05 05:20:48 +0000341 PlistPath = os.path.join(Dir, SBOutputDir, "date")
George Karpenkova8076602017-10-02 17:59:12 +0000342 FailPath = os.path.join(PlistPath, "failures")
343 os.makedirs(FailPath)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000344
Anna Zaks4720a732011-11-05 05:20:48 +0000345 for FullFileName in glob.glob(Dir + "/*"):
346 FileName = os.path.basename(FullFileName)
347 Failed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000348
Anna Zaks4720a732011-11-05 05:20:48 +0000349 # Only run the analyzes on supported files.
George Karpenkovbf92c442017-10-24 23:52:48 +0000350 if SATestUtils.hasNoExtension(FileName):
Anna Zaks4720a732011-11-05 05:20:48 +0000351 continue
George Karpenkovbf92c442017-10-24 23:52:48 +0000352 if not SATestUtils.isValidSingleInputFile(FileName):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000353 Local.stderr.write(
354 "Error: Invalid single input file %s.\n" % (FullFileName,))
Anna Zaks4720a732011-11-05 05:20:48 +0000355 raise Exception()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000356
Anna Zaks4720a732011-11-05 05:20:48 +0000357 # Build and call the analyzer command.
Devin Coughlinab95cd22016-01-22 07:08:06 +0000358 OutputOption = "-o '%s.plist' " % os.path.join(PlistPath, FileName)
359 Command = CmdPrefix + OutputOption + ("'%s'" % FileName)
Anna Zaks4720a732011-11-05 05:20:48 +0000360 LogFile = open(os.path.join(FailPath, FileName + ".stderr.txt"), "w+b")
361 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000362 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000363 Local.stdout.write(" Executing: %s\n" % (Command,))
George Karpenkova8076602017-10-02 17:59:12 +0000364 check_call(Command, cwd=Dir, stderr=LogFile,
365 stdout=LogFile,
366 shell=True)
Anna Zaks4720a732011-11-05 05:20:48 +0000367 except CalledProcessError, e:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000368 Local.stderr.write("Error: Analyzes of %s failed. "
369 "See %s for details."
370 "Error code %d.\n" % (
371 FullFileName, LogFile.name, e.returncode))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000372 Failed = True
Anna Zaks4720a732011-11-05 05:20:48 +0000373 finally:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000374 LogFile.close()
375
Anna Zaks4720a732011-11-05 05:20:48 +0000376 # If command did not fail, erase the log file.
George Karpenkova8076602017-10-02 17:59:12 +0000377 if not Failed:
378 os.remove(LogFile.name)
379
Anna Zaks4720a732011-11-05 05:20:48 +0000380
Devin Coughlin9ea80332016-01-23 01:09:07 +0000381def getBuildLogPath(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000382 return os.path.join(SBOutputDir, LogFolderName, BuildLogName)
383
Devin Coughlin9ea80332016-01-23 01:09:07 +0000384
385def removeLogFile(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000386 BuildLogPath = getBuildLogPath(SBOutputDir)
387 # Clean up the log file.
388 if (os.path.exists(BuildLogPath)):
389 RmCommand = "rm '%s'" % BuildLogPath
390 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000391 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
George Karpenkova8076602017-10-02 17:59:12 +0000392 check_call(RmCommand, shell=True)
393
Devin Coughlin9ea80332016-01-23 01:09:07 +0000394
Anna Zaksa2f970b2012-09-06 23:30:27 +0000395def buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000396 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000397
Devin Coughlin9ea80332016-01-23 01:09:07 +0000398 BuildLogPath = getBuildLogPath(SBOutputDir)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000399 Local.stdout.write("Log file: %s\n" % (BuildLogPath,))
400 Local.stdout.write("Output directory: %s\n" % (SBOutputDir, ))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000401
Devin Coughlin9ea80332016-01-23 01:09:07 +0000402 removeLogFile(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000403
Anna Zaks4720a732011-11-05 05:20:48 +0000404 # Clean up scan build results.
George Karpenkova8076602017-10-02 17:59:12 +0000405 if (os.path.exists(SBOutputDir)):
Devin Coughlinab95cd22016-01-22 07:08:06 +0000406 RmCommand = "rm -r '%s'" % SBOutputDir
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000407 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000408 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
409 check_call(RmCommand, shell=True, stdout=Local.stdout,
410 stderr=Local.stderr)
Anna Zaks4720a732011-11-05 05:20:48 +0000411 assert(not os.path.exists(SBOutputDir))
412 os.makedirs(os.path.join(SBOutputDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000413
Anna Zaks4720a732011-11-05 05:20:48 +0000414 # Build and analyze the project.
George Karpenkov0a6dba72017-10-27 22:52:36 +0000415 with open(BuildLogPath, "wb+") as PBuildLogFile:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000416 if (ProjectBuildMode == 1):
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000417 downloadAndPatch(Dir, PBuildLogFile)
418 runCleanupScript(Dir, PBuildLogFile)
Anna Zaks4720a732011-11-05 05:20:48 +0000419 runScanBuild(Dir, SBOutputDir, PBuildLogFile)
420 else:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000421 runAnalyzePreprocessed(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000422
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000423 if IsReferenceBuild:
Anna Zaks42a44632011-11-02 20:46:50 +0000424 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000425 normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000426
George Karpenkovf37d3a52018-02-08 21:22:42 +0000427 Local.stdout.write("Build complete (time: %.2f). "
428 "See the log for more details: %s\n" % (
429 (time.time() - TBegin), BuildLogPath))
George Karpenkova8076602017-10-02 17:59:12 +0000430
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000431
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000432def normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode):
433 """
434 Make the absolute paths relative in the reference results.
435 """
436 for (DirPath, Dirnames, Filenames) in os.walk(SBOutputDir):
437 for F in Filenames:
438 if (not F.endswith('plist')):
439 continue
440 Plist = os.path.join(DirPath, F)
441 Data = plistlib.readPlist(Plist)
442 PathPrefix = Dir
443 if (ProjectBuildMode == 1):
444 PathPrefix = os.path.join(Dir, PatchedSourceDirName)
George Karpenkova8076602017-10-02 17:59:12 +0000445 Paths = [SourceFile[len(PathPrefix) + 1:]
446 if SourceFile.startswith(PathPrefix)
447 else SourceFile for SourceFile in Data['files']]
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000448 Data['files'] = Paths
George Karpenkov318cd1f2017-10-24 23:52:46 +0000449
450 # Remove transient fields which change from run to run.
451 for Diag in Data['diagnostics']:
452 if 'HTMLDiagnostics_files' in Diag:
453 Diag.pop('HTMLDiagnostics_files')
454 if 'clang_version' in Data:
455 Data.pop('clang_version')
456
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000457 plistlib.writePlist(Data, Plist)
458
George Karpenkova8076602017-10-02 17:59:12 +0000459
Anna Zaksf0c41162011-10-06 23:26:27 +0000460def CleanUpEmptyPlists(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000461 """
462 A plist file is created for each call to the analyzer(each source file).
463 We are only interested on the once that have bug reports,
464 so delete the rest.
465 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000466 for F in glob.glob(SBOutputDir + "/*/*.plist"):
467 P = os.path.join(SBOutputDir, F)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000468
Anna Zaksf0c41162011-10-06 23:26:27 +0000469 Data = plistlib.readPlist(P)
470 # Delete empty reports.
471 if not Data['files']:
472 os.remove(P)
473 continue
474
George Karpenkova8076602017-10-02 17:59:12 +0000475
George Karpenkov3c128cb2017-10-30 19:40:33 +0000476def CleanUpEmptyFolders(SBOutputDir):
477 """
478 Remove empty folders from results, as git would not store them.
479 """
480 Subfolders = glob.glob(SBOutputDir + "/*")
481 for Folder in Subfolders:
482 if not os.listdir(Folder):
483 os.removedirs(Folder)
484
485
Anna Zaksf0c41162011-10-06 23:26:27 +0000486def checkBuild(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000487 """
488 Given the scan-build output directory, checks if the build failed
489 (by searching for the failures directories). If there are failures, it
490 creates a summary file in the output directory.
491
492 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000493 # Check if there are failures.
494 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt")
George Karpenkova8076602017-10-02 17:59:12 +0000495 TotalFailed = len(Failures)
Anna Zaksf0c41162011-10-06 23:26:27 +0000496 if TotalFailed == 0:
Jordan Rose9858b122012-08-31 00:36:30 +0000497 CleanUpEmptyPlists(SBOutputDir)
George Karpenkov3c128cb2017-10-30 19:40:33 +0000498 CleanUpEmptyFolders(SBOutputDir)
Jordan Rose9858b122012-08-31 00:36:30 +0000499 Plists = glob.glob(SBOutputDir + "/*/*.plist")
George Karpenkovf37d3a52018-02-08 21:22:42 +0000500 Local.stdout.write(
501 "Number of bug reports (non-empty plist files) produced: %d\n" %
502 len(Plists))
George Karpenkova8076602017-10-02 17:59:12 +0000503 return
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000504
George Karpenkovf37d3a52018-02-08 21:22:42 +0000505 Local.stderr.write("Error: analysis failed.\n")
506 Local.stderr.write("Total of %d failures discovered.\n" % TotalFailed)
George Karpenkovff555ce2017-10-26 19:00:22 +0000507 if TotalFailed > NumOfFailuresInSummary:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000508 Local.stderr.write(
509 "See the first %d below.\n" % NumOfFailuresInSummary)
Anna Zaksf0c41162011-10-06 23:26:27 +0000510 # TODO: Add a line "See the results folder for more."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000511
George Karpenkovff555ce2017-10-26 19:00:22 +0000512 Idx = 0
513 for FailLogPathI in Failures:
514 if Idx >= NumOfFailuresInSummary:
515 break
516 Idx += 1
George Karpenkovf37d3a52018-02-08 21:22:42 +0000517 Local.stderr.write("\n-- Error #%d -----------\n" % Idx)
George Karpenkovff555ce2017-10-26 19:00:22 +0000518 with open(FailLogPathI, "r") as FailLogI:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000519 shutil.copyfileobj(FailLogI, Local.stdout)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000520
George Karpenkov65839bd2017-10-26 01:13:22 +0000521 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000522
Anna Zaksf0c41162011-10-06 23:26:27 +0000523
George Karpenkova8076602017-10-02 17:59:12 +0000524def runCmpResults(Dir, Strictness=0):
525 """
526 Compare the warnings produced by scan-build.
527 Strictness defines the success criteria for the test:
528 0 - success if there are no crashes or analyzer failure.
529 1 - success if there are no difference in the number of reported bugs.
530 2 - success if all the bug reports are identical.
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000531
532 :return success: Whether tests pass according to the Strictness
533 criteria.
George Karpenkova8076602017-10-02 17:59:12 +0000534 """
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000535 TestsPassed = True
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000536 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000537
538 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName)
539 NewDir = os.path.join(Dir, SBOutputDirName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000540
Anna Zaksf0c41162011-10-06 23:26:27 +0000541 # We have to go one level down the directory tree.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000542 RefList = glob.glob(RefDir + "/*")
Anna Zaksf0c41162011-10-06 23:26:27 +0000543 NewList = glob.glob(NewDir + "/*")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000544
Jordan Rosec7b992e2013-06-10 19:34:30 +0000545 # Log folders are also located in the results dir, so ignore them.
546 RefLogDir = os.path.join(RefDir, LogFolderName)
547 if RefLogDir in RefList:
548 RefList.remove(RefLogDir)
Anna Zaks4720a732011-11-05 05:20:48 +0000549 NewList.remove(os.path.join(NewDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000550
George Karpenkov65839bd2017-10-26 01:13:22 +0000551 if len(RefList) != len(NewList):
552 print "Mismatch in number of results folders: %s vs %s" % (
553 RefList, NewList)
554 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000555
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000556 # There might be more then one folder underneath - one per each scan-build
Anna Zaksf0c41162011-10-06 23:26:27 +0000557 # command (Ex: one for configure and one for make).
558 if (len(RefList) > 1):
559 # Assume that the corresponding folders have the same names.
560 RefList.sort()
561 NewList.sort()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000562
Anna Zaksf0c41162011-10-06 23:26:27 +0000563 # Iterate and find the differences.
Anna Zaks767d3562011-11-08 19:56:31 +0000564 NumDiffs = 0
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000565 PairList = zip(RefList, NewList)
566 for P in PairList:
567 RefDir = P[0]
Anna Zaksf0c41162011-10-06 23:26:27 +0000568 NewDir = P[1]
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000569
570 assert(RefDir != NewDir)
571 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000572 Local.stdout.write(" Comparing Results: %s %s\n" % (
573 RefDir, NewDir))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000574
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000575 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
George Karpenkovfc782a32018-02-09 18:39:47 +0000576 Opts, Args = CmpRuns.generate_option_parser().parse_args(
George Karpenkov192d9a12018-02-12 22:13:01 +0000577 ["--rootA", "", "--rootB", PatchedSourceDirPath])
Anna Zaksf0c41162011-10-06 23:26:27 +0000578 # Scan the results, delete empty plist files.
Gabor Horvath93fde942015-06-30 15:31:17 +0000579 NumDiffs, ReportsInRef, ReportsInNew = \
George Karpenkovb7120c92018-02-13 23:36:01 +0000580 CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts,
581 deleteEmpty=False,
582 Stdout=Local.stdout)
George Karpenkova8076602017-10-02 17:59:12 +0000583 if (NumDiffs > 0):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000584 Local.stdout.write("Warning: %s differences in diagnostics.\n"
585 % NumDiffs)
Gabor Horvath93fde942015-06-30 15:31:17 +0000586 if Strictness >= 2 and NumDiffs > 0:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000587 Local.stdout.write("Error: Diffs found in strict mode (2).\n")
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000588 TestsPassed = False
Gabor Horvath93fde942015-06-30 15:31:17 +0000589 elif Strictness >= 1 and ReportsInRef != ReportsInNew:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000590 Local.stdout.write("Error: The number of results are different " +
591 " strict mode (1).\n")
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000592 TestsPassed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000593
George Karpenkovf37d3a52018-02-08 21:22:42 +0000594 Local.stdout.write("Diagnostic comparison complete (time: %.2f).\n" % (
595 time.time() - TBegin))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000596 return TestsPassed
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000597
George Karpenkova8076602017-10-02 17:59:12 +0000598
Devin Coughlin9ea80332016-01-23 01:09:07 +0000599def cleanupReferenceResults(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000600 """
601 Delete html, css, and js files from reference results. These can
602 include multiple copies of the benchmark source and so get very large.
603 """
Devin Coughlin9ea80332016-01-23 01:09:07 +0000604 Extensions = ["html", "css", "js"]
605 for E in Extensions:
606 for F in glob.glob("%s/*/*.%s" % (SBOutputDir, E)):
607 P = os.path.join(SBOutputDir, F)
608 RmCommand = "rm '%s'" % P
609 check_call(RmCommand, shell=True)
610
611 # Remove the log file. It leaks absolute path names.
612 removeLogFile(SBOutputDir)
613
George Karpenkova8076602017-10-02 17:59:12 +0000614
George Karpenkovf37d3a52018-02-08 21:22:42 +0000615class TestProjectThread(threading.Thread):
616 def __init__(self, TasksQueue, ResultsDiffer, FailureFlag):
617 """
618 :param ResultsDiffer: Used to signify that results differ from
619 the canonical ones.
620 :param FailureFlag: Used to signify a failure during the run.
621 """
622 self.TasksQueue = TasksQueue
623 self.ResultsDiffer = ResultsDiffer
624 self.FailureFlag = FailureFlag
625 super(TestProjectThread, self).__init__()
626
627 # Needed to gracefully handle interrupts with Ctrl-C
628 self.daemon = True
629
630 def run(self):
631 while not self.TasksQueue.empty():
632 try:
633 ProjArgs = self.TasksQueue.get()
634 Logger = logging.getLogger(ProjArgs[0])
635 Local.stdout = StreamToLogger(Logger, logging.INFO)
636 Local.stderr = StreamToLogger(Logger, logging.ERROR)
637 if not testProject(*ProjArgs):
638 self.ResultsDiffer.set()
639 self.TasksQueue.task_done()
640 except:
641 self.FailureFlag.set()
642 raise
643
644
George Karpenkova8076602017-10-02 17:59:12 +0000645def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Strictness=0):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000646 """
647 Test a given project.
648 :return TestsPassed: Whether tests have passed according
649 to the :param Strictness: criteria.
650 """
George Karpenkovf37d3a52018-02-08 21:22:42 +0000651 Local.stdout.write(" \n\n--- Building project %s\n" % (ID,))
Anna Zaks4720a732011-11-05 05:20:48 +0000652
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000653 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000654
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000655 Dir = getProjectDir(ID)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000656 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000657 Local.stdout.write(" Build directory: %s.\n" % (Dir,))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000658
Anna Zaksf0c41162011-10-06 23:26:27 +0000659 # Set the build results directory.
Jordan Rose01ac5722012-06-01 16:24:38 +0000660 RelOutputDir = getSBOutputDirName(IsReferenceBuild)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000661 SBOutputDir = os.path.join(Dir, RelOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000662
Anna Zaksa2f970b2012-09-06 23:30:27 +0000663 buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild)
Anna Zaksf0c41162011-10-06 23:26:27 +0000664
665 checkBuild(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000666
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000667 if IsReferenceBuild:
Devin Coughlin9ea80332016-01-23 01:09:07 +0000668 cleanupReferenceResults(SBOutputDir)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000669 TestsPassed = True
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000670 else:
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000671 TestsPassed = runCmpResults(Dir, Strictness)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000672
George Karpenkovf37d3a52018-02-08 21:22:42 +0000673 Local.stdout.write("Completed tests for project %s (time: %.2f).\n" % (
674 ID, (time.time() - TBegin)))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000675 return TestsPassed
George Karpenkova8076602017-10-02 17:59:12 +0000676
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000677
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000678def projectFileHandler():
679 return open(getProjectMapPath(), "rb")
680
George Karpenkova8076602017-10-02 17:59:12 +0000681
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000682def iterateOverProjects(PMapFile):
683 """
684 Iterate over all projects defined in the project file handler `PMapFile`
685 from the start.
686 """
687 PMapFile.seek(0)
George Karpenkova8076602017-10-02 17:59:12 +0000688 for I in csv.reader(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000689 if (SATestUtils.isCommentCSVLine(I)):
George Karpenkova8076602017-10-02 17:59:12 +0000690 continue
691 yield I
692
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000693
694def validateProjectFile(PMapFile):
695 """
696 Validate project file.
697 """
698 for I in iterateOverProjects(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000699 if len(I) != 2:
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000700 print "Error: Rows in the ProjectMapFile should have 2 entries."
701 raise Exception()
George Karpenkovbf92c442017-10-24 23:52:48 +0000702 if I[1] not in ('0', '1', '2'):
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000703 print "Error: Second entry in the ProjectMapFile should be 0" \
George Karpenkova8076602017-10-02 17:59:12 +0000704 " (single file), 1 (project), or 2(single file c++11)."
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000705 raise Exception()
706
George Karpenkovf37d3a52018-02-08 21:22:42 +0000707def singleThreadedTestAll(ProjectsToTest):
708 """
709 Run all projects.
710 :return: whether tests have passed.
711 """
712 Success = True
713 for ProjArgs in ProjectsToTest:
714 Success &= testProject(*ProjArgs)
715 return Success
George Karpenkova8076602017-10-02 17:59:12 +0000716
George Karpenkovf37d3a52018-02-08 21:22:42 +0000717def multiThreadedTestAll(ProjectsToTest, Jobs):
718 """
719 Run each project in a separate thread.
720
721 This is OK despite GIL, as testing is blocked
722 on launching external processes.
723
724 :return: whether tests have passed.
725 """
726 TasksQueue = Queue.Queue()
727
728 for ProjArgs in ProjectsToTest:
729 TasksQueue.put(ProjArgs)
730
731 ResultsDiffer = threading.Event()
732 FailureFlag = threading.Event()
733
734 for i in range(Jobs):
735 T = TestProjectThread(TasksQueue, ResultsDiffer, FailureFlag)
736 T.start()
737
738 # Required to handle Ctrl-C gracefully.
739 while TasksQueue.unfinished_tasks:
740 time.sleep(0.1) # Seconds.
741 if FailureFlag.is_set():
742 Local.stderr.write("Test runner crashed\n")
743 sys.exit(1)
744 return not ResultsDiffer.is_set()
745
746
747def testAll(Args):
748 ProjectsToTest = []
749
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000750 with projectFileHandler() as PMapFile:
751 validateProjectFile(PMapFile)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000752
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000753 # Test the projects.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000754 for (ProjName, ProjBuildMode) in iterateOverProjects(PMapFile):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000755 ProjectsToTest.append((ProjName,
756 int(ProjBuildMode),
757 Args.regenerate,
758 Args.strictness))
759 if Args.jobs <= 1:
760 return singleThreadedTestAll(ProjectsToTest)
761 else:
762 return multiThreadedTestAll(ProjectsToTest, Args.jobs)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000763
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000764
Anna Zaksf0c41162011-10-06 23:26:27 +0000765if __name__ == '__main__':
Gabor Horvath93fde942015-06-30 15:31:17 +0000766 # Parse command line arguments.
George Karpenkova8076602017-10-02 17:59:12 +0000767 Parser = argparse.ArgumentParser(
768 description='Test the Clang Static Analyzer.')
Gabor Horvath93fde942015-06-30 15:31:17 +0000769 Parser.add_argument('--strictness', dest='strictness', type=int, default=0,
George Karpenkova8076602017-10-02 17:59:12 +0000770 help='0 to fail on runtime errors, 1 to fail when the \
771 number of found bugs are different from the \
772 reference, 2 to fail on any difference from the \
773 reference. Default is 0.')
774 Parser.add_argument('-r', dest='regenerate', action='store_true',
775 default=False, help='Regenerate reference output.')
George Karpenkovf37d3a52018-02-08 21:22:42 +0000776 Parser.add_argument('-j', '--jobs', dest='jobs', type=int,
777 default=0,
778 help='Number of projects to test concurrently')
Gabor Horvath93fde942015-06-30 15:31:17 +0000779 Args = Parser.parse_args()
780
George Karpenkovf37d3a52018-02-08 21:22:42 +0000781 TestsPassed = testAll(Args)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000782 if not TestsPassed:
George Karpenkovbf92c442017-10-24 23:52:48 +0000783 print "ERROR: Tests failed."
George Karpenkov65839bd2017-10-26 01:13:22 +0000784 sys.exit(42)