blob: 347f1e2dd5e0208664d43f5597b8c02010d6f5cd [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()
Artem Dergachev11c8c2a2020-05-12 22:43:32 +0300301 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
Artem Dergachev11c8c2a2020-05-12 22:43:32 +0300314 if Command.startswith("#"):
315 continue
316
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000317 # If using 'make', auto imply a -jX argument
318 # to speed up analysis. xcodebuild will
319 # automatically use the maximum number of cores.
Jordan Rose64e4cf02012-11-26 19:59:57 +0000320 if (Command.startswith("make ") or Command == "make") and \
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000321 "-j" not in Command:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000322 Command += " -j%d" % MaxJobs
Anna Zaksf0c41162011-10-06 23:26:27 +0000323 SBCommand = SBPrefix + Command
George Karpenkovf37d3a52018-02-08 21:22:42 +0000324
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000325 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000326 Local.stdout.write(" Executing: %s\n" % (SBCommand,))
George Karpenkova8076602017-10-02 17:59:12 +0000327 check_call(SBCommand, cwd=SBCwd,
328 stderr=PBuildLogFile,
329 stdout=PBuildLogFile,
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000330 env=dict(os.environ, **ExtraEnv),
George Karpenkova8076602017-10-02 17:59:12 +0000331 shell=True)
George Karpenkove58044d2017-10-27 22:39:54 +0000332 except CalledProcessError:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000333 Local.stderr.write("Error: scan-build failed. Its output was: \n")
George Karpenkove58044d2017-10-27 22:39:54 +0000334 PBuildLogFile.seek(0)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000335 shutil.copyfileobj(PBuildLogFile, Local.stderr)
George Karpenkove58044d2017-10-27 22:39:54 +0000336 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000337
George Karpenkova8076602017-10-02 17:59:12 +0000338
George Karpenkovac986832018-10-02 21:19:23 +0000339def runAnalyzePreprocessed(Args, Dir, SBOutputDir, Mode):
George Karpenkova8076602017-10-02 17:59:12 +0000340 """
341 Run analysis on a set of preprocessed files.
342 """
Anna Zaks4720a732011-11-05 05:20:48 +0000343 if os.path.exists(os.path.join(Dir, BuildScript)):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000344 Local.stderr.write(
345 "Error: The preprocessed files project should not contain %s\n" % (
346 BuildScript))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000347 raise Exception()
Anna Zaks4720a732011-11-05 05:20:48 +0000348
Artem Dergacheve8b29c02019-05-29 18:49:31 +0000349 CmdPrefix = Clang + " --analyze "
Devin Coughlinbace0322015-09-14 21:22:24 +0000350
Artem Dergacheve8b29c02019-05-29 18:49:31 +0000351 CmdPrefix += "--analyzer-output plist "
352 CmdPrefix += " -Xclang -analyzer-checker=" + Checkers
George Karpenkova8076602017-10-02 17:59:12 +0000353 CmdPrefix += " -fcxx-exceptions -fblocks "
Artem Dergacheve8b29c02019-05-29 18:49:31 +0000354 CmdPrefix += " -Xclang -analyzer-config -Xclang %s "\
355 % generateAnalyzerConfig(Args)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000356
George Karpenkova8076602017-10-02 17:59:12 +0000357 if (Mode == 2):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000358 CmdPrefix += "-std=c++11 "
359
Anna Zaks4720a732011-11-05 05:20:48 +0000360 PlistPath = os.path.join(Dir, SBOutputDir, "date")
George Karpenkova8076602017-10-02 17:59:12 +0000361 FailPath = os.path.join(PlistPath, "failures")
362 os.makedirs(FailPath)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000363
Anna Zaks4720a732011-11-05 05:20:48 +0000364 for FullFileName in glob.glob(Dir + "/*"):
365 FileName = os.path.basename(FullFileName)
366 Failed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000367
Anna Zaks4720a732011-11-05 05:20:48 +0000368 # Only run the analyzes on supported files.
George Karpenkovbf92c442017-10-24 23:52:48 +0000369 if SATestUtils.hasNoExtension(FileName):
Anna Zaks4720a732011-11-05 05:20:48 +0000370 continue
George Karpenkovbf92c442017-10-24 23:52:48 +0000371 if not SATestUtils.isValidSingleInputFile(FileName):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000372 Local.stderr.write(
373 "Error: Invalid single input file %s.\n" % (FullFileName,))
Anna Zaks4720a732011-11-05 05:20:48 +0000374 raise Exception()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000375
Anna Zaks4720a732011-11-05 05:20:48 +0000376 # Build and call the analyzer command.
Devin Coughlinab95cd22016-01-22 07:08:06 +0000377 OutputOption = "-o '%s.plist' " % os.path.join(PlistPath, FileName)
378 Command = CmdPrefix + OutputOption + ("'%s'" % FileName)
Anna Zaks4720a732011-11-05 05:20:48 +0000379 LogFile = open(os.path.join(FailPath, FileName + ".stderr.txt"), "w+b")
380 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000381 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000382 Local.stdout.write(" Executing: %s\n" % (Command,))
George Karpenkova8076602017-10-02 17:59:12 +0000383 check_call(Command, cwd=Dir, stderr=LogFile,
384 stdout=LogFile,
385 shell=True)
Serge Guelton3de41082018-12-03 12:11:21 +0000386 except CalledProcessError as e:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000387 Local.stderr.write("Error: Analyzes of %s failed. "
388 "See %s for details."
389 "Error code %d.\n" % (
390 FullFileName, LogFile.name, e.returncode))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000391 Failed = True
Anna Zaks4720a732011-11-05 05:20:48 +0000392 finally:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000393 LogFile.close()
394
Anna Zaks4720a732011-11-05 05:20:48 +0000395 # If command did not fail, erase the log file.
George Karpenkova8076602017-10-02 17:59:12 +0000396 if not Failed:
397 os.remove(LogFile.name)
398
Anna Zaks4720a732011-11-05 05:20:48 +0000399
Devin Coughlin9ea80332016-01-23 01:09:07 +0000400def getBuildLogPath(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000401 return os.path.join(SBOutputDir, LogFolderName, BuildLogName)
402
Devin Coughlin9ea80332016-01-23 01:09:07 +0000403
404def removeLogFile(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000405 BuildLogPath = getBuildLogPath(SBOutputDir)
406 # Clean up the log file.
407 if (os.path.exists(BuildLogPath)):
408 RmCommand = "rm '%s'" % BuildLogPath
409 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000410 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
George Karpenkova8076602017-10-02 17:59:12 +0000411 check_call(RmCommand, shell=True)
412
Devin Coughlin9ea80332016-01-23 01:09:07 +0000413
George Karpenkov47e54932018-09-27 01:10:59 +0000414def buildProject(Args, Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000415 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000416
Devin Coughlin9ea80332016-01-23 01:09:07 +0000417 BuildLogPath = getBuildLogPath(SBOutputDir)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000418 Local.stdout.write("Log file: %s\n" % (BuildLogPath,))
419 Local.stdout.write("Output directory: %s\n" % (SBOutputDir, ))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000420
Devin Coughlin9ea80332016-01-23 01:09:07 +0000421 removeLogFile(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000422
Anna Zaks4720a732011-11-05 05:20:48 +0000423 # Clean up scan build results.
George Karpenkova8076602017-10-02 17:59:12 +0000424 if (os.path.exists(SBOutputDir)):
Devin Coughlinab95cd22016-01-22 07:08:06 +0000425 RmCommand = "rm -r '%s'" % SBOutputDir
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000426 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000427 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
428 check_call(RmCommand, shell=True, stdout=Local.stdout,
429 stderr=Local.stderr)
Anna Zaks4720a732011-11-05 05:20:48 +0000430 assert(not os.path.exists(SBOutputDir))
431 os.makedirs(os.path.join(SBOutputDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000432
Anna Zaks4720a732011-11-05 05:20:48 +0000433 # Build and analyze the project.
George Karpenkov0a6dba72017-10-27 22:52:36 +0000434 with open(BuildLogPath, "wb+") as PBuildLogFile:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000435 if (ProjectBuildMode == 1):
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000436 downloadAndPatch(Dir, PBuildLogFile)
437 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov47e54932018-09-27 01:10:59 +0000438 runScanBuild(Args, Dir, SBOutputDir, PBuildLogFile)
Anna Zaks4720a732011-11-05 05:20:48 +0000439 else:
George Karpenkovac986832018-10-02 21:19:23 +0000440 runAnalyzePreprocessed(Args, Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000441
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000442 if IsReferenceBuild:
Anna Zaks42a44632011-11-02 20:46:50 +0000443 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000444 normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000445
George Karpenkovf37d3a52018-02-08 21:22:42 +0000446 Local.stdout.write("Build complete (time: %.2f). "
447 "See the log for more details: %s\n" % (
448 (time.time() - TBegin), BuildLogPath))
George Karpenkova8076602017-10-02 17:59:12 +0000449
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000450
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000451def normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode):
452 """
453 Make the absolute paths relative in the reference results.
454 """
455 for (DirPath, Dirnames, Filenames) in os.walk(SBOutputDir):
456 for F in Filenames:
457 if (not F.endswith('plist')):
458 continue
459 Plist = os.path.join(DirPath, F)
460 Data = plistlib.readPlist(Plist)
461 PathPrefix = Dir
462 if (ProjectBuildMode == 1):
463 PathPrefix = os.path.join(Dir, PatchedSourceDirName)
George Karpenkova8076602017-10-02 17:59:12 +0000464 Paths = [SourceFile[len(PathPrefix) + 1:]
465 if SourceFile.startswith(PathPrefix)
466 else SourceFile for SourceFile in Data['files']]
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000467 Data['files'] = Paths
George Karpenkov318cd1f2017-10-24 23:52:46 +0000468
469 # Remove transient fields which change from run to run.
470 for Diag in Data['diagnostics']:
471 if 'HTMLDiagnostics_files' in Diag:
472 Diag.pop('HTMLDiagnostics_files')
473 if 'clang_version' in Data:
474 Data.pop('clang_version')
475
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000476 plistlib.writePlist(Data, Plist)
477
George Karpenkova8076602017-10-02 17:59:12 +0000478
Anna Zaksf0c41162011-10-06 23:26:27 +0000479def CleanUpEmptyPlists(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000480 """
481 A plist file is created for each call to the analyzer(each source file).
482 We are only interested on the once that have bug reports,
483 so delete the rest.
484 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000485 for F in glob.glob(SBOutputDir + "/*/*.plist"):
486 P = os.path.join(SBOutputDir, F)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000487
Anna Zaksf0c41162011-10-06 23:26:27 +0000488 Data = plistlib.readPlist(P)
489 # Delete empty reports.
490 if not Data['files']:
491 os.remove(P)
492 continue
493
George Karpenkova8076602017-10-02 17:59:12 +0000494
George Karpenkov3c128cb2017-10-30 19:40:33 +0000495def CleanUpEmptyFolders(SBOutputDir):
496 """
497 Remove empty folders from results, as git would not store them.
498 """
499 Subfolders = glob.glob(SBOutputDir + "/*")
500 for Folder in Subfolders:
501 if not os.listdir(Folder):
502 os.removedirs(Folder)
503
504
Anna Zaksf0c41162011-10-06 23:26:27 +0000505def checkBuild(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000506 """
507 Given the scan-build output directory, checks if the build failed
508 (by searching for the failures directories). If there are failures, it
509 creates a summary file in the output directory.
510
511 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000512 # Check if there are failures.
513 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt")
George Karpenkova8076602017-10-02 17:59:12 +0000514 TotalFailed = len(Failures)
Anna Zaksf0c41162011-10-06 23:26:27 +0000515 if TotalFailed == 0:
Jordan Rose9858b122012-08-31 00:36:30 +0000516 CleanUpEmptyPlists(SBOutputDir)
George Karpenkov3c128cb2017-10-30 19:40:33 +0000517 CleanUpEmptyFolders(SBOutputDir)
Jordan Rose9858b122012-08-31 00:36:30 +0000518 Plists = glob.glob(SBOutputDir + "/*/*.plist")
George Karpenkovf37d3a52018-02-08 21:22:42 +0000519 Local.stdout.write(
520 "Number of bug reports (non-empty plist files) produced: %d\n" %
521 len(Plists))
George Karpenkova8076602017-10-02 17:59:12 +0000522 return
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000523
George Karpenkovf37d3a52018-02-08 21:22:42 +0000524 Local.stderr.write("Error: analysis failed.\n")
525 Local.stderr.write("Total of %d failures discovered.\n" % TotalFailed)
George Karpenkovff555ce2017-10-26 19:00:22 +0000526 if TotalFailed > NumOfFailuresInSummary:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000527 Local.stderr.write(
528 "See the first %d below.\n" % NumOfFailuresInSummary)
Anna Zaksf0c41162011-10-06 23:26:27 +0000529 # TODO: Add a line "See the results folder for more."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000530
George Karpenkovff555ce2017-10-26 19:00:22 +0000531 Idx = 0
532 for FailLogPathI in Failures:
533 if Idx >= NumOfFailuresInSummary:
534 break
535 Idx += 1
George Karpenkovf37d3a52018-02-08 21:22:42 +0000536 Local.stderr.write("\n-- Error #%d -----------\n" % Idx)
George Karpenkovff555ce2017-10-26 19:00:22 +0000537 with open(FailLogPathI, "r") as FailLogI:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000538 shutil.copyfileobj(FailLogI, Local.stdout)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000539
George Karpenkov65839bd2017-10-26 01:13:22 +0000540 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000541
Anna Zaksf0c41162011-10-06 23:26:27 +0000542
George Karpenkova8076602017-10-02 17:59:12 +0000543def runCmpResults(Dir, Strictness=0):
544 """
545 Compare the warnings produced by scan-build.
546 Strictness defines the success criteria for the test:
547 0 - success if there are no crashes or analyzer failure.
548 1 - success if there are no difference in the number of reported bugs.
549 2 - success if all the bug reports are identical.
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000550
551 :return success: Whether tests pass according to the Strictness
552 criteria.
George Karpenkova8076602017-10-02 17:59:12 +0000553 """
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000554 TestsPassed = True
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000555 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000556
557 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName)
558 NewDir = os.path.join(Dir, SBOutputDirName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000559
Anna Zaksf0c41162011-10-06 23:26:27 +0000560 # We have to go one level down the directory tree.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000561 RefList = glob.glob(RefDir + "/*")
Anna Zaksf0c41162011-10-06 23:26:27 +0000562 NewList = glob.glob(NewDir + "/*")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000563
Jordan Rosec7b992e2013-06-10 19:34:30 +0000564 # Log folders are also located in the results dir, so ignore them.
565 RefLogDir = os.path.join(RefDir, LogFolderName)
566 if RefLogDir in RefList:
567 RefList.remove(RefLogDir)
Anna Zaks4720a732011-11-05 05:20:48 +0000568 NewList.remove(os.path.join(NewDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000569
George Karpenkov65839bd2017-10-26 01:13:22 +0000570 if len(RefList) != len(NewList):
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000571 print("Mismatch in number of results folders: %s vs %s" % (
572 RefList, NewList))
George Karpenkov65839bd2017-10-26 01:13:22 +0000573 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000574
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000575 # There might be more then one folder underneath - one per each scan-build
Anna Zaksf0c41162011-10-06 23:26:27 +0000576 # command (Ex: one for configure and one for make).
577 if (len(RefList) > 1):
578 # Assume that the corresponding folders have the same names.
579 RefList.sort()
580 NewList.sort()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000581
Anna Zaksf0c41162011-10-06 23:26:27 +0000582 # Iterate and find the differences.
Anna Zaks767d3562011-11-08 19:56:31 +0000583 NumDiffs = 0
Serge Gueltond4589742018-12-18 16:04:21 +0000584 for P in zip(RefList, NewList):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000585 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:
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000719 print("Error: Rows in the ProjectMapFile should have 2 entries.")
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000720 raise Exception()
George Karpenkovbf92c442017-10-24 23:52:48 +0000721 if I[1] not in ('0', '1', '2'):
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000722 print("Error: Second entry in the ProjectMapFile should be 0" \
723 " (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 """
Serge Guelton1f88dc52018-12-13 07:44:19 +0000745 TasksQueue = queue.Queue()
George Karpenkovf37d3a52018-02-08 21:22:42 +0000746
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:
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000806 print("ERROR: Tests failed.")
George Karpenkov65839bd2017-10-26 01:13:22 +0000807 sys.exit(42)