blob: 4da025aa53b3aebd9b3ac2573400158db6f5bf26 [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
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000139# A comment in a build script which disables wrapping.
140NoPrefixCmd = "#NOPREFIX"
141
Ted Kremenek42c14422012-08-28 20:40:02 +0000142# The log file name.
143LogFolderName = "Logs"
144BuildLogName = "run_static_analyzer.log"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000145# Summary file - contains the summary of the failures. Ex: This info can be be
Ted Kremenek42c14422012-08-28 20:40:02 +0000146# displayed when buildbot detects a build failure.
147NumOfFailuresInSummary = 10
148FailuresSummaryFileName = "failures.txt"
Ted Kremenek42c14422012-08-28 20:40:02 +0000149
150# The scan-build result directory.
151SBOutputDirName = "ScanBuildResults"
152SBOutputDirReferencePrefix = "Ref"
153
George Karpenkova8076602017-10-02 17:59:12 +0000154# The name of the directory storing the cached project source. If this
155# directory does not exist, the download script will be executed.
156# That script should create the "CachedSource" directory and download the
157# project source into it.
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000158CachedSourceDirName = "CachedSource"
159
160# The name of the directory containing the source code that will be analyzed.
161# Each time a project is analyzed, a fresh copy of its CachedSource directory
162# will be copied to the PatchedSource directory and then the local patches
163# in PatchfileName will be applied (if PatchfileName exists).
164PatchedSourceDirName = "PatchedSource"
165
166# The name of the patchfile specifying any changes that should be applied
167# to the CachedSource before analyzing.
168PatchfileName = "changes_for_analyzer.patch"
169
Ted Kremenek42c14422012-08-28 20:40:02 +0000170# The list of checkers used during analyzes.
Alp Tokerd4733632013-12-05 04:47:09 +0000171# Currently, consists of all the non-experimental checkers, plus a few alpha
Jordan Rose10ad0812013-04-05 17:55:07 +0000172# checkers we don't want to regress on.
George Karpenkova8076602017-10-02 17:59:12 +0000173Checkers = ",".join([
George Karpenkovaf76b4a2017-09-30 00:05:24 +0000174 "alpha.unix.SimpleStream",
175 "alpha.security.taint",
176 "cplusplus.NewDeleteLeaks",
177 "core",
178 "cplusplus",
179 "deadcode",
180 "security",
181 "unix",
182 "osx",
183 "nullability"
184])
Ted Kremenek42c14422012-08-28 20:40:02 +0000185
George Karpenkov71105812018-03-29 01:23:54 +0000186Verbose = 0
Ted Kremenek42c14422012-08-28 20:40:02 +0000187
188#------------------------------------------------------------------------------
189# Test harness logic.
190#------------------------------------------------------------------------------
191
George Karpenkova8076602017-10-02 17:59:12 +0000192
Anna Zaks42a44632011-11-02 20:46:50 +0000193def runCleanupScript(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000194 """
195 Run pre-processing script if any.
196 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000197 Cwd = os.path.join(Dir, PatchedSourceDirName)
Anna Zaks42a44632011-11-02 20:46:50 +0000198 ScriptPath = os.path.join(Dir, CleanupScript)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000199 SATestUtils.runScript(ScriptPath, PBuildLogFile, Cwd,
200 Stdout=Local.stdout, Stderr=Local.stderr)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000201
George Karpenkova8076602017-10-02 17:59:12 +0000202
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000203def runDownloadScript(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000204 """
205 Run the script to download the project, if it exists.
206 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000207 ScriptPath = os.path.join(Dir, DownloadScript)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000208 SATestUtils.runScript(ScriptPath, PBuildLogFile, Dir,
209 Stdout=Local.stdout, Stderr=Local.stderr)
Anna Zaksf0c41162011-10-06 23:26:27 +0000210
George Karpenkova8076602017-10-02 17:59:12 +0000211
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000212def downloadAndPatch(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000213 """
214 Download the project and apply the local patchfile if it exists.
215 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000216 CachedSourceDirPath = os.path.join(Dir, CachedSourceDirName)
217
218 # If the we don't already have the cached source, run the project's
219 # download script to download it.
220 if not os.path.exists(CachedSourceDirPath):
George Karpenkova8076602017-10-02 17:59:12 +0000221 runDownloadScript(Dir, PBuildLogFile)
222 if not os.path.exists(CachedSourceDirPath):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000223 Local.stderr.write("Error: '%s' not found after download.\n" % (
224 CachedSourceDirPath))
George Karpenkov65839bd2017-10-26 01:13:22 +0000225 exit(1)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000226
227 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
228
229 # Remove potentially stale patched source.
230 if os.path.exists(PatchedSourceDirPath):
231 shutil.rmtree(PatchedSourceDirPath)
232
233 # Copy the cached source and apply any patches to the copy.
234 shutil.copytree(CachedSourceDirPath, PatchedSourceDirPath, symlinks=True)
235 applyPatch(Dir, PBuildLogFile)
236
George Karpenkova8076602017-10-02 17:59:12 +0000237
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000238def applyPatch(Dir, PBuildLogFile):
239 PatchfilePath = os.path.join(Dir, PatchfileName)
240 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
241 if not os.path.exists(PatchfilePath):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000242 Local.stdout.write(" No local patches.\n")
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000243 return
244
George Karpenkovf37d3a52018-02-08 21:22:42 +0000245 Local.stdout.write(" Applying patch.\n")
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000246 try:
Devin Coughlinab95cd22016-01-22 07:08:06 +0000247 check_call("patch -p1 < '%s'" % (PatchfilePath),
George Karpenkova8076602017-10-02 17:59:12 +0000248 cwd=PatchedSourceDirPath,
249 stderr=PBuildLogFile,
250 stdout=PBuildLogFile,
251 shell=True)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000252 except:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000253 Local.stderr.write("Error: Patch failed. See %s for details.\n" % (
254 PBuildLogFile.name))
George Karpenkov65839bd2017-10-26 01:13:22 +0000255 sys.exit(1)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000256
George Karpenkova8076602017-10-02 17:59:12 +0000257
Anna Zaksf0c41162011-10-06 23:26:27 +0000258def runScanBuild(Dir, SBOutputDir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000259 """
260 Build the project with scan-build by reading in the commands and
261 prefixing them with the scan-build options.
262 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000263 BuildScriptPath = os.path.join(Dir, BuildScript)
264 if not os.path.exists(BuildScriptPath):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000265 Local.stderr.write(
266 "Error: build script is not defined: %s\n" % BuildScriptPath)
George Karpenkov65839bd2017-10-26 01:13:22 +0000267 sys.exit(1)
Devin Coughlinf3695c82015-09-16 01:52:32 +0000268
269 AllCheckers = Checkers
George Karpenkova8076602017-10-02 17:59:12 +0000270 if 'SA_ADDITIONAL_CHECKERS' in os.environ:
Devin Coughlinf3695c82015-09-16 01:52:32 +0000271 AllCheckers = AllCheckers + ',' + os.environ['SA_ADDITIONAL_CHECKERS']
272
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000273 # Run scan-build from within the patched source directory.
274 SBCwd = os.path.join(Dir, PatchedSourceDirName)
275
George Karpenkova8076602017-10-02 17:59:12 +0000276 SBOptions = "--use-analyzer '%s' " % Clang
Devin Coughlin86f61a92016-01-22 18:45:22 +0000277 SBOptions += "-plist-html -o '%s' " % SBOutputDir
Devin Coughlinf3695c82015-09-16 01:52:32 +0000278 SBOptions += "-enable-checker " + AllCheckers + " "
Jordan Roseb18179d2013-01-24 23:07:59 +0000279 SBOptions += "--keep-empty "
George Karpenkova262cf32018-06-29 22:05:13 +0000280 AnalyzerConfig = [
281 ("stable-report-filename", "true"),
282 ("serialize-stats", "true"),
283 ]
George Karpenkov30130b72018-06-26 23:17:35 +0000284
285 SBOptions += "-analyzer-config '%s' " % (
George Karpenkova262cf32018-06-29 22:05:13 +0000286 ",".join("%s=%s" % (key, value) for (key, value) in AnalyzerConfig))
Mikhail R. Gadelhaafc62b72018-06-27 14:39:41 +0000287
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000288 # Always use ccc-analyze to ensure that we can locate the failures
Anna Zaks7b4f8a42013-05-31 02:31:09 +0000289 # directory.
290 SBOptions += "--override-compiler "
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000291 ExtraEnv = {}
Anna Zaksf0c41162011-10-06 23:26:27 +0000292 try:
293 SBCommandFile = open(BuildScriptPath, "r")
294 SBPrefix = "scan-build " + SBOptions + " "
295 for Command in SBCommandFile:
Jordan Rose7bd91862013-09-06 16:12:41 +0000296 Command = Command.strip()
Gabor Horvath93fde942015-06-30 15:31:17 +0000297 if len(Command) == 0:
George Karpenkova8076602017-10-02 17:59:12 +0000298 continue
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000299
300 # Custom analyzer invocation specified by project.
301 # Communicate required information using environment variables
302 # instead.
303 if Command == NoPrefixCmd:
304 SBPrefix = ""
305 ExtraEnv['OUTPUT'] = SBOutputDir
George Karpenkov6e4ddf42018-07-02 17:10:40 +0000306 ExtraEnv['CC'] = Clang
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000307 continue
308
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000309 # If using 'make', auto imply a -jX argument
310 # to speed up analysis. xcodebuild will
311 # automatically use the maximum number of cores.
Jordan Rose64e4cf02012-11-26 19:59:57 +0000312 if (Command.startswith("make ") or Command == "make") and \
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000313 "-j" not in Command:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000314 Command += " -j%d" % MaxJobs
Anna Zaksf0c41162011-10-06 23:26:27 +0000315 SBCommand = SBPrefix + Command
George Karpenkovf37d3a52018-02-08 21:22:42 +0000316
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000317 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000318 Local.stdout.write(" Executing: %s\n" % (SBCommand,))
George Karpenkova8076602017-10-02 17:59:12 +0000319 check_call(SBCommand, cwd=SBCwd,
320 stderr=PBuildLogFile,
321 stdout=PBuildLogFile,
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000322 env=dict(os.environ, **ExtraEnv),
George Karpenkova8076602017-10-02 17:59:12 +0000323 shell=True)
George Karpenkove58044d2017-10-27 22:39:54 +0000324 except CalledProcessError:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000325 Local.stderr.write("Error: scan-build failed. Its output was: \n")
George Karpenkove58044d2017-10-27 22:39:54 +0000326 PBuildLogFile.seek(0)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000327 shutil.copyfileobj(PBuildLogFile, Local.stderr)
George Karpenkove58044d2017-10-27 22:39:54 +0000328 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000329
George Karpenkova8076602017-10-02 17:59:12 +0000330
Anna Zaksa2f970b2012-09-06 23:30:27 +0000331def runAnalyzePreprocessed(Dir, SBOutputDir, Mode):
George Karpenkova8076602017-10-02 17:59:12 +0000332 """
333 Run analysis on a set of preprocessed files.
334 """
Anna Zaks4720a732011-11-05 05:20:48 +0000335 if os.path.exists(os.path.join(Dir, BuildScript)):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000336 Local.stderr.write(
337 "Error: The preprocessed files project should not contain %s\n" % (
338 BuildScript))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000339 raise Exception()
Anna Zaks4720a732011-11-05 05:20:48 +0000340
Devin Coughlinbace0322015-09-14 21:22:24 +0000341 CmdPrefix = Clang + " -cc1 "
342
343 # For now, we assume the preprocessed files should be analyzed
344 # with the OS X SDK.
George Karpenkovbf92c442017-10-24 23:52:48 +0000345 SDKPath = SATestUtils.getSDKPath("macosx")
Devin Coughlinbace0322015-09-14 21:22:24 +0000346 if SDKPath is not None:
George Karpenkova8076602017-10-02 17:59:12 +0000347 CmdPrefix += "-isysroot " + SDKPath + " "
Devin Coughlinbace0322015-09-14 21:22:24 +0000348
349 CmdPrefix += "-analyze -analyzer-output=plist -w "
George Karpenkova8076602017-10-02 17:59:12 +0000350 CmdPrefix += "-analyzer-checker=" + Checkers
351 CmdPrefix += " -fcxx-exceptions -fblocks "
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000352
George Karpenkova8076602017-10-02 17:59:12 +0000353 if (Mode == 2):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000354 CmdPrefix += "-std=c++11 "
355
Anna Zaks4720a732011-11-05 05:20:48 +0000356 PlistPath = os.path.join(Dir, SBOutputDir, "date")
George Karpenkova8076602017-10-02 17:59:12 +0000357 FailPath = os.path.join(PlistPath, "failures")
358 os.makedirs(FailPath)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000359
Anna Zaks4720a732011-11-05 05:20:48 +0000360 for FullFileName in glob.glob(Dir + "/*"):
361 FileName = os.path.basename(FullFileName)
362 Failed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000363
Anna Zaks4720a732011-11-05 05:20:48 +0000364 # Only run the analyzes on supported files.
George Karpenkovbf92c442017-10-24 23:52:48 +0000365 if SATestUtils.hasNoExtension(FileName):
Anna Zaks4720a732011-11-05 05:20:48 +0000366 continue
George Karpenkovbf92c442017-10-24 23:52:48 +0000367 if not SATestUtils.isValidSingleInputFile(FileName):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000368 Local.stderr.write(
369 "Error: Invalid single input file %s.\n" % (FullFileName,))
Anna Zaks4720a732011-11-05 05:20:48 +0000370 raise Exception()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000371
Anna Zaks4720a732011-11-05 05:20:48 +0000372 # Build and call the analyzer command.
Devin Coughlinab95cd22016-01-22 07:08:06 +0000373 OutputOption = "-o '%s.plist' " % os.path.join(PlistPath, FileName)
374 Command = CmdPrefix + OutputOption + ("'%s'" % FileName)
Anna Zaks4720a732011-11-05 05:20:48 +0000375 LogFile = open(os.path.join(FailPath, FileName + ".stderr.txt"), "w+b")
376 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000377 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000378 Local.stdout.write(" Executing: %s\n" % (Command,))
George Karpenkova8076602017-10-02 17:59:12 +0000379 check_call(Command, cwd=Dir, stderr=LogFile,
380 stdout=LogFile,
381 shell=True)
Anna Zaks4720a732011-11-05 05:20:48 +0000382 except CalledProcessError, e:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000383 Local.stderr.write("Error: Analyzes of %s failed. "
384 "See %s for details."
385 "Error code %d.\n" % (
386 FullFileName, LogFile.name, e.returncode))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000387 Failed = True
Anna Zaks4720a732011-11-05 05:20:48 +0000388 finally:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000389 LogFile.close()
390
Anna Zaks4720a732011-11-05 05:20:48 +0000391 # If command did not fail, erase the log file.
George Karpenkova8076602017-10-02 17:59:12 +0000392 if not Failed:
393 os.remove(LogFile.name)
394
Anna Zaks4720a732011-11-05 05:20:48 +0000395
Devin Coughlin9ea80332016-01-23 01:09:07 +0000396def getBuildLogPath(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000397 return os.path.join(SBOutputDir, LogFolderName, BuildLogName)
398
Devin Coughlin9ea80332016-01-23 01:09:07 +0000399
400def removeLogFile(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000401 BuildLogPath = getBuildLogPath(SBOutputDir)
402 # Clean up the log file.
403 if (os.path.exists(BuildLogPath)):
404 RmCommand = "rm '%s'" % BuildLogPath
405 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000406 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
George Karpenkova8076602017-10-02 17:59:12 +0000407 check_call(RmCommand, shell=True)
408
Devin Coughlin9ea80332016-01-23 01:09:07 +0000409
Anna Zaksa2f970b2012-09-06 23:30:27 +0000410def buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000411 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000412
Devin Coughlin9ea80332016-01-23 01:09:07 +0000413 BuildLogPath = getBuildLogPath(SBOutputDir)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000414 Local.stdout.write("Log file: %s\n" % (BuildLogPath,))
415 Local.stdout.write("Output directory: %s\n" % (SBOutputDir, ))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000416
Devin Coughlin9ea80332016-01-23 01:09:07 +0000417 removeLogFile(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000418
Anna Zaks4720a732011-11-05 05:20:48 +0000419 # Clean up scan build results.
George Karpenkova8076602017-10-02 17:59:12 +0000420 if (os.path.exists(SBOutputDir)):
Devin Coughlinab95cd22016-01-22 07:08:06 +0000421 RmCommand = "rm -r '%s'" % SBOutputDir
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000422 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000423 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
424 check_call(RmCommand, shell=True, stdout=Local.stdout,
425 stderr=Local.stderr)
Anna Zaks4720a732011-11-05 05:20:48 +0000426 assert(not os.path.exists(SBOutputDir))
427 os.makedirs(os.path.join(SBOutputDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000428
Anna Zaks4720a732011-11-05 05:20:48 +0000429 # Build and analyze the project.
George Karpenkov0a6dba72017-10-27 22:52:36 +0000430 with open(BuildLogPath, "wb+") as PBuildLogFile:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000431 if (ProjectBuildMode == 1):
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000432 downloadAndPatch(Dir, PBuildLogFile)
433 runCleanupScript(Dir, PBuildLogFile)
Anna Zaks4720a732011-11-05 05:20:48 +0000434 runScanBuild(Dir, SBOutputDir, PBuildLogFile)
435 else:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000436 runAnalyzePreprocessed(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000437
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000438 if IsReferenceBuild:
Anna Zaks42a44632011-11-02 20:46:50 +0000439 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000440 normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000441
George Karpenkovf37d3a52018-02-08 21:22:42 +0000442 Local.stdout.write("Build complete (time: %.2f). "
443 "See the log for more details: %s\n" % (
444 (time.time() - TBegin), BuildLogPath))
George Karpenkova8076602017-10-02 17:59:12 +0000445
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000446
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000447def normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode):
448 """
449 Make the absolute paths relative in the reference results.
450 """
451 for (DirPath, Dirnames, Filenames) in os.walk(SBOutputDir):
452 for F in Filenames:
453 if (not F.endswith('plist')):
454 continue
455 Plist = os.path.join(DirPath, F)
456 Data = plistlib.readPlist(Plist)
457 PathPrefix = Dir
458 if (ProjectBuildMode == 1):
459 PathPrefix = os.path.join(Dir, PatchedSourceDirName)
George Karpenkova8076602017-10-02 17:59:12 +0000460 Paths = [SourceFile[len(PathPrefix) + 1:]
461 if SourceFile.startswith(PathPrefix)
462 else SourceFile for SourceFile in Data['files']]
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000463 Data['files'] = Paths
George Karpenkov318cd1f2017-10-24 23:52:46 +0000464
465 # Remove transient fields which change from run to run.
466 for Diag in Data['diagnostics']:
467 if 'HTMLDiagnostics_files' in Diag:
468 Diag.pop('HTMLDiagnostics_files')
469 if 'clang_version' in Data:
470 Data.pop('clang_version')
471
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000472 plistlib.writePlist(Data, Plist)
473
George Karpenkova8076602017-10-02 17:59:12 +0000474
Anna Zaksf0c41162011-10-06 23:26:27 +0000475def CleanUpEmptyPlists(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000476 """
477 A plist file is created for each call to the analyzer(each source file).
478 We are only interested on the once that have bug reports,
479 so delete the rest.
480 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000481 for F in glob.glob(SBOutputDir + "/*/*.plist"):
482 P = os.path.join(SBOutputDir, F)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000483
Anna Zaksf0c41162011-10-06 23:26:27 +0000484 Data = plistlib.readPlist(P)
485 # Delete empty reports.
486 if not Data['files']:
487 os.remove(P)
488 continue
489
George Karpenkova8076602017-10-02 17:59:12 +0000490
George Karpenkov3c128cb2017-10-30 19:40:33 +0000491def CleanUpEmptyFolders(SBOutputDir):
492 """
493 Remove empty folders from results, as git would not store them.
494 """
495 Subfolders = glob.glob(SBOutputDir + "/*")
496 for Folder in Subfolders:
497 if not os.listdir(Folder):
498 os.removedirs(Folder)
499
500
Anna Zaksf0c41162011-10-06 23:26:27 +0000501def checkBuild(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000502 """
503 Given the scan-build output directory, checks if the build failed
504 (by searching for the failures directories). If there are failures, it
505 creates a summary file in the output directory.
506
507 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000508 # Check if there are failures.
509 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt")
George Karpenkova8076602017-10-02 17:59:12 +0000510 TotalFailed = len(Failures)
Anna Zaksf0c41162011-10-06 23:26:27 +0000511 if TotalFailed == 0:
Jordan Rose9858b122012-08-31 00:36:30 +0000512 CleanUpEmptyPlists(SBOutputDir)
George Karpenkov3c128cb2017-10-30 19:40:33 +0000513 CleanUpEmptyFolders(SBOutputDir)
Jordan Rose9858b122012-08-31 00:36:30 +0000514 Plists = glob.glob(SBOutputDir + "/*/*.plist")
George Karpenkovf37d3a52018-02-08 21:22:42 +0000515 Local.stdout.write(
516 "Number of bug reports (non-empty plist files) produced: %d\n" %
517 len(Plists))
George Karpenkova8076602017-10-02 17:59:12 +0000518 return
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000519
George Karpenkovf37d3a52018-02-08 21:22:42 +0000520 Local.stderr.write("Error: analysis failed.\n")
521 Local.stderr.write("Total of %d failures discovered.\n" % TotalFailed)
George Karpenkovff555ce2017-10-26 19:00:22 +0000522 if TotalFailed > NumOfFailuresInSummary:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000523 Local.stderr.write(
524 "See the first %d below.\n" % NumOfFailuresInSummary)
Anna Zaksf0c41162011-10-06 23:26:27 +0000525 # TODO: Add a line "See the results folder for more."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000526
George Karpenkovff555ce2017-10-26 19:00:22 +0000527 Idx = 0
528 for FailLogPathI in Failures:
529 if Idx >= NumOfFailuresInSummary:
530 break
531 Idx += 1
George Karpenkovf37d3a52018-02-08 21:22:42 +0000532 Local.stderr.write("\n-- Error #%d -----------\n" % Idx)
George Karpenkovff555ce2017-10-26 19:00:22 +0000533 with open(FailLogPathI, "r") as FailLogI:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000534 shutil.copyfileobj(FailLogI, Local.stdout)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000535
George Karpenkov65839bd2017-10-26 01:13:22 +0000536 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000537
Anna Zaksf0c41162011-10-06 23:26:27 +0000538
George Karpenkova8076602017-10-02 17:59:12 +0000539def runCmpResults(Dir, Strictness=0):
540 """
541 Compare the warnings produced by scan-build.
542 Strictness defines the success criteria for the test:
543 0 - success if there are no crashes or analyzer failure.
544 1 - success if there are no difference in the number of reported bugs.
545 2 - success if all the bug reports are identical.
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000546
547 :return success: Whether tests pass according to the Strictness
548 criteria.
George Karpenkova8076602017-10-02 17:59:12 +0000549 """
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000550 TestsPassed = True
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000551 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000552
553 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName)
554 NewDir = os.path.join(Dir, SBOutputDirName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000555
Anna Zaksf0c41162011-10-06 23:26:27 +0000556 # We have to go one level down the directory tree.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000557 RefList = glob.glob(RefDir + "/*")
Anna Zaksf0c41162011-10-06 23:26:27 +0000558 NewList = glob.glob(NewDir + "/*")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000559
Jordan Rosec7b992e2013-06-10 19:34:30 +0000560 # Log folders are also located in the results dir, so ignore them.
561 RefLogDir = os.path.join(RefDir, LogFolderName)
562 if RefLogDir in RefList:
563 RefList.remove(RefLogDir)
Anna Zaks4720a732011-11-05 05:20:48 +0000564 NewList.remove(os.path.join(NewDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000565
George Karpenkov65839bd2017-10-26 01:13:22 +0000566 if len(RefList) != len(NewList):
567 print "Mismatch in number of results folders: %s vs %s" % (
568 RefList, NewList)
569 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000570
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000571 # There might be more then one folder underneath - one per each scan-build
Anna Zaksf0c41162011-10-06 23:26:27 +0000572 # command (Ex: one for configure and one for make).
573 if (len(RefList) > 1):
574 # Assume that the corresponding folders have the same names.
575 RefList.sort()
576 NewList.sort()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000577
Anna Zaksf0c41162011-10-06 23:26:27 +0000578 # Iterate and find the differences.
Anna Zaks767d3562011-11-08 19:56:31 +0000579 NumDiffs = 0
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000580 PairList = zip(RefList, NewList)
581 for P in PairList:
582 RefDir = P[0]
Anna Zaksf0c41162011-10-06 23:26:27 +0000583 NewDir = P[1]
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000584
585 assert(RefDir != NewDir)
586 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000587 Local.stdout.write(" Comparing Results: %s %s\n" % (
588 RefDir, NewDir))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000589
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000590 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
George Karpenkovfc782a32018-02-09 18:39:47 +0000591 Opts, Args = CmpRuns.generate_option_parser().parse_args(
George Karpenkov192d9a12018-02-12 22:13:01 +0000592 ["--rootA", "", "--rootB", PatchedSourceDirPath])
Anna Zaksf0c41162011-10-06 23:26:27 +0000593 # Scan the results, delete empty plist files.
Gabor Horvath93fde942015-06-30 15:31:17 +0000594 NumDiffs, ReportsInRef, ReportsInNew = \
George Karpenkovb7120c92018-02-13 23:36:01 +0000595 CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts,
596 deleteEmpty=False,
597 Stdout=Local.stdout)
George Karpenkova8076602017-10-02 17:59:12 +0000598 if (NumDiffs > 0):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000599 Local.stdout.write("Warning: %s differences in diagnostics.\n"
600 % NumDiffs)
Gabor Horvath93fde942015-06-30 15:31:17 +0000601 if Strictness >= 2 and NumDiffs > 0:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000602 Local.stdout.write("Error: Diffs found in strict mode (2).\n")
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000603 TestsPassed = False
Gabor Horvath93fde942015-06-30 15:31:17 +0000604 elif Strictness >= 1 and ReportsInRef != ReportsInNew:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000605 Local.stdout.write("Error: The number of results are different " +
606 " strict mode (1).\n")
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000607 TestsPassed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000608
George Karpenkovf37d3a52018-02-08 21:22:42 +0000609 Local.stdout.write("Diagnostic comparison complete (time: %.2f).\n" % (
610 time.time() - TBegin))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000611 return TestsPassed
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000612
George Karpenkova8076602017-10-02 17:59:12 +0000613
Devin Coughlin9ea80332016-01-23 01:09:07 +0000614def cleanupReferenceResults(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000615 """
616 Delete html, css, and js files from reference results. These can
617 include multiple copies of the benchmark source and so get very large.
618 """
Devin Coughlin9ea80332016-01-23 01:09:07 +0000619 Extensions = ["html", "css", "js"]
620 for E in Extensions:
621 for F in glob.glob("%s/*/*.%s" % (SBOutputDir, E)):
622 P = os.path.join(SBOutputDir, F)
623 RmCommand = "rm '%s'" % P
624 check_call(RmCommand, shell=True)
625
626 # Remove the log file. It leaks absolute path names.
627 removeLogFile(SBOutputDir)
628
George Karpenkova8076602017-10-02 17:59:12 +0000629
George Karpenkovf37d3a52018-02-08 21:22:42 +0000630class TestProjectThread(threading.Thread):
631 def __init__(self, TasksQueue, ResultsDiffer, FailureFlag):
632 """
633 :param ResultsDiffer: Used to signify that results differ from
634 the canonical ones.
635 :param FailureFlag: Used to signify a failure during the run.
636 """
637 self.TasksQueue = TasksQueue
638 self.ResultsDiffer = ResultsDiffer
639 self.FailureFlag = FailureFlag
640 super(TestProjectThread, self).__init__()
641
642 # Needed to gracefully handle interrupts with Ctrl-C
643 self.daemon = True
644
645 def run(self):
646 while not self.TasksQueue.empty():
647 try:
648 ProjArgs = self.TasksQueue.get()
649 Logger = logging.getLogger(ProjArgs[0])
650 Local.stdout = StreamToLogger(Logger, logging.INFO)
651 Local.stderr = StreamToLogger(Logger, logging.ERROR)
652 if not testProject(*ProjArgs):
653 self.ResultsDiffer.set()
654 self.TasksQueue.task_done()
655 except:
656 self.FailureFlag.set()
657 raise
658
659
George Karpenkova8076602017-10-02 17:59:12 +0000660def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Strictness=0):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000661 """
662 Test a given project.
663 :return TestsPassed: Whether tests have passed according
664 to the :param Strictness: criteria.
665 """
George Karpenkovf37d3a52018-02-08 21:22:42 +0000666 Local.stdout.write(" \n\n--- Building project %s\n" % (ID,))
Anna Zaks4720a732011-11-05 05:20:48 +0000667
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000668 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000669
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000670 Dir = getProjectDir(ID)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000671 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000672 Local.stdout.write(" Build directory: %s.\n" % (Dir,))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000673
Anna Zaksf0c41162011-10-06 23:26:27 +0000674 # Set the build results directory.
Jordan Rose01ac5722012-06-01 16:24:38 +0000675 RelOutputDir = getSBOutputDirName(IsReferenceBuild)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000676 SBOutputDir = os.path.join(Dir, RelOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000677
Anna Zaksa2f970b2012-09-06 23:30:27 +0000678 buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild)
Anna Zaksf0c41162011-10-06 23:26:27 +0000679
680 checkBuild(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000681
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000682 if IsReferenceBuild:
Devin Coughlin9ea80332016-01-23 01:09:07 +0000683 cleanupReferenceResults(SBOutputDir)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000684 TestsPassed = True
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000685 else:
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000686 TestsPassed = runCmpResults(Dir, Strictness)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000687
George Karpenkovf37d3a52018-02-08 21:22:42 +0000688 Local.stdout.write("Completed tests for project %s (time: %.2f).\n" % (
689 ID, (time.time() - TBegin)))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000690 return TestsPassed
George Karpenkova8076602017-10-02 17:59:12 +0000691
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000692
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000693def projectFileHandler():
694 return open(getProjectMapPath(), "rb")
695
George Karpenkova8076602017-10-02 17:59:12 +0000696
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000697def iterateOverProjects(PMapFile):
698 """
699 Iterate over all projects defined in the project file handler `PMapFile`
700 from the start.
701 """
702 PMapFile.seek(0)
George Karpenkova8076602017-10-02 17:59:12 +0000703 for I in csv.reader(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000704 if (SATestUtils.isCommentCSVLine(I)):
George Karpenkova8076602017-10-02 17:59:12 +0000705 continue
706 yield I
707
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000708
709def validateProjectFile(PMapFile):
710 """
711 Validate project file.
712 """
713 for I in iterateOverProjects(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000714 if len(I) != 2:
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000715 print "Error: Rows in the ProjectMapFile should have 2 entries."
716 raise Exception()
George Karpenkovbf92c442017-10-24 23:52:48 +0000717 if I[1] not in ('0', '1', '2'):
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000718 print "Error: Second entry in the ProjectMapFile should be 0" \
George Karpenkova8076602017-10-02 17:59:12 +0000719 " (single file), 1 (project), or 2(single file c++11)."
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000720 raise Exception()
721
George Karpenkovf37d3a52018-02-08 21:22:42 +0000722def singleThreadedTestAll(ProjectsToTest):
723 """
724 Run all projects.
725 :return: whether tests have passed.
726 """
727 Success = True
728 for ProjArgs in ProjectsToTest:
729 Success &= testProject(*ProjArgs)
730 return Success
George Karpenkova8076602017-10-02 17:59:12 +0000731
George Karpenkovf37d3a52018-02-08 21:22:42 +0000732def multiThreadedTestAll(ProjectsToTest, Jobs):
733 """
734 Run each project in a separate thread.
735
736 This is OK despite GIL, as testing is blocked
737 on launching external processes.
738
739 :return: whether tests have passed.
740 """
741 TasksQueue = Queue.Queue()
742
743 for ProjArgs in ProjectsToTest:
744 TasksQueue.put(ProjArgs)
745
746 ResultsDiffer = threading.Event()
747 FailureFlag = threading.Event()
748
749 for i in range(Jobs):
750 T = TestProjectThread(TasksQueue, ResultsDiffer, FailureFlag)
751 T.start()
752
753 # Required to handle Ctrl-C gracefully.
754 while TasksQueue.unfinished_tasks:
755 time.sleep(0.1) # Seconds.
756 if FailureFlag.is_set():
757 Local.stderr.write("Test runner crashed\n")
758 sys.exit(1)
759 return not ResultsDiffer.is_set()
760
761
762def testAll(Args):
763 ProjectsToTest = []
764
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000765 with projectFileHandler() as PMapFile:
766 validateProjectFile(PMapFile)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000767
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000768 # Test the projects.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000769 for (ProjName, ProjBuildMode) in iterateOverProjects(PMapFile):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000770 ProjectsToTest.append((ProjName,
771 int(ProjBuildMode),
772 Args.regenerate,
773 Args.strictness))
774 if Args.jobs <= 1:
775 return singleThreadedTestAll(ProjectsToTest)
776 else:
777 return multiThreadedTestAll(ProjectsToTest, Args.jobs)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000778
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000779
Anna Zaksf0c41162011-10-06 23:26:27 +0000780if __name__ == '__main__':
Gabor Horvath93fde942015-06-30 15:31:17 +0000781 # Parse command line arguments.
George Karpenkova8076602017-10-02 17:59:12 +0000782 Parser = argparse.ArgumentParser(
783 description='Test the Clang Static Analyzer.')
Gabor Horvath93fde942015-06-30 15:31:17 +0000784 Parser.add_argument('--strictness', dest='strictness', type=int, default=0,
George Karpenkova8076602017-10-02 17:59:12 +0000785 help='0 to fail on runtime errors, 1 to fail when the \
786 number of found bugs are different from the \
787 reference, 2 to fail on any difference from the \
788 reference. Default is 0.')
789 Parser.add_argument('-r', dest='regenerate', action='store_true',
790 default=False, help='Regenerate reference output.')
George Karpenkovf37d3a52018-02-08 21:22:42 +0000791 Parser.add_argument('-j', '--jobs', dest='jobs', type=int,
792 default=0,
793 help='Number of projects to test concurrently')
Gabor Horvath93fde942015-06-30 15:31:17 +0000794 Args = Parser.parse_args()
795
George Karpenkovf37d3a52018-02-08 21:22:42 +0000796 TestsPassed = testAll(Args)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000797 if not TestsPassed:
George Karpenkovbf92c442017-10-24 23:52:48 +0000798 print "ERROR: Tests failed."
George Karpenkov65839bd2017-10-26 01:13:22 +0000799 sys.exit(42)