blob: 691ded80a1ef145086518bd64c30087b11d9cdcd [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
Serge Guelton1f88dc52018-12-13 07:44:19 +000061try:
62 import queue
63except ImportError:
64 import Queue as queue
Anna Zaksf0c41162011-10-06 23:26:27 +000065
George Karpenkov13d37482018-07-30 23:01:20 +000066###############################################################################
Ted Kremenek42c14422012-08-28 20:40:02 +000067# Helper functions.
George Karpenkov13d37482018-07-30 23:01:20 +000068###############################################################################
Anna Zaksf0c41162011-10-06 23:26:27 +000069
George Karpenkovf37d3a52018-02-08 21:22:42 +000070Local = threading.local()
71Local.stdout = sys.stdout
72Local.stderr = sys.stderr
73logging.basicConfig(
74 level=logging.DEBUG,
75 format='%(asctime)s:%(levelname)s:%(name)s: %(message)s')
Ted Kremenekf9a539d2012-08-28 20:40:04 +000076
George Karpenkovf37d3a52018-02-08 21:22:42 +000077class StreamToLogger(object):
78 def __init__(self, logger, log_level=logging.INFO):
79 self.logger = logger
80 self.log_level = log_level
81
82 def write(self, buf):
83 # Rstrip in order not to write an extra newline.
84 self.logger.log(self.log_level, buf.rstrip())
85
86 def flush(self):
87 pass
88
89 def fileno(self):
90 return 0
Anna Zaksde1f7f8b2012-01-10 18:10:25 +000091
George Karpenkova8076602017-10-02 17:59:12 +000092
Anna Zaksf0c41162011-10-06 23:26:27 +000093def getProjectMapPath():
Ted Kremenek3a0678e2015-09-08 03:50:52 +000094 ProjectMapPath = os.path.join(os.path.abspath(os.curdir),
Anna Zaksf0c41162011-10-06 23:26:27 +000095 ProjectMapFile)
96 if not os.path.exists(ProjectMapPath):
George Karpenkov13d37482018-07-30 23:01:20 +000097 Local.stdout.write("Error: Cannot find the Project Map file " +
98 ProjectMapPath +
99 "\nRunning script for the wrong directory?\n")
George Karpenkov65839bd2017-10-26 01:13:22 +0000100 sys.exit(1)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000101 return ProjectMapPath
Anna Zaksf0c41162011-10-06 23:26:27 +0000102
George Karpenkova8076602017-10-02 17:59:12 +0000103
Anna Zaksf0c41162011-10-06 23:26:27 +0000104def getProjectDir(ID):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000105 return os.path.join(os.path.abspath(os.curdir), ID)
Anna Zaksf0c41162011-10-06 23:26:27 +0000106
George Karpenkova8076602017-10-02 17:59:12 +0000107
108def getSBOutputDirName(IsReferenceBuild):
109 if IsReferenceBuild:
Anna Zaks4720a732011-11-05 05:20:48 +0000110 return SBOutputDirReferencePrefix + SBOutputDirName
George Karpenkova8076602017-10-02 17:59:12 +0000111 else:
Anna Zaks4720a732011-11-05 05:20:48 +0000112 return SBOutputDirName
113
George Karpenkov13d37482018-07-30 23:01:20 +0000114###############################################################################
Ted Kremenek42c14422012-08-28 20:40:02 +0000115# Configuration setup.
George Karpenkov13d37482018-07-30 23:01:20 +0000116###############################################################################
Ted Kremenek42c14422012-08-28 20:40:02 +0000117
George Karpenkova8076602017-10-02 17:59:12 +0000118
Ted Kremenek42c14422012-08-28 20:40:02 +0000119# Find Clang for static analysis.
George Karpenkovbe6c3292017-09-21 22:12:49 +0000120if 'CC' in os.environ:
121 Clang = os.environ['CC']
122else:
George Karpenkovbf92c442017-10-24 23:52:48 +0000123 Clang = SATestUtils.which("clang", os.environ['PATH'])
Ted Kremenek42c14422012-08-28 20:40:02 +0000124if not Clang:
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000125 print("Error: cannot find 'clang' in PATH")
George Karpenkov65839bd2017-10-26 01:13:22 +0000126 sys.exit(1)
Ted Kremenek42c14422012-08-28 20:40:02 +0000127
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000128# Number of jobs.
George Karpenkovf37d3a52018-02-08 21:22:42 +0000129MaxJobs = int(math.ceil(multiprocessing.cpu_count() * 0.75))
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000130
Ted Kremenek42c14422012-08-28 20:40:02 +0000131# Project map stores info about all the "registered" projects.
132ProjectMapFile = "projectMap.csv"
133
134# Names of the project specific scripts.
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000135# The script that downloads the project.
136DownloadScript = "download_project.sh"
Ted Kremenek42c14422012-08-28 20:40:02 +0000137# The script that needs to be executed before the build can start.
138CleanupScript = "cleanup_run_static_analyzer.sh"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000139# This is a file containing commands for scan-build.
Ted Kremenek42c14422012-08-28 20:40:02 +0000140BuildScript = "run_static_analyzer.cmd"
141
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000142# A comment in a build script which disables wrapping.
143NoPrefixCmd = "#NOPREFIX"
144
Ted Kremenek42c14422012-08-28 20:40:02 +0000145# The log file name.
146LogFolderName = "Logs"
147BuildLogName = "run_static_analyzer.log"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000148# Summary file - contains the summary of the failures. Ex: This info can be be
Ted Kremenek42c14422012-08-28 20:40:02 +0000149# displayed when buildbot detects a build failure.
150NumOfFailuresInSummary = 10
151FailuresSummaryFileName = "failures.txt"
Ted Kremenek42c14422012-08-28 20:40:02 +0000152
153# The scan-build result directory.
154SBOutputDirName = "ScanBuildResults"
155SBOutputDirReferencePrefix = "Ref"
156
George Karpenkova8076602017-10-02 17:59:12 +0000157# The name of the directory storing the cached project source. If this
158# directory does not exist, the download script will be executed.
159# That script should create the "CachedSource" directory and download the
160# project source into it.
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000161CachedSourceDirName = "CachedSource"
162
163# The name of the directory containing the source code that will be analyzed.
164# Each time a project is analyzed, a fresh copy of its CachedSource directory
165# will be copied to the PatchedSource directory and then the local patches
166# in PatchfileName will be applied (if PatchfileName exists).
167PatchedSourceDirName = "PatchedSource"
168
169# The name of the patchfile specifying any changes that should be applied
170# to the CachedSource before analyzing.
171PatchfileName = "changes_for_analyzer.patch"
172
Ted Kremenek42c14422012-08-28 20:40:02 +0000173# The list of checkers used during analyzes.
Alp Tokerd4733632013-12-05 04:47:09 +0000174# Currently, consists of all the non-experimental checkers, plus a few alpha
Jordan Rose10ad0812013-04-05 17:55:07 +0000175# checkers we don't want to regress on.
George Karpenkova8076602017-10-02 17:59:12 +0000176Checkers = ",".join([
George Karpenkovaf76b4a2017-09-30 00:05:24 +0000177 "alpha.unix.SimpleStream",
178 "alpha.security.taint",
179 "cplusplus.NewDeleteLeaks",
180 "core",
181 "cplusplus",
182 "deadcode",
183 "security",
184 "unix",
185 "osx",
186 "nullability"
187])
Ted Kremenek42c14422012-08-28 20:40:02 +0000188
George Karpenkov71105812018-03-29 01:23:54 +0000189Verbose = 0
Ted Kremenek42c14422012-08-28 20:40:02 +0000190
George Karpenkov13d37482018-07-30 23:01:20 +0000191###############################################################################
Ted Kremenek42c14422012-08-28 20:40:02 +0000192# Test harness logic.
George Karpenkov13d37482018-07-30 23:01:20 +0000193###############################################################################
Ted Kremenek42c14422012-08-28 20:40:02 +0000194
George Karpenkova8076602017-10-02 17:59:12 +0000195
Anna Zaks42a44632011-11-02 20:46:50 +0000196def runCleanupScript(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000197 """
198 Run pre-processing script if any.
199 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000200 Cwd = os.path.join(Dir, PatchedSourceDirName)
Anna Zaks42a44632011-11-02 20:46:50 +0000201 ScriptPath = os.path.join(Dir, CleanupScript)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000202 SATestUtils.runScript(ScriptPath, PBuildLogFile, Cwd,
203 Stdout=Local.stdout, Stderr=Local.stderr)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000204
George Karpenkova8076602017-10-02 17:59:12 +0000205
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000206def runDownloadScript(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000207 """
208 Run the script to download the project, if it exists.
209 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000210 ScriptPath = os.path.join(Dir, DownloadScript)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000211 SATestUtils.runScript(ScriptPath, PBuildLogFile, Dir,
212 Stdout=Local.stdout, Stderr=Local.stderr)
Anna Zaksf0c41162011-10-06 23:26:27 +0000213
George Karpenkova8076602017-10-02 17:59:12 +0000214
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000215def downloadAndPatch(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000216 """
217 Download the project and apply the local patchfile if it exists.
218 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000219 CachedSourceDirPath = os.path.join(Dir, CachedSourceDirName)
220
221 # If the we don't already have the cached source, run the project's
222 # download script to download it.
223 if not os.path.exists(CachedSourceDirPath):
George Karpenkova8076602017-10-02 17:59:12 +0000224 runDownloadScript(Dir, PBuildLogFile)
225 if not os.path.exists(CachedSourceDirPath):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000226 Local.stderr.write("Error: '%s' not found after download.\n" % (
227 CachedSourceDirPath))
George Karpenkov65839bd2017-10-26 01:13:22 +0000228 exit(1)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000229
230 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
231
232 # Remove potentially stale patched source.
233 if os.path.exists(PatchedSourceDirPath):
234 shutil.rmtree(PatchedSourceDirPath)
235
236 # Copy the cached source and apply any patches to the copy.
237 shutil.copytree(CachedSourceDirPath, PatchedSourceDirPath, symlinks=True)
238 applyPatch(Dir, PBuildLogFile)
239
George Karpenkova8076602017-10-02 17:59:12 +0000240
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000241def applyPatch(Dir, PBuildLogFile):
242 PatchfilePath = os.path.join(Dir, PatchfileName)
243 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
244 if not os.path.exists(PatchfilePath):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000245 Local.stdout.write(" No local patches.\n")
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000246 return
247
George Karpenkovf37d3a52018-02-08 21:22:42 +0000248 Local.stdout.write(" Applying patch.\n")
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000249 try:
Devin Coughlinab95cd22016-01-22 07:08:06 +0000250 check_call("patch -p1 < '%s'" % (PatchfilePath),
George Karpenkova8076602017-10-02 17:59:12 +0000251 cwd=PatchedSourceDirPath,
252 stderr=PBuildLogFile,
253 stdout=PBuildLogFile,
254 shell=True)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000255 except:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000256 Local.stderr.write("Error: Patch failed. See %s for details.\n" % (
257 PBuildLogFile.name))
George Karpenkov65839bd2017-10-26 01:13:22 +0000258 sys.exit(1)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000259
George Karpenkova8076602017-10-02 17:59:12 +0000260
George Karpenkovac986832018-10-02 21:19:23 +0000261def generateAnalyzerConfig(Args):
262 Out = "serialize-stats=true,stable-report-filename=true"
263 if Args.extra_analyzer_config:
264 Out += "," + Args.extra_analyzer_config
265 return Out
266
267
George Karpenkov47e54932018-09-27 01:10:59 +0000268def runScanBuild(Args, Dir, SBOutputDir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000269 """
270 Build the project with scan-build by reading in the commands and
271 prefixing them with the scan-build options.
272 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000273 BuildScriptPath = os.path.join(Dir, BuildScript)
274 if not os.path.exists(BuildScriptPath):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000275 Local.stderr.write(
276 "Error: build script is not defined: %s\n" % BuildScriptPath)
George Karpenkov65839bd2017-10-26 01:13:22 +0000277 sys.exit(1)
Devin Coughlinf3695c82015-09-16 01:52:32 +0000278
279 AllCheckers = Checkers
George Karpenkova8076602017-10-02 17:59:12 +0000280 if 'SA_ADDITIONAL_CHECKERS' in os.environ:
Devin Coughlinf3695c82015-09-16 01:52:32 +0000281 AllCheckers = AllCheckers + ',' + os.environ['SA_ADDITIONAL_CHECKERS']
282
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000283 # Run scan-build from within the patched source directory.
284 SBCwd = os.path.join(Dir, PatchedSourceDirName)
285
George Karpenkova8076602017-10-02 17:59:12 +0000286 SBOptions = "--use-analyzer '%s' " % Clang
Devin Coughlin86f61a92016-01-22 18:45:22 +0000287 SBOptions += "-plist-html -o '%s' " % SBOutputDir
Devin Coughlinf3695c82015-09-16 01:52:32 +0000288 SBOptions += "-enable-checker " + AllCheckers + " "
Jordan Roseb18179d2013-01-24 23:07:59 +0000289 SBOptions += "--keep-empty "
George Karpenkovac986832018-10-02 21:19:23 +0000290 SBOptions += "-analyzer-config '%s' " % generateAnalyzerConfig(Args)
Mikhail R. Gadelhaafc62b72018-06-27 14:39:41 +0000291
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000292 # Always use ccc-analyze to ensure that we can locate the failures
Anna Zaks7b4f8a42013-05-31 02:31:09 +0000293 # directory.
294 SBOptions += "--override-compiler "
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000295 ExtraEnv = {}
Anna Zaksf0c41162011-10-06 23:26:27 +0000296 try:
297 SBCommandFile = open(BuildScriptPath, "r")
298 SBPrefix = "scan-build " + SBOptions + " "
299 for Command in SBCommandFile:
Jordan Rose7bd91862013-09-06 16:12:41 +0000300 Command = Command.strip()
Gabor Horvath93fde942015-06-30 15:31:17 +0000301 if len(Command) == 0:
George Karpenkova8076602017-10-02 17:59:12 +0000302 continue
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000303
304 # Custom analyzer invocation specified by project.
305 # Communicate required information using environment variables
306 # instead.
307 if Command == NoPrefixCmd:
308 SBPrefix = ""
309 ExtraEnv['OUTPUT'] = SBOutputDir
George Karpenkov6e4ddf42018-07-02 17:10:40 +0000310 ExtraEnv['CC'] = Clang
George Karpenkovac986832018-10-02 21:19:23 +0000311 ExtraEnv['ANALYZER_CONFIG'] = generateAnalyzerConfig(Args)
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000312 continue
313
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000314 # If using 'make', auto imply a -jX argument
315 # to speed up analysis. xcodebuild will
316 # automatically use the maximum number of cores.
Jordan Rose64e4cf02012-11-26 19:59:57 +0000317 if (Command.startswith("make ") or Command == "make") and \
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000318 "-j" not in Command:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000319 Command += " -j%d" % MaxJobs
Anna Zaksf0c41162011-10-06 23:26:27 +0000320 SBCommand = SBPrefix + Command
George Karpenkovf37d3a52018-02-08 21:22:42 +0000321
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000322 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000323 Local.stdout.write(" Executing: %s\n" % (SBCommand,))
George Karpenkova8076602017-10-02 17:59:12 +0000324 check_call(SBCommand, cwd=SBCwd,
325 stderr=PBuildLogFile,
326 stdout=PBuildLogFile,
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000327 env=dict(os.environ, **ExtraEnv),
George Karpenkova8076602017-10-02 17:59:12 +0000328 shell=True)
George Karpenkove58044d2017-10-27 22:39:54 +0000329 except CalledProcessError:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000330 Local.stderr.write("Error: scan-build failed. Its output was: \n")
George Karpenkove58044d2017-10-27 22:39:54 +0000331 PBuildLogFile.seek(0)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000332 shutil.copyfileobj(PBuildLogFile, Local.stderr)
George Karpenkove58044d2017-10-27 22:39:54 +0000333 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000334
George Karpenkova8076602017-10-02 17:59:12 +0000335
George Karpenkovac986832018-10-02 21:19:23 +0000336def runAnalyzePreprocessed(Args, Dir, SBOutputDir, Mode):
George Karpenkova8076602017-10-02 17:59:12 +0000337 """
338 Run analysis on a set of preprocessed files.
339 """
Anna Zaks4720a732011-11-05 05:20:48 +0000340 if os.path.exists(os.path.join(Dir, BuildScript)):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000341 Local.stderr.write(
342 "Error: The preprocessed files project should not contain %s\n" % (
343 BuildScript))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000344 raise Exception()
Anna Zaks4720a732011-11-05 05:20:48 +0000345
Artem Dergacheve8b29c02019-05-29 18:49:31 +0000346 CmdPrefix = Clang + " --analyze "
Devin Coughlinbace0322015-09-14 21:22:24 +0000347
Artem Dergacheve8b29c02019-05-29 18:49:31 +0000348 CmdPrefix += "--analyzer-output plist "
349 CmdPrefix += " -Xclang -analyzer-checker=" + Checkers
George Karpenkova8076602017-10-02 17:59:12 +0000350 CmdPrefix += " -fcxx-exceptions -fblocks "
Artem Dergacheve8b29c02019-05-29 18:49:31 +0000351 CmdPrefix += " -Xclang -analyzer-config -Xclang %s "\
352 % generateAnalyzerConfig(Args)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000353
George Karpenkova8076602017-10-02 17:59:12 +0000354 if (Mode == 2):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000355 CmdPrefix += "-std=c++11 "
356
Anna Zaks4720a732011-11-05 05:20:48 +0000357 PlistPath = os.path.join(Dir, SBOutputDir, "date")
George Karpenkova8076602017-10-02 17:59:12 +0000358 FailPath = os.path.join(PlistPath, "failures")
359 os.makedirs(FailPath)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000360
Anna Zaks4720a732011-11-05 05:20:48 +0000361 for FullFileName in glob.glob(Dir + "/*"):
362 FileName = os.path.basename(FullFileName)
363 Failed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000364
Anna Zaks4720a732011-11-05 05:20:48 +0000365 # Only run the analyzes on supported files.
George Karpenkovbf92c442017-10-24 23:52:48 +0000366 if SATestUtils.hasNoExtension(FileName):
Anna Zaks4720a732011-11-05 05:20:48 +0000367 continue
George Karpenkovbf92c442017-10-24 23:52:48 +0000368 if not SATestUtils.isValidSingleInputFile(FileName):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000369 Local.stderr.write(
370 "Error: Invalid single input file %s.\n" % (FullFileName,))
Anna Zaks4720a732011-11-05 05:20:48 +0000371 raise Exception()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000372
Anna Zaks4720a732011-11-05 05:20:48 +0000373 # Build and call the analyzer command.
Devin Coughlinab95cd22016-01-22 07:08:06 +0000374 OutputOption = "-o '%s.plist' " % os.path.join(PlistPath, FileName)
375 Command = CmdPrefix + OutputOption + ("'%s'" % FileName)
Anna Zaks4720a732011-11-05 05:20:48 +0000376 LogFile = open(os.path.join(FailPath, FileName + ".stderr.txt"), "w+b")
377 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000378 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000379 Local.stdout.write(" Executing: %s\n" % (Command,))
George Karpenkova8076602017-10-02 17:59:12 +0000380 check_call(Command, cwd=Dir, stderr=LogFile,
381 stdout=LogFile,
382 shell=True)
Serge Guelton3de41082018-12-03 12:11:21 +0000383 except CalledProcessError as e:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000384 Local.stderr.write("Error: Analyzes of %s failed. "
385 "See %s for details."
386 "Error code %d.\n" % (
387 FullFileName, LogFile.name, e.returncode))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000388 Failed = True
Anna Zaks4720a732011-11-05 05:20:48 +0000389 finally:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000390 LogFile.close()
391
Anna Zaks4720a732011-11-05 05:20:48 +0000392 # If command did not fail, erase the log file.
George Karpenkova8076602017-10-02 17:59:12 +0000393 if not Failed:
394 os.remove(LogFile.name)
395
Anna Zaks4720a732011-11-05 05:20:48 +0000396
Devin Coughlin9ea80332016-01-23 01:09:07 +0000397def getBuildLogPath(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000398 return os.path.join(SBOutputDir, LogFolderName, BuildLogName)
399
Devin Coughlin9ea80332016-01-23 01:09:07 +0000400
401def removeLogFile(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000402 BuildLogPath = getBuildLogPath(SBOutputDir)
403 # Clean up the log file.
404 if (os.path.exists(BuildLogPath)):
405 RmCommand = "rm '%s'" % BuildLogPath
406 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000407 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
George Karpenkova8076602017-10-02 17:59:12 +0000408 check_call(RmCommand, shell=True)
409
Devin Coughlin9ea80332016-01-23 01:09:07 +0000410
George Karpenkov47e54932018-09-27 01:10:59 +0000411def buildProject(Args, Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000412 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000413
Devin Coughlin9ea80332016-01-23 01:09:07 +0000414 BuildLogPath = getBuildLogPath(SBOutputDir)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000415 Local.stdout.write("Log file: %s\n" % (BuildLogPath,))
416 Local.stdout.write("Output directory: %s\n" % (SBOutputDir, ))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000417
Devin Coughlin9ea80332016-01-23 01:09:07 +0000418 removeLogFile(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000419
Anna Zaks4720a732011-11-05 05:20:48 +0000420 # Clean up scan build results.
George Karpenkova8076602017-10-02 17:59:12 +0000421 if (os.path.exists(SBOutputDir)):
Devin Coughlinab95cd22016-01-22 07:08:06 +0000422 RmCommand = "rm -r '%s'" % SBOutputDir
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000423 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000424 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
425 check_call(RmCommand, shell=True, stdout=Local.stdout,
426 stderr=Local.stderr)
Anna Zaks4720a732011-11-05 05:20:48 +0000427 assert(not os.path.exists(SBOutputDir))
428 os.makedirs(os.path.join(SBOutputDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000429
Anna Zaks4720a732011-11-05 05:20:48 +0000430 # Build and analyze the project.
George Karpenkov0a6dba72017-10-27 22:52:36 +0000431 with open(BuildLogPath, "wb+") as PBuildLogFile:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000432 if (ProjectBuildMode == 1):
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000433 downloadAndPatch(Dir, PBuildLogFile)
434 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov47e54932018-09-27 01:10:59 +0000435 runScanBuild(Args, Dir, SBOutputDir, PBuildLogFile)
Anna Zaks4720a732011-11-05 05:20:48 +0000436 else:
George Karpenkovac986832018-10-02 21:19:23 +0000437 runAnalyzePreprocessed(Args, Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000438
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000439 if IsReferenceBuild:
Anna Zaks42a44632011-11-02 20:46:50 +0000440 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000441 normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000442
George Karpenkovf37d3a52018-02-08 21:22:42 +0000443 Local.stdout.write("Build complete (time: %.2f). "
444 "See the log for more details: %s\n" % (
445 (time.time() - TBegin), BuildLogPath))
George Karpenkova8076602017-10-02 17:59:12 +0000446
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000447
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000448def normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode):
449 """
450 Make the absolute paths relative in the reference results.
451 """
452 for (DirPath, Dirnames, Filenames) in os.walk(SBOutputDir):
453 for F in Filenames:
454 if (not F.endswith('plist')):
455 continue
456 Plist = os.path.join(DirPath, F)
457 Data = plistlib.readPlist(Plist)
458 PathPrefix = Dir
459 if (ProjectBuildMode == 1):
460 PathPrefix = os.path.join(Dir, PatchedSourceDirName)
George Karpenkova8076602017-10-02 17:59:12 +0000461 Paths = [SourceFile[len(PathPrefix) + 1:]
462 if SourceFile.startswith(PathPrefix)
463 else SourceFile for SourceFile in Data['files']]
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000464 Data['files'] = Paths
George Karpenkov318cd1f2017-10-24 23:52:46 +0000465
466 # Remove transient fields which change from run to run.
467 for Diag in Data['diagnostics']:
468 if 'HTMLDiagnostics_files' in Diag:
469 Diag.pop('HTMLDiagnostics_files')
470 if 'clang_version' in Data:
471 Data.pop('clang_version')
472
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000473 plistlib.writePlist(Data, Plist)
474
George Karpenkova8076602017-10-02 17:59:12 +0000475
Anna Zaksf0c41162011-10-06 23:26:27 +0000476def CleanUpEmptyPlists(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000477 """
478 A plist file is created for each call to the analyzer(each source file).
479 We are only interested on the once that have bug reports,
480 so delete the rest.
481 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000482 for F in glob.glob(SBOutputDir + "/*/*.plist"):
483 P = os.path.join(SBOutputDir, F)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000484
Anna Zaksf0c41162011-10-06 23:26:27 +0000485 Data = plistlib.readPlist(P)
486 # Delete empty reports.
487 if not Data['files']:
488 os.remove(P)
489 continue
490
George Karpenkova8076602017-10-02 17:59:12 +0000491
George Karpenkov3c128cb2017-10-30 19:40:33 +0000492def CleanUpEmptyFolders(SBOutputDir):
493 """
494 Remove empty folders from results, as git would not store them.
495 """
496 Subfolders = glob.glob(SBOutputDir + "/*")
497 for Folder in Subfolders:
498 if not os.listdir(Folder):
499 os.removedirs(Folder)
500
501
Anna Zaksf0c41162011-10-06 23:26:27 +0000502def checkBuild(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000503 """
504 Given the scan-build output directory, checks if the build failed
505 (by searching for the failures directories). If there are failures, it
506 creates a summary file in the output directory.
507
508 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000509 # Check if there are failures.
510 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt")
George Karpenkova8076602017-10-02 17:59:12 +0000511 TotalFailed = len(Failures)
Anna Zaksf0c41162011-10-06 23:26:27 +0000512 if TotalFailed == 0:
Jordan Rose9858b122012-08-31 00:36:30 +0000513 CleanUpEmptyPlists(SBOutputDir)
George Karpenkov3c128cb2017-10-30 19:40:33 +0000514 CleanUpEmptyFolders(SBOutputDir)
Jordan Rose9858b122012-08-31 00:36:30 +0000515 Plists = glob.glob(SBOutputDir + "/*/*.plist")
George Karpenkovf37d3a52018-02-08 21:22:42 +0000516 Local.stdout.write(
517 "Number of bug reports (non-empty plist files) produced: %d\n" %
518 len(Plists))
George Karpenkova8076602017-10-02 17:59:12 +0000519 return
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000520
George Karpenkovf37d3a52018-02-08 21:22:42 +0000521 Local.stderr.write("Error: analysis failed.\n")
522 Local.stderr.write("Total of %d failures discovered.\n" % TotalFailed)
George Karpenkovff555ce2017-10-26 19:00:22 +0000523 if TotalFailed > NumOfFailuresInSummary:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000524 Local.stderr.write(
525 "See the first %d below.\n" % NumOfFailuresInSummary)
Anna Zaksf0c41162011-10-06 23:26:27 +0000526 # TODO: Add a line "See the results folder for more."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000527
George Karpenkovff555ce2017-10-26 19:00:22 +0000528 Idx = 0
529 for FailLogPathI in Failures:
530 if Idx >= NumOfFailuresInSummary:
531 break
532 Idx += 1
George Karpenkovf37d3a52018-02-08 21:22:42 +0000533 Local.stderr.write("\n-- Error #%d -----------\n" % Idx)
George Karpenkovff555ce2017-10-26 19:00:22 +0000534 with open(FailLogPathI, "r") as FailLogI:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000535 shutil.copyfileobj(FailLogI, Local.stdout)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000536
George Karpenkov65839bd2017-10-26 01:13:22 +0000537 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000538
Anna Zaksf0c41162011-10-06 23:26:27 +0000539
George Karpenkova8076602017-10-02 17:59:12 +0000540def runCmpResults(Dir, Strictness=0):
541 """
542 Compare the warnings produced by scan-build.
543 Strictness defines the success criteria for the test:
544 0 - success if there are no crashes or analyzer failure.
545 1 - success if there are no difference in the number of reported bugs.
546 2 - success if all the bug reports are identical.
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000547
548 :return success: Whether tests pass according to the Strictness
549 criteria.
George Karpenkova8076602017-10-02 17:59:12 +0000550 """
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000551 TestsPassed = True
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000552 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000553
554 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName)
555 NewDir = os.path.join(Dir, SBOutputDirName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000556
Anna Zaksf0c41162011-10-06 23:26:27 +0000557 # We have to go one level down the directory tree.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000558 RefList = glob.glob(RefDir + "/*")
Anna Zaksf0c41162011-10-06 23:26:27 +0000559 NewList = glob.glob(NewDir + "/*")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000560
Jordan Rosec7b992e2013-06-10 19:34:30 +0000561 # Log folders are also located in the results dir, so ignore them.
562 RefLogDir = os.path.join(RefDir, LogFolderName)
563 if RefLogDir in RefList:
564 RefList.remove(RefLogDir)
Anna Zaks4720a732011-11-05 05:20:48 +0000565 NewList.remove(os.path.join(NewDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000566
George Karpenkov65839bd2017-10-26 01:13:22 +0000567 if len(RefList) != len(NewList):
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000568 print("Mismatch in number of results folders: %s vs %s" % (
569 RefList, NewList))
George Karpenkov65839bd2017-10-26 01:13:22 +0000570 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000571
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000572 # There might be more then one folder underneath - one per each scan-build
Anna Zaksf0c41162011-10-06 23:26:27 +0000573 # command (Ex: one for configure and one for make).
574 if (len(RefList) > 1):
575 # Assume that the corresponding folders have the same names.
576 RefList.sort()
577 NewList.sort()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000578
Anna Zaksf0c41162011-10-06 23:26:27 +0000579 # Iterate and find the differences.
Anna Zaks767d3562011-11-08 19:56:31 +0000580 NumDiffs = 0
Serge Gueltond4589742018-12-18 16:04:21 +0000581 for P in zip(RefList, NewList):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000582 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):
George Karpenkov47e54932018-09-27 01:10:59 +0000631 def __init__(self, Args, TasksQueue, ResultsDiffer, FailureFlag):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000632 """
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 """
George Karpenkov47e54932018-09-27 01:10:59 +0000637 self.Args = Args
George Karpenkovf37d3a52018-02-08 21:22:42 +0000638 self.TasksQueue = TasksQueue
639 self.ResultsDiffer = ResultsDiffer
640 self.FailureFlag = FailureFlag
641 super(TestProjectThread, self).__init__()
642
643 # Needed to gracefully handle interrupts with Ctrl-C
644 self.daemon = True
645
646 def run(self):
647 while not self.TasksQueue.empty():
648 try:
649 ProjArgs = self.TasksQueue.get()
650 Logger = logging.getLogger(ProjArgs[0])
651 Local.stdout = StreamToLogger(Logger, logging.INFO)
652 Local.stderr = StreamToLogger(Logger, logging.ERROR)
George Karpenkov47e54932018-09-27 01:10:59 +0000653 if not testProject(Args, *ProjArgs):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000654 self.ResultsDiffer.set()
655 self.TasksQueue.task_done()
656 except:
657 self.FailureFlag.set()
658 raise
659
660
George Karpenkov47e54932018-09-27 01:10:59 +0000661def testProject(Args, ID, ProjectBuildMode, IsReferenceBuild=False, Strictness=0):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000662 """
663 Test a given project.
664 :return TestsPassed: Whether tests have passed according
665 to the :param Strictness: criteria.
666 """
George Karpenkovf37d3a52018-02-08 21:22:42 +0000667 Local.stdout.write(" \n\n--- Building project %s\n" % (ID,))
Anna Zaks4720a732011-11-05 05:20:48 +0000668
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000669 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000670
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000671 Dir = getProjectDir(ID)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000672 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000673 Local.stdout.write(" Build directory: %s.\n" % (Dir,))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000674
Anna Zaksf0c41162011-10-06 23:26:27 +0000675 # Set the build results directory.
Jordan Rose01ac5722012-06-01 16:24:38 +0000676 RelOutputDir = getSBOutputDirName(IsReferenceBuild)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000677 SBOutputDir = os.path.join(Dir, RelOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000678
George Karpenkov47e54932018-09-27 01:10:59 +0000679 buildProject(Args, Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild)
Anna Zaksf0c41162011-10-06 23:26:27 +0000680
681 checkBuild(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000682
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000683 if IsReferenceBuild:
Devin Coughlin9ea80332016-01-23 01:09:07 +0000684 cleanupReferenceResults(SBOutputDir)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000685 TestsPassed = True
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000686 else:
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000687 TestsPassed = runCmpResults(Dir, Strictness)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000688
George Karpenkovf37d3a52018-02-08 21:22:42 +0000689 Local.stdout.write("Completed tests for project %s (time: %.2f).\n" % (
690 ID, (time.time() - TBegin)))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000691 return TestsPassed
George Karpenkova8076602017-10-02 17:59:12 +0000692
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000693
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000694def projectFileHandler():
695 return open(getProjectMapPath(), "rb")
696
George Karpenkova8076602017-10-02 17:59:12 +0000697
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000698def iterateOverProjects(PMapFile):
699 """
700 Iterate over all projects defined in the project file handler `PMapFile`
701 from the start.
702 """
703 PMapFile.seek(0)
George Karpenkova8076602017-10-02 17:59:12 +0000704 for I in csv.reader(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000705 if (SATestUtils.isCommentCSVLine(I)):
George Karpenkova8076602017-10-02 17:59:12 +0000706 continue
707 yield I
708
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000709
710def validateProjectFile(PMapFile):
711 """
712 Validate project file.
713 """
714 for I in iterateOverProjects(PMapFile):
George Karpenkovbf92c442017-10-24 23:52:48 +0000715 if len(I) != 2:
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000716 print("Error: Rows in the ProjectMapFile should have 2 entries.")
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000717 raise Exception()
George Karpenkovbf92c442017-10-24 23:52:48 +0000718 if I[1] not in ('0', '1', '2'):
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000719 print("Error: Second entry in the ProjectMapFile should be 0" \
720 " (single file), 1 (project), or 2(single file c++11).")
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000721 raise Exception()
722
George Karpenkov47e54932018-09-27 01:10:59 +0000723def singleThreadedTestAll(Args, ProjectsToTest):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000724 """
725 Run all projects.
726 :return: whether tests have passed.
727 """
728 Success = True
729 for ProjArgs in ProjectsToTest:
George Karpenkov47e54932018-09-27 01:10:59 +0000730 Success &= testProject(Args, *ProjArgs)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000731 return Success
George Karpenkova8076602017-10-02 17:59:12 +0000732
George Karpenkov47e54932018-09-27 01:10:59 +0000733def multiThreadedTestAll(Args, ProjectsToTest, Jobs):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000734 """
735 Run each project in a separate thread.
736
737 This is OK despite GIL, as testing is blocked
738 on launching external processes.
739
740 :return: whether tests have passed.
741 """
Serge Guelton1f88dc52018-12-13 07:44:19 +0000742 TasksQueue = queue.Queue()
George Karpenkovf37d3a52018-02-08 21:22:42 +0000743
744 for ProjArgs in ProjectsToTest:
745 TasksQueue.put(ProjArgs)
746
747 ResultsDiffer = threading.Event()
748 FailureFlag = threading.Event()
749
750 for i in range(Jobs):
George Karpenkov47e54932018-09-27 01:10:59 +0000751 T = TestProjectThread(Args, TasksQueue, ResultsDiffer, FailureFlag)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000752 T.start()
753
754 # Required to handle Ctrl-C gracefully.
755 while TasksQueue.unfinished_tasks:
756 time.sleep(0.1) # Seconds.
757 if FailureFlag.is_set():
758 Local.stderr.write("Test runner crashed\n")
759 sys.exit(1)
760 return not ResultsDiffer.is_set()
761
762
763def testAll(Args):
764 ProjectsToTest = []
765
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000766 with projectFileHandler() as PMapFile:
767 validateProjectFile(PMapFile)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000768
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000769 # Test the projects.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000770 for (ProjName, ProjBuildMode) in iterateOverProjects(PMapFile):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000771 ProjectsToTest.append((ProjName,
772 int(ProjBuildMode),
773 Args.regenerate,
774 Args.strictness))
775 if Args.jobs <= 1:
George Karpenkov47e54932018-09-27 01:10:59 +0000776 return singleThreadedTestAll(Args, ProjectsToTest)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000777 else:
George Karpenkov47e54932018-09-27 01:10:59 +0000778 return multiThreadedTestAll(Args, ProjectsToTest, Args.jobs)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000779
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000780
Anna Zaksf0c41162011-10-06 23:26:27 +0000781if __name__ == '__main__':
Gabor Horvath93fde942015-06-30 15:31:17 +0000782 # Parse command line arguments.
George Karpenkova8076602017-10-02 17:59:12 +0000783 Parser = argparse.ArgumentParser(
784 description='Test the Clang Static Analyzer.')
Gabor Horvath93fde942015-06-30 15:31:17 +0000785 Parser.add_argument('--strictness', dest='strictness', type=int, default=0,
George Karpenkova8076602017-10-02 17:59:12 +0000786 help='0 to fail on runtime errors, 1 to fail when the \
787 number of found bugs are different from the \
788 reference, 2 to fail on any difference from the \
789 reference. Default is 0.')
790 Parser.add_argument('-r', dest='regenerate', action='store_true',
791 default=False, help='Regenerate reference output.')
George Karpenkovf37d3a52018-02-08 21:22:42 +0000792 Parser.add_argument('-j', '--jobs', dest='jobs', type=int,
793 default=0,
794 help='Number of projects to test concurrently')
George Karpenkovac986832018-10-02 21:19:23 +0000795 Parser.add_argument('--extra-analyzer-config', dest='extra_analyzer_config',
796 type=str,
797 default="",
798 help="Arguments passed to to -analyzer-config")
Gabor Horvath93fde942015-06-30 15:31:17 +0000799 Args = Parser.parse_args()
800
George Karpenkovf37d3a52018-02-08 21:22:42 +0000801 TestsPassed = testAll(Args)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000802 if not TestsPassed:
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000803 print("ERROR: Tests failed.")
George Karpenkov65839bd2017-10-26 01:13:22 +0000804 sys.exit(42)