blob: 3c886a7d6a4ed7fbf6e4f255e7cd445e9dbfb1e6 [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 Karpenkov47e54932018-09-27 01:10:59 +0000258def runScanBuild(Args, 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 Karpenkov47e54932018-09-27 01:10:59 +0000284 AnalyzerConfigSerialized = ",".join(
285 "%s=%s" % (key, value) for (key, value) in AnalyzerConfig)
286 if Args.extra_args:
287 AnalyzerConfigSerialized += "," + Args.extra_args
288 SBOptions += "-analyzer-config '%s' " % AnalyzerConfigSerialized
Mikhail R. Gadelhaafc62b72018-06-27 14:39:41 +0000289
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000290 # Always use ccc-analyze to ensure that we can locate the failures
Anna Zaks7b4f8a42013-05-31 02:31:09 +0000291 # directory.
292 SBOptions += "--override-compiler "
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000293 ExtraEnv = {}
Anna Zaksf0c41162011-10-06 23:26:27 +0000294 try:
295 SBCommandFile = open(BuildScriptPath, "r")
296 SBPrefix = "scan-build " + SBOptions + " "
297 for Command in SBCommandFile:
Jordan Rose7bd91862013-09-06 16:12:41 +0000298 Command = Command.strip()
Gabor Horvath93fde942015-06-30 15:31:17 +0000299 if len(Command) == 0:
George Karpenkova8076602017-10-02 17:59:12 +0000300 continue
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000301
302 # Custom analyzer invocation specified by project.
303 # Communicate required information using environment variables
304 # instead.
305 if Command == NoPrefixCmd:
306 SBPrefix = ""
307 ExtraEnv['OUTPUT'] = SBOutputDir
George Karpenkov6e4ddf42018-07-02 17:10:40 +0000308 ExtraEnv['CC'] = Clang
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
Anna Zaksa2f970b2012-09-06 23:30:27 +0000333def runAnalyzePreprocessed(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 "
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000354
George Karpenkova8076602017-10-02 17:59:12 +0000355 if (Mode == 2):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000356 CmdPrefix += "-std=c++11 "
357
Anna Zaks4720a732011-11-05 05:20:48 +0000358 PlistPath = os.path.join(Dir, SBOutputDir, "date")
George Karpenkova8076602017-10-02 17:59:12 +0000359 FailPath = os.path.join(PlistPath, "failures")
360 os.makedirs(FailPath)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000361
Anna Zaks4720a732011-11-05 05:20:48 +0000362 for FullFileName in glob.glob(Dir + "/*"):
363 FileName = os.path.basename(FullFileName)
364 Failed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000365
Anna Zaks4720a732011-11-05 05:20:48 +0000366 # Only run the analyzes on supported files.
George Karpenkovbf92c442017-10-24 23:52:48 +0000367 if SATestUtils.hasNoExtension(FileName):
Anna Zaks4720a732011-11-05 05:20:48 +0000368 continue
George Karpenkovbf92c442017-10-24 23:52:48 +0000369 if not SATestUtils.isValidSingleInputFile(FileName):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000370 Local.stderr.write(
371 "Error: Invalid single input file %s.\n" % (FullFileName,))
Anna Zaks4720a732011-11-05 05:20:48 +0000372 raise Exception()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000373
Anna Zaks4720a732011-11-05 05:20:48 +0000374 # Build and call the analyzer command.
Devin Coughlinab95cd22016-01-22 07:08:06 +0000375 OutputOption = "-o '%s.plist' " % os.path.join(PlistPath, FileName)
376 Command = CmdPrefix + OutputOption + ("'%s'" % FileName)
Anna Zaks4720a732011-11-05 05:20:48 +0000377 LogFile = open(os.path.join(FailPath, FileName + ".stderr.txt"), "w+b")
378 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000379 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000380 Local.stdout.write(" Executing: %s\n" % (Command,))
George Karpenkova8076602017-10-02 17:59:12 +0000381 check_call(Command, cwd=Dir, stderr=LogFile,
382 stdout=LogFile,
383 shell=True)
Anna Zaks4720a732011-11-05 05:20:48 +0000384 except CalledProcessError, e:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000385 Local.stderr.write("Error: Analyzes of %s failed. "
386 "See %s for details."
387 "Error code %d.\n" % (
388 FullFileName, LogFile.name, e.returncode))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000389 Failed = True
Anna Zaks4720a732011-11-05 05:20:48 +0000390 finally:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000391 LogFile.close()
392
Anna Zaks4720a732011-11-05 05:20:48 +0000393 # If command did not fail, erase the log file.
George Karpenkova8076602017-10-02 17:59:12 +0000394 if not Failed:
395 os.remove(LogFile.name)
396
Anna Zaks4720a732011-11-05 05:20:48 +0000397
Devin Coughlin9ea80332016-01-23 01:09:07 +0000398def getBuildLogPath(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000399 return os.path.join(SBOutputDir, LogFolderName, BuildLogName)
400
Devin Coughlin9ea80332016-01-23 01:09:07 +0000401
402def removeLogFile(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000403 BuildLogPath = getBuildLogPath(SBOutputDir)
404 # Clean up the log file.
405 if (os.path.exists(BuildLogPath)):
406 RmCommand = "rm '%s'" % BuildLogPath
407 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000408 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
George Karpenkova8076602017-10-02 17:59:12 +0000409 check_call(RmCommand, shell=True)
410
Devin Coughlin9ea80332016-01-23 01:09:07 +0000411
George Karpenkov47e54932018-09-27 01:10:59 +0000412def buildProject(Args, Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000413 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000414
Devin Coughlin9ea80332016-01-23 01:09:07 +0000415 BuildLogPath = getBuildLogPath(SBOutputDir)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000416 Local.stdout.write("Log file: %s\n" % (BuildLogPath,))
417 Local.stdout.write("Output directory: %s\n" % (SBOutputDir, ))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000418
Devin Coughlin9ea80332016-01-23 01:09:07 +0000419 removeLogFile(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000420
Anna Zaks4720a732011-11-05 05:20:48 +0000421 # Clean up scan build results.
George Karpenkova8076602017-10-02 17:59:12 +0000422 if (os.path.exists(SBOutputDir)):
Devin Coughlinab95cd22016-01-22 07:08:06 +0000423 RmCommand = "rm -r '%s'" % SBOutputDir
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000424 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000425 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
426 check_call(RmCommand, shell=True, stdout=Local.stdout,
427 stderr=Local.stderr)
Anna Zaks4720a732011-11-05 05:20:48 +0000428 assert(not os.path.exists(SBOutputDir))
429 os.makedirs(os.path.join(SBOutputDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000430
Anna Zaks4720a732011-11-05 05:20:48 +0000431 # Build and analyze the project.
George Karpenkov0a6dba72017-10-27 22:52:36 +0000432 with open(BuildLogPath, "wb+") as PBuildLogFile:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000433 if (ProjectBuildMode == 1):
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000434 downloadAndPatch(Dir, PBuildLogFile)
435 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov47e54932018-09-27 01:10:59 +0000436 runScanBuild(Args, Dir, SBOutputDir, PBuildLogFile)
Anna Zaks4720a732011-11-05 05:20:48 +0000437 else:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000438 runAnalyzePreprocessed(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000439
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000440 if IsReferenceBuild:
Anna Zaks42a44632011-11-02 20:46:50 +0000441 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000442 normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000443
George Karpenkovf37d3a52018-02-08 21:22:42 +0000444 Local.stdout.write("Build complete (time: %.2f). "
445 "See the log for more details: %s\n" % (
446 (time.time() - TBegin), BuildLogPath))
George Karpenkova8076602017-10-02 17:59:12 +0000447
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000448
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000449def normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode):
450 """
451 Make the absolute paths relative in the reference results.
452 """
453 for (DirPath, Dirnames, Filenames) in os.walk(SBOutputDir):
454 for F in Filenames:
455 if (not F.endswith('plist')):
456 continue
457 Plist = os.path.join(DirPath, F)
458 Data = plistlib.readPlist(Plist)
459 PathPrefix = Dir
460 if (ProjectBuildMode == 1):
461 PathPrefix = os.path.join(Dir, PatchedSourceDirName)
George Karpenkova8076602017-10-02 17:59:12 +0000462 Paths = [SourceFile[len(PathPrefix) + 1:]
463 if SourceFile.startswith(PathPrefix)
464 else SourceFile for SourceFile in Data['files']]
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000465 Data['files'] = Paths
George Karpenkov318cd1f2017-10-24 23:52:46 +0000466
467 # Remove transient fields which change from run to run.
468 for Diag in Data['diagnostics']:
469 if 'HTMLDiagnostics_files' in Diag:
470 Diag.pop('HTMLDiagnostics_files')
471 if 'clang_version' in Data:
472 Data.pop('clang_version')
473
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000474 plistlib.writePlist(Data, Plist)
475
George Karpenkova8076602017-10-02 17:59:12 +0000476
Anna Zaksf0c41162011-10-06 23:26:27 +0000477def CleanUpEmptyPlists(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000478 """
479 A plist file is created for each call to the analyzer(each source file).
480 We are only interested on the once that have bug reports,
481 so delete the rest.
482 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000483 for F in glob.glob(SBOutputDir + "/*/*.plist"):
484 P = os.path.join(SBOutputDir, F)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000485
Anna Zaksf0c41162011-10-06 23:26:27 +0000486 Data = plistlib.readPlist(P)
487 # Delete empty reports.
488 if not Data['files']:
489 os.remove(P)
490 continue
491
George Karpenkova8076602017-10-02 17:59:12 +0000492
George Karpenkov3c128cb2017-10-30 19:40:33 +0000493def CleanUpEmptyFolders(SBOutputDir):
494 """
495 Remove empty folders from results, as git would not store them.
496 """
497 Subfolders = glob.glob(SBOutputDir + "/*")
498 for Folder in Subfolders:
499 if not os.listdir(Folder):
500 os.removedirs(Folder)
501
502
Anna Zaksf0c41162011-10-06 23:26:27 +0000503def checkBuild(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000504 """
505 Given the scan-build output directory, checks if the build failed
506 (by searching for the failures directories). If there are failures, it
507 creates a summary file in the output directory.
508
509 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000510 # Check if there are failures.
511 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt")
George Karpenkova8076602017-10-02 17:59:12 +0000512 TotalFailed = len(Failures)
Anna Zaksf0c41162011-10-06 23:26:27 +0000513 if TotalFailed == 0:
Jordan Rose9858b122012-08-31 00:36:30 +0000514 CleanUpEmptyPlists(SBOutputDir)
George Karpenkov3c128cb2017-10-30 19:40:33 +0000515 CleanUpEmptyFolders(SBOutputDir)
Jordan Rose9858b122012-08-31 00:36:30 +0000516 Plists = glob.glob(SBOutputDir + "/*/*.plist")
George Karpenkovf37d3a52018-02-08 21:22:42 +0000517 Local.stdout.write(
518 "Number of bug reports (non-empty plist files) produced: %d\n" %
519 len(Plists))
George Karpenkova8076602017-10-02 17:59:12 +0000520 return
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000521
George Karpenkovf37d3a52018-02-08 21:22:42 +0000522 Local.stderr.write("Error: analysis failed.\n")
523 Local.stderr.write("Total of %d failures discovered.\n" % TotalFailed)
George Karpenkovff555ce2017-10-26 19:00:22 +0000524 if TotalFailed > NumOfFailuresInSummary:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000525 Local.stderr.write(
526 "See the first %d below.\n" % NumOfFailuresInSummary)
Anna Zaksf0c41162011-10-06 23:26:27 +0000527 # TODO: Add a line "See the results folder for more."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000528
George Karpenkovff555ce2017-10-26 19:00:22 +0000529 Idx = 0
530 for FailLogPathI in Failures:
531 if Idx >= NumOfFailuresInSummary:
532 break
533 Idx += 1
George Karpenkovf37d3a52018-02-08 21:22:42 +0000534 Local.stderr.write("\n-- Error #%d -----------\n" % Idx)
George Karpenkovff555ce2017-10-26 19:00:22 +0000535 with open(FailLogPathI, "r") as FailLogI:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000536 shutil.copyfileobj(FailLogI, Local.stdout)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000537
George Karpenkov65839bd2017-10-26 01:13:22 +0000538 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000539
Anna Zaksf0c41162011-10-06 23:26:27 +0000540
George Karpenkova8076602017-10-02 17:59:12 +0000541def runCmpResults(Dir, Strictness=0):
542 """
543 Compare the warnings produced by scan-build.
544 Strictness defines the success criteria for the test:
545 0 - success if there are no crashes or analyzer failure.
546 1 - success if there are no difference in the number of reported bugs.
547 2 - success if all the bug reports are identical.
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000548
549 :return success: Whether tests pass according to the Strictness
550 criteria.
George Karpenkova8076602017-10-02 17:59:12 +0000551 """
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000552 TestsPassed = True
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000553 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000554
555 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName)
556 NewDir = os.path.join(Dir, SBOutputDirName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000557
Anna Zaksf0c41162011-10-06 23:26:27 +0000558 # We have to go one level down the directory tree.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000559 RefList = glob.glob(RefDir + "/*")
Anna Zaksf0c41162011-10-06 23:26:27 +0000560 NewList = glob.glob(NewDir + "/*")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000561
Jordan Rosec7b992e2013-06-10 19:34:30 +0000562 # Log folders are also located in the results dir, so ignore them.
563 RefLogDir = os.path.join(RefDir, LogFolderName)
564 if RefLogDir in RefList:
565 RefList.remove(RefLogDir)
Anna Zaks4720a732011-11-05 05:20:48 +0000566 NewList.remove(os.path.join(NewDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000567
George Karpenkov65839bd2017-10-26 01:13:22 +0000568 if len(RefList) != len(NewList):
569 print "Mismatch in number of results folders: %s vs %s" % (
570 RefList, NewList)
571 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000572
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000573 # There might be more then one folder underneath - one per each scan-build
Anna Zaksf0c41162011-10-06 23:26:27 +0000574 # command (Ex: one for configure and one for make).
575 if (len(RefList) > 1):
576 # Assume that the corresponding folders have the same names.
577 RefList.sort()
578 NewList.sort()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000579
Anna Zaksf0c41162011-10-06 23:26:27 +0000580 # Iterate and find the differences.
Anna Zaks767d3562011-11-08 19:56:31 +0000581 NumDiffs = 0
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000582 PairList = zip(RefList, NewList)
583 for P in PairList:
584 RefDir = P[0]
Anna Zaksf0c41162011-10-06 23:26:27 +0000585 NewDir = P[1]
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000586
587 assert(RefDir != NewDir)
588 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000589 Local.stdout.write(" Comparing Results: %s %s\n" % (
590 RefDir, NewDir))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000591
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000592 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
George Karpenkovfc782a32018-02-09 18:39:47 +0000593 Opts, Args = CmpRuns.generate_option_parser().parse_args(
George Karpenkov192d9a12018-02-12 22:13:01 +0000594 ["--rootA", "", "--rootB", PatchedSourceDirPath])
Anna Zaksf0c41162011-10-06 23:26:27 +0000595 # Scan the results, delete empty plist files.
Gabor Horvath93fde942015-06-30 15:31:17 +0000596 NumDiffs, ReportsInRef, ReportsInNew = \
George Karpenkovb7120c92018-02-13 23:36:01 +0000597 CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts,
598 deleteEmpty=False,
599 Stdout=Local.stdout)
George Karpenkova8076602017-10-02 17:59:12 +0000600 if (NumDiffs > 0):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000601 Local.stdout.write("Warning: %s differences in diagnostics.\n"
602 % NumDiffs)
Gabor Horvath93fde942015-06-30 15:31:17 +0000603 if Strictness >= 2 and NumDiffs > 0:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000604 Local.stdout.write("Error: Diffs found in strict mode (2).\n")
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000605 TestsPassed = False
Gabor Horvath93fde942015-06-30 15:31:17 +0000606 elif Strictness >= 1 and ReportsInRef != ReportsInNew:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000607 Local.stdout.write("Error: The number of results are different " +
608 " strict mode (1).\n")
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000609 TestsPassed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000610
George Karpenkovf37d3a52018-02-08 21:22:42 +0000611 Local.stdout.write("Diagnostic comparison complete (time: %.2f).\n" % (
612 time.time() - TBegin))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000613 return TestsPassed
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000614
George Karpenkova8076602017-10-02 17:59:12 +0000615
Devin Coughlin9ea80332016-01-23 01:09:07 +0000616def cleanupReferenceResults(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000617 """
618 Delete html, css, and js files from reference results. These can
619 include multiple copies of the benchmark source and so get very large.
620 """
Devin Coughlin9ea80332016-01-23 01:09:07 +0000621 Extensions = ["html", "css", "js"]
622 for E in Extensions:
623 for F in glob.glob("%s/*/*.%s" % (SBOutputDir, E)):
624 P = os.path.join(SBOutputDir, F)
625 RmCommand = "rm '%s'" % P
626 check_call(RmCommand, shell=True)
627
628 # Remove the log file. It leaks absolute path names.
629 removeLogFile(SBOutputDir)
630
George Karpenkova8076602017-10-02 17:59:12 +0000631
George Karpenkovf37d3a52018-02-08 21:22:42 +0000632class TestProjectThread(threading.Thread):
George Karpenkov47e54932018-09-27 01:10:59 +0000633 def __init__(self, Args, TasksQueue, ResultsDiffer, FailureFlag):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000634 """
635 :param ResultsDiffer: Used to signify that results differ from
636 the canonical ones.
637 :param FailureFlag: Used to signify a failure during the run.
638 """
George Karpenkov47e54932018-09-27 01:10:59 +0000639 self.Args = Args
George Karpenkovf37d3a52018-02-08 21:22:42 +0000640 self.TasksQueue = TasksQueue
641 self.ResultsDiffer = ResultsDiffer
642 self.FailureFlag = FailureFlag
643 super(TestProjectThread, self).__init__()
644
645 # Needed to gracefully handle interrupts with Ctrl-C
646 self.daemon = True
647
648 def run(self):
649 while not self.TasksQueue.empty():
650 try:
651 ProjArgs = self.TasksQueue.get()
652 Logger = logging.getLogger(ProjArgs[0])
653 Local.stdout = StreamToLogger(Logger, logging.INFO)
654 Local.stderr = StreamToLogger(Logger, logging.ERROR)
George Karpenkov47e54932018-09-27 01:10:59 +0000655 if not testProject(Args, *ProjArgs):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000656 self.ResultsDiffer.set()
657 self.TasksQueue.task_done()
658 except:
659 self.FailureFlag.set()
660 raise
661
662
George Karpenkov47e54932018-09-27 01:10:59 +0000663def testProject(Args, ID, ProjectBuildMode, IsReferenceBuild=False, Strictness=0):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000664 """
665 Test a given project.
666 :return TestsPassed: Whether tests have passed according
667 to the :param Strictness: criteria.
668 """
George Karpenkovf37d3a52018-02-08 21:22:42 +0000669 Local.stdout.write(" \n\n--- Building project %s\n" % (ID,))
Anna Zaks4720a732011-11-05 05:20:48 +0000670
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000671 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000672
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000673 Dir = getProjectDir(ID)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000674 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000675 Local.stdout.write(" Build directory: %s.\n" % (Dir,))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000676
Anna Zaksf0c41162011-10-06 23:26:27 +0000677 # Set the build results directory.
Jordan Rose01ac5722012-06-01 16:24:38 +0000678 RelOutputDir = getSBOutputDirName(IsReferenceBuild)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000679 SBOutputDir = os.path.join(Dir, RelOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000680
George Karpenkov47e54932018-09-27 01:10:59 +0000681 buildProject(Args, Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild)
Anna Zaksf0c41162011-10-06 23:26:27 +0000682
683 checkBuild(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000684
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000685 if IsReferenceBuild:
Devin Coughlin9ea80332016-01-23 01:09:07 +0000686 cleanupReferenceResults(SBOutputDir)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000687 TestsPassed = True
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000688 else:
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000689 TestsPassed = runCmpResults(Dir, Strictness)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000690
George Karpenkovf37d3a52018-02-08 21:22:42 +0000691 Local.stdout.write("Completed tests for project %s (time: %.2f).\n" % (
692 ID, (time.time() - TBegin)))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000693 return TestsPassed
George Karpenkova8076602017-10-02 17:59:12 +0000694
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000695
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000696def projectFileHandler():
697 return open(getProjectMapPath(), "rb")
698
George Karpenkova8076602017-10-02 17:59:12 +0000699
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000700def iterateOverProjects(PMapFile):
701 """
702 Iterate over all projects defined in the project file handler `PMapFile`
703 from the start.
704 """
705 PMapFile.seek(0)
George Karpenkova8076602017-10-02 17:59:12 +0000706 for I in csv.reader(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000707 if (SATestUtils.isCommentCSVLine(I)):
George Karpenkova8076602017-10-02 17:59:12 +0000708 continue
709 yield I
710
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000711
712def validateProjectFile(PMapFile):
713 """
714 Validate project file.
715 """
716 for I in iterateOverProjects(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000717 if len(I) != 2:
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000718 print "Error: Rows in the ProjectMapFile should have 2 entries."
719 raise Exception()
George Karpenkovbf92c442017-10-24 23:52:48 +0000720 if I[1] not in ('0', '1', '2'):
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000721 print "Error: Second entry in the ProjectMapFile should be 0" \
George Karpenkova8076602017-10-02 17:59:12 +0000722 " (single file), 1 (project), or 2(single file c++11)."
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000723 raise Exception()
724
George Karpenkov47e54932018-09-27 01:10:59 +0000725def singleThreadedTestAll(Args, ProjectsToTest):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000726 """
727 Run all projects.
728 :return: whether tests have passed.
729 """
730 Success = True
731 for ProjArgs in ProjectsToTest:
George Karpenkov47e54932018-09-27 01:10:59 +0000732 Success &= testProject(Args, *ProjArgs)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000733 return Success
George Karpenkova8076602017-10-02 17:59:12 +0000734
George Karpenkov47e54932018-09-27 01:10:59 +0000735def multiThreadedTestAll(Args, ProjectsToTest, Jobs):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000736 """
737 Run each project in a separate thread.
738
739 This is OK despite GIL, as testing is blocked
740 on launching external processes.
741
742 :return: whether tests have passed.
743 """
744 TasksQueue = Queue.Queue()
745
746 for ProjArgs in ProjectsToTest:
747 TasksQueue.put(ProjArgs)
748
749 ResultsDiffer = threading.Event()
750 FailureFlag = threading.Event()
751
752 for i in range(Jobs):
George Karpenkov47e54932018-09-27 01:10:59 +0000753 T = TestProjectThread(Args, TasksQueue, ResultsDiffer, FailureFlag)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000754 T.start()
755
756 # Required to handle Ctrl-C gracefully.
757 while TasksQueue.unfinished_tasks:
758 time.sleep(0.1) # Seconds.
759 if FailureFlag.is_set():
760 Local.stderr.write("Test runner crashed\n")
761 sys.exit(1)
762 return not ResultsDiffer.is_set()
763
764
765def testAll(Args):
766 ProjectsToTest = []
767
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000768 with projectFileHandler() as PMapFile:
769 validateProjectFile(PMapFile)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000770
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000771 # Test the projects.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000772 for (ProjName, ProjBuildMode) in iterateOverProjects(PMapFile):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000773 ProjectsToTest.append((ProjName,
774 int(ProjBuildMode),
775 Args.regenerate,
776 Args.strictness))
777 if Args.jobs <= 1:
George Karpenkov47e54932018-09-27 01:10:59 +0000778 return singleThreadedTestAll(Args, ProjectsToTest)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000779 else:
George Karpenkov47e54932018-09-27 01:10:59 +0000780 return multiThreadedTestAll(Args, ProjectsToTest, Args.jobs)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000781
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000782
Anna Zaksf0c41162011-10-06 23:26:27 +0000783if __name__ == '__main__':
Gabor Horvath93fde942015-06-30 15:31:17 +0000784 # Parse command line arguments.
George Karpenkova8076602017-10-02 17:59:12 +0000785 Parser = argparse.ArgumentParser(
786 description='Test the Clang Static Analyzer.')
Gabor Horvath93fde942015-06-30 15:31:17 +0000787 Parser.add_argument('--strictness', dest='strictness', type=int, default=0,
George Karpenkova8076602017-10-02 17:59:12 +0000788 help='0 to fail on runtime errors, 1 to fail when the \
789 number of found bugs are different from the \
790 reference, 2 to fail on any difference from the \
791 reference. Default is 0.')
792 Parser.add_argument('-r', dest='regenerate', action='store_true',
793 default=False, help='Regenerate reference output.')
George Karpenkovf37d3a52018-02-08 21:22:42 +0000794 Parser.add_argument('-j', '--jobs', dest='jobs', type=int,
795 default=0,
796 help='Number of projects to test concurrently')
George Karpenkov47e54932018-09-27 01:10:59 +0000797 Parser.add_argument('--extra-analyzer-args', dest='extra_args',
798 type=str, default="",
799 help="Extra arguments to add to -analyzer-config")
Gabor Horvath93fde942015-06-30 15:31:17 +0000800 Args = Parser.parse_args()
801
George Karpenkovf37d3a52018-02-08 21:22:42 +0000802 TestsPassed = testAll(Args)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000803 if not TestsPassed:
George Karpenkovbf92c442017-10-24 23:52:48 +0000804 print "ERROR: Tests failed."
George Karpenkov65839bd2017-10-26 01:13:22 +0000805 sys.exit(42)