blob: fbf3882c03548e3c5084612ba92c3dd04ba57bb6 [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
George Karpenkov13d37482018-07-30 23:01:20 +000063###############################################################################
Ted Kremenek42c14422012-08-28 20:40:02 +000064# Helper functions.
George Karpenkov13d37482018-07-30 23:01:20 +000065###############################################################################
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 Karpenkov13d37482018-07-30 23:01:20 +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
George Karpenkov13d37482018-07-30 23:01:20 +0000111###############################################################################
Ted Kremenek42c14422012-08-28 20:40:02 +0000112# Configuration setup.
George Karpenkov13d37482018-07-30 23:01:20 +0000113###############################################################################
Ted Kremenek42c14422012-08-28 20:40:02 +0000114
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
George Karpenkov13d37482018-07-30 23:01:20 +0000188###############################################################################
Ted Kremenek42c14422012-08-28 20:40:02 +0000189# Test harness logic.
George Karpenkov13d37482018-07-30 23:01:20 +0000190###############################################################################
Ted Kremenek42c14422012-08-28 20:40:02 +0000191
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
George Karpenkovac986832018-10-02 21:19:23 +0000258def generateAnalyzerConfig(Args):
259 Out = "serialize-stats=true,stable-report-filename=true"
260 if Args.extra_analyzer_config:
261 Out += "," + Args.extra_analyzer_config
262 return Out
263
264
George Karpenkov47e54932018-09-27 01:10:59 +0000265def runScanBuild(Args, Dir, SBOutputDir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000266 """
267 Build the project with scan-build by reading in the commands and
268 prefixing them with the scan-build options.
269 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000270 BuildScriptPath = os.path.join(Dir, BuildScript)
271 if not os.path.exists(BuildScriptPath):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000272 Local.stderr.write(
273 "Error: build script is not defined: %s\n" % BuildScriptPath)
George Karpenkov65839bd2017-10-26 01:13:22 +0000274 sys.exit(1)
Devin Coughlinf3695c82015-09-16 01:52:32 +0000275
276 AllCheckers = Checkers
George Karpenkova8076602017-10-02 17:59:12 +0000277 if 'SA_ADDITIONAL_CHECKERS' in os.environ:
Devin Coughlinf3695c82015-09-16 01:52:32 +0000278 AllCheckers = AllCheckers + ',' + os.environ['SA_ADDITIONAL_CHECKERS']
279
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000280 # Run scan-build from within the patched source directory.
281 SBCwd = os.path.join(Dir, PatchedSourceDirName)
282
George Karpenkova8076602017-10-02 17:59:12 +0000283 SBOptions = "--use-analyzer '%s' " % Clang
Devin Coughlin86f61a92016-01-22 18:45:22 +0000284 SBOptions += "-plist-html -o '%s' " % SBOutputDir
Devin Coughlinf3695c82015-09-16 01:52:32 +0000285 SBOptions += "-enable-checker " + AllCheckers + " "
Jordan Roseb18179d2013-01-24 23:07:59 +0000286 SBOptions += "--keep-empty "
George Karpenkovac986832018-10-02 21:19:23 +0000287 SBOptions += "-analyzer-config '%s' " % generateAnalyzerConfig(Args)
Mikhail R. Gadelhaafc62b72018-06-27 14:39:41 +0000288
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000289 # Always use ccc-analyze to ensure that we can locate the failures
Anna Zaks7b4f8a42013-05-31 02:31:09 +0000290 # directory.
291 SBOptions += "--override-compiler "
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000292 ExtraEnv = {}
Anna Zaksf0c41162011-10-06 23:26:27 +0000293 try:
294 SBCommandFile = open(BuildScriptPath, "r")
295 SBPrefix = "scan-build " + SBOptions + " "
296 for Command in SBCommandFile:
Jordan Rose7bd91862013-09-06 16:12:41 +0000297 Command = Command.strip()
Gabor Horvath93fde942015-06-30 15:31:17 +0000298 if len(Command) == 0:
George Karpenkova8076602017-10-02 17:59:12 +0000299 continue
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000300
301 # Custom analyzer invocation specified by project.
302 # Communicate required information using environment variables
303 # instead.
304 if Command == NoPrefixCmd:
305 SBPrefix = ""
306 ExtraEnv['OUTPUT'] = SBOutputDir
George Karpenkov6e4ddf42018-07-02 17:10:40 +0000307 ExtraEnv['CC'] = Clang
George Karpenkovac986832018-10-02 21:19:23 +0000308 ExtraEnv['ANALYZER_CONFIG'] = generateAnalyzerConfig(Args)
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000309 continue
310
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000311 # If using 'make', auto imply a -jX argument
312 # to speed up analysis. xcodebuild will
313 # automatically use the maximum number of cores.
Jordan Rose64e4cf02012-11-26 19:59:57 +0000314 if (Command.startswith("make ") or Command == "make") and \
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000315 "-j" not in Command:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000316 Command += " -j%d" % MaxJobs
Anna Zaksf0c41162011-10-06 23:26:27 +0000317 SBCommand = SBPrefix + Command
George Karpenkovf37d3a52018-02-08 21:22:42 +0000318
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000319 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000320 Local.stdout.write(" Executing: %s\n" % (SBCommand,))
George Karpenkova8076602017-10-02 17:59:12 +0000321 check_call(SBCommand, cwd=SBCwd,
322 stderr=PBuildLogFile,
323 stdout=PBuildLogFile,
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000324 env=dict(os.environ, **ExtraEnv),
George Karpenkova8076602017-10-02 17:59:12 +0000325 shell=True)
George Karpenkove58044d2017-10-27 22:39:54 +0000326 except CalledProcessError:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000327 Local.stderr.write("Error: scan-build failed. Its output was: \n")
George Karpenkove58044d2017-10-27 22:39:54 +0000328 PBuildLogFile.seek(0)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000329 shutil.copyfileobj(PBuildLogFile, Local.stderr)
George Karpenkove58044d2017-10-27 22:39:54 +0000330 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000331
George Karpenkova8076602017-10-02 17:59:12 +0000332
George Karpenkovac986832018-10-02 21:19:23 +0000333def runAnalyzePreprocessed(Args, Dir, SBOutputDir, Mode):
George Karpenkova8076602017-10-02 17:59:12 +0000334 """
335 Run analysis on a set of preprocessed files.
336 """
Anna Zaks4720a732011-11-05 05:20:48 +0000337 if os.path.exists(os.path.join(Dir, BuildScript)):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000338 Local.stderr.write(
339 "Error: The preprocessed files project should not contain %s\n" % (
340 BuildScript))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000341 raise Exception()
Anna Zaks4720a732011-11-05 05:20:48 +0000342
Devin Coughlinbace0322015-09-14 21:22:24 +0000343 CmdPrefix = Clang + " -cc1 "
344
345 # For now, we assume the preprocessed files should be analyzed
346 # with the OS X SDK.
George Karpenkovbf92c442017-10-24 23:52:48 +0000347 SDKPath = SATestUtils.getSDKPath("macosx")
Devin Coughlinbace0322015-09-14 21:22:24 +0000348 if SDKPath is not None:
George Karpenkova8076602017-10-02 17:59:12 +0000349 CmdPrefix += "-isysroot " + SDKPath + " "
Devin Coughlinbace0322015-09-14 21:22:24 +0000350
351 CmdPrefix += "-analyze -analyzer-output=plist -w "
George Karpenkova8076602017-10-02 17:59:12 +0000352 CmdPrefix += "-analyzer-checker=" + Checkers
353 CmdPrefix += " -fcxx-exceptions -fblocks "
George Karpenkovd3b08462018-10-02 22:31:44 +0000354 CmdPrefix += " -analyzer-config %s " % generateAnalyzerConfig(Args)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000355
George Karpenkova8076602017-10-02 17:59:12 +0000356 if (Mode == 2):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000357 CmdPrefix += "-std=c++11 "
358
Anna Zaks4720a732011-11-05 05:20:48 +0000359 PlistPath = os.path.join(Dir, SBOutputDir, "date")
George Karpenkova8076602017-10-02 17:59:12 +0000360 FailPath = os.path.join(PlistPath, "failures")
361 os.makedirs(FailPath)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000362
Anna Zaks4720a732011-11-05 05:20:48 +0000363 for FullFileName in glob.glob(Dir + "/*"):
364 FileName = os.path.basename(FullFileName)
365 Failed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000366
Anna Zaks4720a732011-11-05 05:20:48 +0000367 # Only run the analyzes on supported files.
George Karpenkovbf92c442017-10-24 23:52:48 +0000368 if SATestUtils.hasNoExtension(FileName):
Anna Zaks4720a732011-11-05 05:20:48 +0000369 continue
George Karpenkovbf92c442017-10-24 23:52:48 +0000370 if not SATestUtils.isValidSingleInputFile(FileName):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000371 Local.stderr.write(
372 "Error: Invalid single input file %s.\n" % (FullFileName,))
Anna Zaks4720a732011-11-05 05:20:48 +0000373 raise Exception()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000374
Anna Zaks4720a732011-11-05 05:20:48 +0000375 # Build and call the analyzer command.
Devin Coughlinab95cd22016-01-22 07:08:06 +0000376 OutputOption = "-o '%s.plist' " % os.path.join(PlistPath, FileName)
377 Command = CmdPrefix + OutputOption + ("'%s'" % FileName)
Anna Zaks4720a732011-11-05 05:20:48 +0000378 LogFile = open(os.path.join(FailPath, FileName + ".stderr.txt"), "w+b")
379 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000380 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000381 Local.stdout.write(" Executing: %s\n" % (Command,))
George Karpenkova8076602017-10-02 17:59:12 +0000382 check_call(Command, cwd=Dir, stderr=LogFile,
383 stdout=LogFile,
384 shell=True)
Anna Zaks4720a732011-11-05 05:20:48 +0000385 except CalledProcessError, e:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000386 Local.stderr.write("Error: Analyzes of %s failed. "
387 "See %s for details."
388 "Error code %d.\n" % (
389 FullFileName, LogFile.name, e.returncode))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000390 Failed = True
Anna Zaks4720a732011-11-05 05:20:48 +0000391 finally:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000392 LogFile.close()
393
Anna Zaks4720a732011-11-05 05:20:48 +0000394 # If command did not fail, erase the log file.
George Karpenkova8076602017-10-02 17:59:12 +0000395 if not Failed:
396 os.remove(LogFile.name)
397
Anna Zaks4720a732011-11-05 05:20:48 +0000398
Devin Coughlin9ea80332016-01-23 01:09:07 +0000399def getBuildLogPath(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000400 return os.path.join(SBOutputDir, LogFolderName, BuildLogName)
401
Devin Coughlin9ea80332016-01-23 01:09:07 +0000402
403def removeLogFile(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000404 BuildLogPath = getBuildLogPath(SBOutputDir)
405 # Clean up the log file.
406 if (os.path.exists(BuildLogPath)):
407 RmCommand = "rm '%s'" % BuildLogPath
408 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000409 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
George Karpenkova8076602017-10-02 17:59:12 +0000410 check_call(RmCommand, shell=True)
411
Devin Coughlin9ea80332016-01-23 01:09:07 +0000412
George Karpenkov47e54932018-09-27 01:10:59 +0000413def buildProject(Args, Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000414 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000415
Devin Coughlin9ea80332016-01-23 01:09:07 +0000416 BuildLogPath = getBuildLogPath(SBOutputDir)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000417 Local.stdout.write("Log file: %s\n" % (BuildLogPath,))
418 Local.stdout.write("Output directory: %s\n" % (SBOutputDir, ))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000419
Devin Coughlin9ea80332016-01-23 01:09:07 +0000420 removeLogFile(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000421
Anna Zaks4720a732011-11-05 05:20:48 +0000422 # Clean up scan build results.
George Karpenkova8076602017-10-02 17:59:12 +0000423 if (os.path.exists(SBOutputDir)):
Devin Coughlinab95cd22016-01-22 07:08:06 +0000424 RmCommand = "rm -r '%s'" % SBOutputDir
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000425 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000426 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
427 check_call(RmCommand, shell=True, stdout=Local.stdout,
428 stderr=Local.stderr)
Anna Zaks4720a732011-11-05 05:20:48 +0000429 assert(not os.path.exists(SBOutputDir))
430 os.makedirs(os.path.join(SBOutputDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000431
Anna Zaks4720a732011-11-05 05:20:48 +0000432 # Build and analyze the project.
George Karpenkov0a6dba72017-10-27 22:52:36 +0000433 with open(BuildLogPath, "wb+") as PBuildLogFile:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000434 if (ProjectBuildMode == 1):
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000435 downloadAndPatch(Dir, PBuildLogFile)
436 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov47e54932018-09-27 01:10:59 +0000437 runScanBuild(Args, Dir, SBOutputDir, PBuildLogFile)
Anna Zaks4720a732011-11-05 05:20:48 +0000438 else:
George Karpenkovac986832018-10-02 21:19:23 +0000439 runAnalyzePreprocessed(Args, Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000440
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000441 if IsReferenceBuild:
Anna Zaks42a44632011-11-02 20:46:50 +0000442 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000443 normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000444
George Karpenkovf37d3a52018-02-08 21:22:42 +0000445 Local.stdout.write("Build complete (time: %.2f). "
446 "See the log for more details: %s\n" % (
447 (time.time() - TBegin), BuildLogPath))
George Karpenkova8076602017-10-02 17:59:12 +0000448
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000449
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000450def normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode):
451 """
452 Make the absolute paths relative in the reference results.
453 """
454 for (DirPath, Dirnames, Filenames) in os.walk(SBOutputDir):
455 for F in Filenames:
456 if (not F.endswith('plist')):
457 continue
458 Plist = os.path.join(DirPath, F)
459 Data = plistlib.readPlist(Plist)
460 PathPrefix = Dir
461 if (ProjectBuildMode == 1):
462 PathPrefix = os.path.join(Dir, PatchedSourceDirName)
George Karpenkova8076602017-10-02 17:59:12 +0000463 Paths = [SourceFile[len(PathPrefix) + 1:]
464 if SourceFile.startswith(PathPrefix)
465 else SourceFile for SourceFile in Data['files']]
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000466 Data['files'] = Paths
George Karpenkov318cd1f2017-10-24 23:52:46 +0000467
468 # Remove transient fields which change from run to run.
469 for Diag in Data['diagnostics']:
470 if 'HTMLDiagnostics_files' in Diag:
471 Diag.pop('HTMLDiagnostics_files')
472 if 'clang_version' in Data:
473 Data.pop('clang_version')
474
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000475 plistlib.writePlist(Data, Plist)
476
George Karpenkova8076602017-10-02 17:59:12 +0000477
Anna Zaksf0c41162011-10-06 23:26:27 +0000478def CleanUpEmptyPlists(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000479 """
480 A plist file is created for each call to the analyzer(each source file).
481 We are only interested on the once that have bug reports,
482 so delete the rest.
483 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000484 for F in glob.glob(SBOutputDir + "/*/*.plist"):
485 P = os.path.join(SBOutputDir, F)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000486
Anna Zaksf0c41162011-10-06 23:26:27 +0000487 Data = plistlib.readPlist(P)
488 # Delete empty reports.
489 if not Data['files']:
490 os.remove(P)
491 continue
492
George Karpenkova8076602017-10-02 17:59:12 +0000493
George Karpenkov3c128cb2017-10-30 19:40:33 +0000494def CleanUpEmptyFolders(SBOutputDir):
495 """
496 Remove empty folders from results, as git would not store them.
497 """
498 Subfolders = glob.glob(SBOutputDir + "/*")
499 for Folder in Subfolders:
500 if not os.listdir(Folder):
501 os.removedirs(Folder)
502
503
Anna Zaksf0c41162011-10-06 23:26:27 +0000504def checkBuild(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000505 """
506 Given the scan-build output directory, checks if the build failed
507 (by searching for the failures directories). If there are failures, it
508 creates a summary file in the output directory.
509
510 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000511 # Check if there are failures.
512 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt")
George Karpenkova8076602017-10-02 17:59:12 +0000513 TotalFailed = len(Failures)
Anna Zaksf0c41162011-10-06 23:26:27 +0000514 if TotalFailed == 0:
Jordan Rose9858b122012-08-31 00:36:30 +0000515 CleanUpEmptyPlists(SBOutputDir)
George Karpenkov3c128cb2017-10-30 19:40:33 +0000516 CleanUpEmptyFolders(SBOutputDir)
Jordan Rose9858b122012-08-31 00:36:30 +0000517 Plists = glob.glob(SBOutputDir + "/*/*.plist")
George Karpenkovf37d3a52018-02-08 21:22:42 +0000518 Local.stdout.write(
519 "Number of bug reports (non-empty plist files) produced: %d\n" %
520 len(Plists))
George Karpenkova8076602017-10-02 17:59:12 +0000521 return
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000522
George Karpenkovf37d3a52018-02-08 21:22:42 +0000523 Local.stderr.write("Error: analysis failed.\n")
524 Local.stderr.write("Total of %d failures discovered.\n" % TotalFailed)
George Karpenkovff555ce2017-10-26 19:00:22 +0000525 if TotalFailed > NumOfFailuresInSummary:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000526 Local.stderr.write(
527 "See the first %d below.\n" % NumOfFailuresInSummary)
Anna Zaksf0c41162011-10-06 23:26:27 +0000528 # TODO: Add a line "See the results folder for more."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000529
George Karpenkovff555ce2017-10-26 19:00:22 +0000530 Idx = 0
531 for FailLogPathI in Failures:
532 if Idx >= NumOfFailuresInSummary:
533 break
534 Idx += 1
George Karpenkovf37d3a52018-02-08 21:22:42 +0000535 Local.stderr.write("\n-- Error #%d -----------\n" % Idx)
George Karpenkovff555ce2017-10-26 19:00:22 +0000536 with open(FailLogPathI, "r") as FailLogI:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000537 shutil.copyfileobj(FailLogI, Local.stdout)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000538
George Karpenkov65839bd2017-10-26 01:13:22 +0000539 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000540
Anna Zaksf0c41162011-10-06 23:26:27 +0000541
George Karpenkova8076602017-10-02 17:59:12 +0000542def runCmpResults(Dir, Strictness=0):
543 """
544 Compare the warnings produced by scan-build.
545 Strictness defines the success criteria for the test:
546 0 - success if there are no crashes or analyzer failure.
547 1 - success if there are no difference in the number of reported bugs.
548 2 - success if all the bug reports are identical.
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000549
550 :return success: Whether tests pass according to the Strictness
551 criteria.
George Karpenkova8076602017-10-02 17:59:12 +0000552 """
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000553 TestsPassed = True
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000554 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000555
556 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName)
557 NewDir = os.path.join(Dir, SBOutputDirName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000558
Anna Zaksf0c41162011-10-06 23:26:27 +0000559 # We have to go one level down the directory tree.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000560 RefList = glob.glob(RefDir + "/*")
Anna Zaksf0c41162011-10-06 23:26:27 +0000561 NewList = glob.glob(NewDir + "/*")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000562
Jordan Rosec7b992e2013-06-10 19:34:30 +0000563 # Log folders are also located in the results dir, so ignore them.
564 RefLogDir = os.path.join(RefDir, LogFolderName)
565 if RefLogDir in RefList:
566 RefList.remove(RefLogDir)
Anna Zaks4720a732011-11-05 05:20:48 +0000567 NewList.remove(os.path.join(NewDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000568
George Karpenkov65839bd2017-10-26 01:13:22 +0000569 if len(RefList) != len(NewList):
570 print "Mismatch in number of results folders: %s vs %s" % (
571 RefList, NewList)
572 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000573
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000574 # There might be more then one folder underneath - one per each scan-build
Anna Zaksf0c41162011-10-06 23:26:27 +0000575 # command (Ex: one for configure and one for make).
576 if (len(RefList) > 1):
577 # Assume that the corresponding folders have the same names.
578 RefList.sort()
579 NewList.sort()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000580
Anna Zaksf0c41162011-10-06 23:26:27 +0000581 # Iterate and find the differences.
Anna Zaks767d3562011-11-08 19:56:31 +0000582 NumDiffs = 0
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000583 PairList = zip(RefList, NewList)
584 for P in PairList:
585 RefDir = P[0]
Anna Zaksf0c41162011-10-06 23:26:27 +0000586 NewDir = P[1]
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000587
588 assert(RefDir != NewDir)
589 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000590 Local.stdout.write(" Comparing Results: %s %s\n" % (
591 RefDir, NewDir))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000592
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000593 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
George Karpenkovfc782a32018-02-09 18:39:47 +0000594 Opts, Args = CmpRuns.generate_option_parser().parse_args(
George Karpenkov192d9a12018-02-12 22:13:01 +0000595 ["--rootA", "", "--rootB", PatchedSourceDirPath])
Anna Zaksf0c41162011-10-06 23:26:27 +0000596 # Scan the results, delete empty plist files.
Gabor Horvath93fde942015-06-30 15:31:17 +0000597 NumDiffs, ReportsInRef, ReportsInNew = \
George Karpenkovb7120c92018-02-13 23:36:01 +0000598 CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts,
599 deleteEmpty=False,
600 Stdout=Local.stdout)
George Karpenkova8076602017-10-02 17:59:12 +0000601 if (NumDiffs > 0):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000602 Local.stdout.write("Warning: %s differences in diagnostics.\n"
603 % NumDiffs)
Gabor Horvath93fde942015-06-30 15:31:17 +0000604 if Strictness >= 2 and NumDiffs > 0:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000605 Local.stdout.write("Error: Diffs found in strict mode (2).\n")
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000606 TestsPassed = False
Gabor Horvath93fde942015-06-30 15:31:17 +0000607 elif Strictness >= 1 and ReportsInRef != ReportsInNew:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000608 Local.stdout.write("Error: The number of results are different " +
609 " strict mode (1).\n")
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000610 TestsPassed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000611
George Karpenkovf37d3a52018-02-08 21:22:42 +0000612 Local.stdout.write("Diagnostic comparison complete (time: %.2f).\n" % (
613 time.time() - TBegin))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000614 return TestsPassed
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000615
George Karpenkova8076602017-10-02 17:59:12 +0000616
Devin Coughlin9ea80332016-01-23 01:09:07 +0000617def cleanupReferenceResults(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000618 """
619 Delete html, css, and js files from reference results. These can
620 include multiple copies of the benchmark source and so get very large.
621 """
Devin Coughlin9ea80332016-01-23 01:09:07 +0000622 Extensions = ["html", "css", "js"]
623 for E in Extensions:
624 for F in glob.glob("%s/*/*.%s" % (SBOutputDir, E)):
625 P = os.path.join(SBOutputDir, F)
626 RmCommand = "rm '%s'" % P
627 check_call(RmCommand, shell=True)
628
629 # Remove the log file. It leaks absolute path names.
630 removeLogFile(SBOutputDir)
631
George Karpenkova8076602017-10-02 17:59:12 +0000632
George Karpenkovf37d3a52018-02-08 21:22:42 +0000633class TestProjectThread(threading.Thread):
George Karpenkov47e54932018-09-27 01:10:59 +0000634 def __init__(self, Args, TasksQueue, ResultsDiffer, FailureFlag):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000635 """
636 :param ResultsDiffer: Used to signify that results differ from
637 the canonical ones.
638 :param FailureFlag: Used to signify a failure during the run.
639 """
George Karpenkov47e54932018-09-27 01:10:59 +0000640 self.Args = Args
George Karpenkovf37d3a52018-02-08 21:22:42 +0000641 self.TasksQueue = TasksQueue
642 self.ResultsDiffer = ResultsDiffer
643 self.FailureFlag = FailureFlag
644 super(TestProjectThread, self).__init__()
645
646 # Needed to gracefully handle interrupts with Ctrl-C
647 self.daemon = True
648
649 def run(self):
650 while not self.TasksQueue.empty():
651 try:
652 ProjArgs = self.TasksQueue.get()
653 Logger = logging.getLogger(ProjArgs[0])
654 Local.stdout = StreamToLogger(Logger, logging.INFO)
655 Local.stderr = StreamToLogger(Logger, logging.ERROR)
George Karpenkov47e54932018-09-27 01:10:59 +0000656 if not testProject(Args, *ProjArgs):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000657 self.ResultsDiffer.set()
658 self.TasksQueue.task_done()
659 except:
660 self.FailureFlag.set()
661 raise
662
663
George Karpenkov47e54932018-09-27 01:10:59 +0000664def testProject(Args, ID, ProjectBuildMode, IsReferenceBuild=False, Strictness=0):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000665 """
666 Test a given project.
667 :return TestsPassed: Whether tests have passed according
668 to the :param Strictness: criteria.
669 """
George Karpenkovf37d3a52018-02-08 21:22:42 +0000670 Local.stdout.write(" \n\n--- Building project %s\n" % (ID,))
Anna Zaks4720a732011-11-05 05:20:48 +0000671
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000672 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000673
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000674 Dir = getProjectDir(ID)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000675 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000676 Local.stdout.write(" Build directory: %s.\n" % (Dir,))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000677
Anna Zaksf0c41162011-10-06 23:26:27 +0000678 # Set the build results directory.
Jordan Rose01ac5722012-06-01 16:24:38 +0000679 RelOutputDir = getSBOutputDirName(IsReferenceBuild)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000680 SBOutputDir = os.path.join(Dir, RelOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000681
George Karpenkov47e54932018-09-27 01:10:59 +0000682 buildProject(Args, Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild)
Anna Zaksf0c41162011-10-06 23:26:27 +0000683
684 checkBuild(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000685
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000686 if IsReferenceBuild:
Devin Coughlin9ea80332016-01-23 01:09:07 +0000687 cleanupReferenceResults(SBOutputDir)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000688 TestsPassed = True
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000689 else:
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000690 TestsPassed = runCmpResults(Dir, Strictness)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000691
George Karpenkovf37d3a52018-02-08 21:22:42 +0000692 Local.stdout.write("Completed tests for project %s (time: %.2f).\n" % (
693 ID, (time.time() - TBegin)))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000694 return TestsPassed
George Karpenkova8076602017-10-02 17:59:12 +0000695
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000696
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000697def projectFileHandler():
698 return open(getProjectMapPath(), "rb")
699
George Karpenkova8076602017-10-02 17:59:12 +0000700
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000701def iterateOverProjects(PMapFile):
702 """
703 Iterate over all projects defined in the project file handler `PMapFile`
704 from the start.
705 """
706 PMapFile.seek(0)
George Karpenkova8076602017-10-02 17:59:12 +0000707 for I in csv.reader(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000708 if (SATestUtils.isCommentCSVLine(I)):
George Karpenkova8076602017-10-02 17:59:12 +0000709 continue
710 yield I
711
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000712
713def validateProjectFile(PMapFile):
714 """
715 Validate project file.
716 """
717 for I in iterateOverProjects(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000718 if len(I) != 2:
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000719 print "Error: Rows in the ProjectMapFile should have 2 entries."
720 raise Exception()
George Karpenkovbf92c442017-10-24 23:52:48 +0000721 if I[1] not in ('0', '1', '2'):
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000722 print "Error: Second entry in the ProjectMapFile should be 0" \
George Karpenkova8076602017-10-02 17:59:12 +0000723 " (single file), 1 (project), or 2(single file c++11)."
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000724 raise Exception()
725
George Karpenkov47e54932018-09-27 01:10:59 +0000726def singleThreadedTestAll(Args, ProjectsToTest):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000727 """
728 Run all projects.
729 :return: whether tests have passed.
730 """
731 Success = True
732 for ProjArgs in ProjectsToTest:
George Karpenkov47e54932018-09-27 01:10:59 +0000733 Success &= testProject(Args, *ProjArgs)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000734 return Success
George Karpenkova8076602017-10-02 17:59:12 +0000735
George Karpenkov47e54932018-09-27 01:10:59 +0000736def multiThreadedTestAll(Args, ProjectsToTest, Jobs):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000737 """
738 Run each project in a separate thread.
739
740 This is OK despite GIL, as testing is blocked
741 on launching external processes.
742
743 :return: whether tests have passed.
744 """
745 TasksQueue = Queue.Queue()
746
747 for ProjArgs in ProjectsToTest:
748 TasksQueue.put(ProjArgs)
749
750 ResultsDiffer = threading.Event()
751 FailureFlag = threading.Event()
752
753 for i in range(Jobs):
George Karpenkov47e54932018-09-27 01:10:59 +0000754 T = TestProjectThread(Args, TasksQueue, ResultsDiffer, FailureFlag)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000755 T.start()
756
757 # Required to handle Ctrl-C gracefully.
758 while TasksQueue.unfinished_tasks:
759 time.sleep(0.1) # Seconds.
760 if FailureFlag.is_set():
761 Local.stderr.write("Test runner crashed\n")
762 sys.exit(1)
763 return not ResultsDiffer.is_set()
764
765
766def testAll(Args):
767 ProjectsToTest = []
768
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000769 with projectFileHandler() as PMapFile:
770 validateProjectFile(PMapFile)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000771
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000772 # Test the projects.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000773 for (ProjName, ProjBuildMode) in iterateOverProjects(PMapFile):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000774 ProjectsToTest.append((ProjName,
775 int(ProjBuildMode),
776 Args.regenerate,
777 Args.strictness))
778 if Args.jobs <= 1:
George Karpenkov47e54932018-09-27 01:10:59 +0000779 return singleThreadedTestAll(Args, ProjectsToTest)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000780 else:
George Karpenkov47e54932018-09-27 01:10:59 +0000781 return multiThreadedTestAll(Args, ProjectsToTest, Args.jobs)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000782
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000783
Anna Zaksf0c41162011-10-06 23:26:27 +0000784if __name__ == '__main__':
Gabor Horvath93fde942015-06-30 15:31:17 +0000785 # Parse command line arguments.
George Karpenkova8076602017-10-02 17:59:12 +0000786 Parser = argparse.ArgumentParser(
787 description='Test the Clang Static Analyzer.')
Gabor Horvath93fde942015-06-30 15:31:17 +0000788 Parser.add_argument('--strictness', dest='strictness', type=int, default=0,
George Karpenkova8076602017-10-02 17:59:12 +0000789 help='0 to fail on runtime errors, 1 to fail when the \
790 number of found bugs are different from the \
791 reference, 2 to fail on any difference from the \
792 reference. Default is 0.')
793 Parser.add_argument('-r', dest='regenerate', action='store_true',
794 default=False, help='Regenerate reference output.')
George Karpenkovf37d3a52018-02-08 21:22:42 +0000795 Parser.add_argument('-j', '--jobs', dest='jobs', type=int,
796 default=0,
797 help='Number of projects to test concurrently')
George Karpenkovac986832018-10-02 21:19:23 +0000798 Parser.add_argument('--extra-analyzer-config', dest='extra_analyzer_config',
799 type=str,
800 default="",
801 help="Arguments passed to to -analyzer-config")
Gabor Horvath93fde942015-06-30 15:31:17 +0000802 Args = Parser.parse_args()
803
George Karpenkovf37d3a52018-02-08 21:22:42 +0000804 TestsPassed = testAll(Args)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000805 if not TestsPassed:
George Karpenkovbf92c442017-10-24 23:52:48 +0000806 print "ERROR: Tests failed."
George Karpenkov65839bd2017-10-26 01:13:22 +0000807 sys.exit(42)