blob: 02eb887500d7793b5aaf2b87e96f3588ff1c5b58 [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
Artem Dergachev1a3b8012020-05-15 14:27:30 +030061
Serge Guelton1f88dc52018-12-13 07:44:19 +000062try:
63 import queue
64except ImportError:
65 import Queue as queue
Anna Zaksf0c41162011-10-06 23:26:27 +000066
George Karpenkov13d37482018-07-30 23:01:20 +000067###############################################################################
Ted Kremenek42c14422012-08-28 20:40:02 +000068# Helper functions.
George Karpenkov13d37482018-07-30 23:01:20 +000069###############################################################################
Anna Zaksf0c41162011-10-06 23:26:27 +000070
George Karpenkovf37d3a52018-02-08 21:22:42 +000071Local = threading.local()
72Local.stdout = sys.stdout
73Local.stderr = sys.stderr
74logging.basicConfig(
75 level=logging.DEBUG,
76 format='%(asctime)s:%(levelname)s:%(name)s: %(message)s')
Ted Kremenekf9a539d2012-08-28 20:40:04 +000077
Valeriy Savchenkoc98872e2020-05-14 13:31:01 +030078
79class StreamToLogger:
George Karpenkovf37d3a52018-02-08 21:22:42 +000080 def __init__(self, logger, log_level=logging.INFO):
81 self.logger = logger
82 self.log_level = log_level
83
84 def write(self, buf):
85 # Rstrip in order not to write an extra newline.
86 self.logger.log(self.log_level, buf.rstrip())
87
88 def flush(self):
89 pass
90
91 def fileno(self):
92 return 0
Anna Zaksde1f7f8b2012-01-10 18:10:25 +000093
George Karpenkova8076602017-10-02 17:59:12 +000094
Anna Zaksf0c41162011-10-06 23:26:27 +000095def getProjectMapPath():
Ted Kremenek3a0678e2015-09-08 03:50:52 +000096 ProjectMapPath = os.path.join(os.path.abspath(os.curdir),
Anna Zaksf0c41162011-10-06 23:26:27 +000097 ProjectMapFile)
98 if not os.path.exists(ProjectMapPath):
George Karpenkov13d37482018-07-30 23:01:20 +000099 Local.stdout.write("Error: Cannot find the Project Map file " +
100 ProjectMapPath +
101 "\nRunning script for the wrong directory?\n")
George Karpenkov65839bd2017-10-26 01:13:22 +0000102 sys.exit(1)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000103 return ProjectMapPath
Anna Zaksf0c41162011-10-06 23:26:27 +0000104
George Karpenkova8076602017-10-02 17:59:12 +0000105
Anna Zaksf0c41162011-10-06 23:26:27 +0000106def getProjectDir(ID):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000107 return os.path.join(os.path.abspath(os.curdir), ID)
Anna Zaksf0c41162011-10-06 23:26:27 +0000108
George Karpenkova8076602017-10-02 17:59:12 +0000109
110def getSBOutputDirName(IsReferenceBuild):
111 if IsReferenceBuild:
Anna Zaks4720a732011-11-05 05:20:48 +0000112 return SBOutputDirReferencePrefix + SBOutputDirName
George Karpenkova8076602017-10-02 17:59:12 +0000113 else:
Anna Zaks4720a732011-11-05 05:20:48 +0000114 return SBOutputDirName
115
George Karpenkov13d37482018-07-30 23:01:20 +0000116###############################################################################
Ted Kremenek42c14422012-08-28 20:40:02 +0000117# Configuration setup.
George Karpenkov13d37482018-07-30 23:01:20 +0000118###############################################################################
Ted Kremenek42c14422012-08-28 20:40:02 +0000119
George Karpenkova8076602017-10-02 17:59:12 +0000120
Ted Kremenek42c14422012-08-28 20:40:02 +0000121# Find Clang for static analysis.
George Karpenkovbe6c3292017-09-21 22:12:49 +0000122if 'CC' in os.environ:
123 Clang = os.environ['CC']
124else:
George Karpenkovbf92c442017-10-24 23:52:48 +0000125 Clang = SATestUtils.which("clang", os.environ['PATH'])
Ted Kremenek42c14422012-08-28 20:40:02 +0000126if not Clang:
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000127 print("Error: cannot find 'clang' in PATH")
George Karpenkov65839bd2017-10-26 01:13:22 +0000128 sys.exit(1)
Ted Kremenek42c14422012-08-28 20:40:02 +0000129
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000130# Number of jobs.
George Karpenkovf37d3a52018-02-08 21:22:42 +0000131MaxJobs = int(math.ceil(multiprocessing.cpu_count() * 0.75))
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000132
Ted Kremenek42c14422012-08-28 20:40:02 +0000133# Project map stores info about all the "registered" projects.
134ProjectMapFile = "projectMap.csv"
135
136# Names of the project specific scripts.
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000137# The script that downloads the project.
138DownloadScript = "download_project.sh"
Ted Kremenek42c14422012-08-28 20:40:02 +0000139# The script that needs to be executed before the build can start.
140CleanupScript = "cleanup_run_static_analyzer.sh"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000141# This is a file containing commands for scan-build.
Ted Kremenek42c14422012-08-28 20:40:02 +0000142BuildScript = "run_static_analyzer.cmd"
143
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000144# A comment in a build script which disables wrapping.
145NoPrefixCmd = "#NOPREFIX"
146
Ted Kremenek42c14422012-08-28 20:40:02 +0000147# The log file name.
148LogFolderName = "Logs"
149BuildLogName = "run_static_analyzer.log"
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000150# Summary file - contains the summary of the failures. Ex: This info can be be
Ted Kremenek42c14422012-08-28 20:40:02 +0000151# displayed when buildbot detects a build failure.
152NumOfFailuresInSummary = 10
153FailuresSummaryFileName = "failures.txt"
Ted Kremenek42c14422012-08-28 20:40:02 +0000154
155# The scan-build result directory.
156SBOutputDirName = "ScanBuildResults"
157SBOutputDirReferencePrefix = "Ref"
158
George Karpenkova8076602017-10-02 17:59:12 +0000159# The name of the directory storing the cached project source. If this
160# directory does not exist, the download script will be executed.
161# That script should create the "CachedSource" directory and download the
162# project source into it.
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000163CachedSourceDirName = "CachedSource"
164
165# The name of the directory containing the source code that will be analyzed.
166# Each time a project is analyzed, a fresh copy of its CachedSource directory
167# will be copied to the PatchedSource directory and then the local patches
168# in PatchfileName will be applied (if PatchfileName exists).
169PatchedSourceDirName = "PatchedSource"
170
171# The name of the patchfile specifying any changes that should be applied
172# to the CachedSource before analyzing.
173PatchfileName = "changes_for_analyzer.patch"
174
Ted Kremenek42c14422012-08-28 20:40:02 +0000175# The list of checkers used during analyzes.
Alp Tokerd4733632013-12-05 04:47:09 +0000176# Currently, consists of all the non-experimental checkers, plus a few alpha
Jordan Rose10ad0812013-04-05 17:55:07 +0000177# checkers we don't want to regress on.
George Karpenkova8076602017-10-02 17:59:12 +0000178Checkers = ",".join([
George Karpenkovaf76b4a2017-09-30 00:05:24 +0000179 "alpha.unix.SimpleStream",
180 "alpha.security.taint",
181 "cplusplus.NewDeleteLeaks",
182 "core",
183 "cplusplus",
184 "deadcode",
185 "security",
186 "unix",
187 "osx",
188 "nullability"
189])
Ted Kremenek42c14422012-08-28 20:40:02 +0000190
George Karpenkov71105812018-03-29 01:23:54 +0000191Verbose = 0
Ted Kremenek42c14422012-08-28 20:40:02 +0000192
George Karpenkov13d37482018-07-30 23:01:20 +0000193###############################################################################
Ted Kremenek42c14422012-08-28 20:40:02 +0000194# Test harness logic.
George Karpenkov13d37482018-07-30 23:01:20 +0000195###############################################################################
Ted Kremenek42c14422012-08-28 20:40:02 +0000196
George Karpenkova8076602017-10-02 17:59:12 +0000197
Anna Zaks42a44632011-11-02 20:46:50 +0000198def runCleanupScript(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000199 """
200 Run pre-processing script if any.
201 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000202 Cwd = os.path.join(Dir, PatchedSourceDirName)
Anna Zaks42a44632011-11-02 20:46:50 +0000203 ScriptPath = os.path.join(Dir, CleanupScript)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000204 SATestUtils.runScript(ScriptPath, PBuildLogFile, Cwd,
205 Stdout=Local.stdout, Stderr=Local.stderr)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000206
George Karpenkova8076602017-10-02 17:59:12 +0000207
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000208def runDownloadScript(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000209 """
210 Run the script to download the project, if it exists.
211 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000212 ScriptPath = os.path.join(Dir, DownloadScript)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000213 SATestUtils.runScript(ScriptPath, PBuildLogFile, Dir,
214 Stdout=Local.stdout, Stderr=Local.stderr)
Anna Zaksf0c41162011-10-06 23:26:27 +0000215
George Karpenkova8076602017-10-02 17:59:12 +0000216
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000217def downloadAndPatch(Dir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000218 """
219 Download the project and apply the local patchfile if it exists.
220 """
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000221 CachedSourceDirPath = os.path.join(Dir, CachedSourceDirName)
222
223 # If the we don't already have the cached source, run the project's
224 # download script to download it.
225 if not os.path.exists(CachedSourceDirPath):
George Karpenkova8076602017-10-02 17:59:12 +0000226 runDownloadScript(Dir, PBuildLogFile)
227 if not os.path.exists(CachedSourceDirPath):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000228 Local.stderr.write("Error: '%s' not found after download.\n" % (
229 CachedSourceDirPath))
George Karpenkov65839bd2017-10-26 01:13:22 +0000230 exit(1)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000231
232 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
233
234 # Remove potentially stale patched source.
235 if os.path.exists(PatchedSourceDirPath):
236 shutil.rmtree(PatchedSourceDirPath)
237
238 # Copy the cached source and apply any patches to the copy.
239 shutil.copytree(CachedSourceDirPath, PatchedSourceDirPath, symlinks=True)
240 applyPatch(Dir, PBuildLogFile)
241
George Karpenkova8076602017-10-02 17:59:12 +0000242
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000243def applyPatch(Dir, PBuildLogFile):
244 PatchfilePath = os.path.join(Dir, PatchfileName)
245 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
246 if not os.path.exists(PatchfilePath):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000247 Local.stdout.write(" No local patches.\n")
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000248 return
249
George Karpenkovf37d3a52018-02-08 21:22:42 +0000250 Local.stdout.write(" Applying patch.\n")
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000251 try:
Devin Coughlinab95cd22016-01-22 07:08:06 +0000252 check_call("patch -p1 < '%s'" % (PatchfilePath),
George Karpenkova8076602017-10-02 17:59:12 +0000253 cwd=PatchedSourceDirPath,
254 stderr=PBuildLogFile,
255 stdout=PBuildLogFile,
256 shell=True)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000257 except:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000258 Local.stderr.write("Error: Patch failed. See %s for details.\n" % (
259 PBuildLogFile.name))
George Karpenkov65839bd2017-10-26 01:13:22 +0000260 sys.exit(1)
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000261
George Karpenkova8076602017-10-02 17:59:12 +0000262
George Karpenkovac986832018-10-02 21:19:23 +0000263def generateAnalyzerConfig(Args):
264 Out = "serialize-stats=true,stable-report-filename=true"
265 if Args.extra_analyzer_config:
266 Out += "," + Args.extra_analyzer_config
267 return Out
268
269
George Karpenkov47e54932018-09-27 01:10:59 +0000270def runScanBuild(Args, Dir, SBOutputDir, PBuildLogFile):
George Karpenkova8076602017-10-02 17:59:12 +0000271 """
272 Build the project with scan-build by reading in the commands and
273 prefixing them with the scan-build options.
274 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000275 BuildScriptPath = os.path.join(Dir, BuildScript)
276 if not os.path.exists(BuildScriptPath):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000277 Local.stderr.write(
278 "Error: build script is not defined: %s\n" % BuildScriptPath)
George Karpenkov65839bd2017-10-26 01:13:22 +0000279 sys.exit(1)
Devin Coughlinf3695c82015-09-16 01:52:32 +0000280
281 AllCheckers = Checkers
George Karpenkova8076602017-10-02 17:59:12 +0000282 if 'SA_ADDITIONAL_CHECKERS' in os.environ:
Devin Coughlinf3695c82015-09-16 01:52:32 +0000283 AllCheckers = AllCheckers + ',' + os.environ['SA_ADDITIONAL_CHECKERS']
284
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000285 # Run scan-build from within the patched source directory.
286 SBCwd = os.path.join(Dir, PatchedSourceDirName)
287
George Karpenkova8076602017-10-02 17:59:12 +0000288 SBOptions = "--use-analyzer '%s' " % Clang
Devin Coughlin86f61a92016-01-22 18:45:22 +0000289 SBOptions += "-plist-html -o '%s' " % SBOutputDir
Devin Coughlinf3695c82015-09-16 01:52:32 +0000290 SBOptions += "-enable-checker " + AllCheckers + " "
Jordan Roseb18179d2013-01-24 23:07:59 +0000291 SBOptions += "--keep-empty "
George Karpenkovac986832018-10-02 21:19:23 +0000292 SBOptions += "-analyzer-config '%s' " % generateAnalyzerConfig(Args)
Mikhail R. Gadelhaafc62b72018-06-27 14:39:41 +0000293
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000294 # Always use ccc-analyze to ensure that we can locate the failures
Anna Zaks7b4f8a42013-05-31 02:31:09 +0000295 # directory.
296 SBOptions += "--override-compiler "
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000297 ExtraEnv = {}
Anna Zaksf0c41162011-10-06 23:26:27 +0000298 try:
299 SBCommandFile = open(BuildScriptPath, "r")
300 SBPrefix = "scan-build " + SBOptions + " "
301 for Command in SBCommandFile:
Jordan Rose7bd91862013-09-06 16:12:41 +0000302 Command = Command.strip()
Artem Dergachev11c8c2a2020-05-12 22:43:32 +0300303 if len(Command) == 0:
George Karpenkova8076602017-10-02 17:59:12 +0000304 continue
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000305
306 # Custom analyzer invocation specified by project.
307 # Communicate required information using environment variables
308 # instead.
309 if Command == NoPrefixCmd:
310 SBPrefix = ""
311 ExtraEnv['OUTPUT'] = SBOutputDir
George Karpenkov6e4ddf42018-07-02 17:10:40 +0000312 ExtraEnv['CC'] = Clang
George Karpenkovac986832018-10-02 21:19:23 +0000313 ExtraEnv['ANALYZER_CONFIG'] = generateAnalyzerConfig(Args)
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000314 continue
315
Artem Dergachev11c8c2a2020-05-12 22:43:32 +0300316 if Command.startswith("#"):
317 continue
318
Ted Kremenekf9a539d2012-08-28 20:40:04 +0000319 # If using 'make', auto imply a -jX argument
320 # to speed up analysis. xcodebuild will
321 # automatically use the maximum number of cores.
Jordan Rose64e4cf02012-11-26 19:59:57 +0000322 if (Command.startswith("make ") or Command == "make") and \
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000323 "-j" not in Command:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000324 Command += " -j%d" % MaxJobs
Anna Zaksf0c41162011-10-06 23:26:27 +0000325 SBCommand = SBPrefix + Command
George Karpenkovf37d3a52018-02-08 21:22:42 +0000326
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000327 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000328 Local.stdout.write(" Executing: %s\n" % (SBCommand,))
George Karpenkova8076602017-10-02 17:59:12 +0000329 check_call(SBCommand, cwd=SBCwd,
330 stderr=PBuildLogFile,
331 stdout=PBuildLogFile,
George Karpenkov5c23d6a2018-06-29 22:05:32 +0000332 env=dict(os.environ, **ExtraEnv),
George Karpenkova8076602017-10-02 17:59:12 +0000333 shell=True)
George Karpenkove58044d2017-10-27 22:39:54 +0000334 except CalledProcessError:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000335 Local.stderr.write("Error: scan-build failed. Its output was: \n")
George Karpenkove58044d2017-10-27 22:39:54 +0000336 PBuildLogFile.seek(0)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000337 shutil.copyfileobj(PBuildLogFile, Local.stderr)
George Karpenkove58044d2017-10-27 22:39:54 +0000338 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000339
George Karpenkova8076602017-10-02 17:59:12 +0000340
George Karpenkovac986832018-10-02 21:19:23 +0000341def runAnalyzePreprocessed(Args, Dir, SBOutputDir, Mode):
George Karpenkova8076602017-10-02 17:59:12 +0000342 """
343 Run analysis on a set of preprocessed files.
344 """
Anna Zaks4720a732011-11-05 05:20:48 +0000345 if os.path.exists(os.path.join(Dir, BuildScript)):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000346 Local.stderr.write(
347 "Error: The preprocessed files project should not contain %s\n" % (
348 BuildScript))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000349 raise Exception()
Anna Zaks4720a732011-11-05 05:20:48 +0000350
Artem Dergacheve8b29c02019-05-29 18:49:31 +0000351 CmdPrefix = Clang + " --analyze "
Devin Coughlinbace0322015-09-14 21:22:24 +0000352
Artem Dergacheve8b29c02019-05-29 18:49:31 +0000353 CmdPrefix += "--analyzer-output plist "
354 CmdPrefix += " -Xclang -analyzer-checker=" + Checkers
George Karpenkova8076602017-10-02 17:59:12 +0000355 CmdPrefix += " -fcxx-exceptions -fblocks "
Artem Dergacheve8b29c02019-05-29 18:49:31 +0000356 CmdPrefix += " -Xclang -analyzer-config -Xclang %s "\
357 % generateAnalyzerConfig(Args)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000358
George Karpenkova8076602017-10-02 17:59:12 +0000359 if (Mode == 2):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000360 CmdPrefix += "-std=c++11 "
361
Anna Zaks4720a732011-11-05 05:20:48 +0000362 PlistPath = os.path.join(Dir, SBOutputDir, "date")
George Karpenkova8076602017-10-02 17:59:12 +0000363 FailPath = os.path.join(PlistPath, "failures")
364 os.makedirs(FailPath)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000365
Anna Zaks4720a732011-11-05 05:20:48 +0000366 for FullFileName in glob.glob(Dir + "/*"):
367 FileName = os.path.basename(FullFileName)
368 Failed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000369
Anna Zaks4720a732011-11-05 05:20:48 +0000370 # Only run the analyzes on supported files.
George Karpenkovbf92c442017-10-24 23:52:48 +0000371 if SATestUtils.hasNoExtension(FileName):
Anna Zaks4720a732011-11-05 05:20:48 +0000372 continue
George Karpenkovbf92c442017-10-24 23:52:48 +0000373 if not SATestUtils.isValidSingleInputFile(FileName):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000374 Local.stderr.write(
375 "Error: Invalid single input file %s.\n" % (FullFileName,))
Anna Zaks4720a732011-11-05 05:20:48 +0000376 raise Exception()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000377
Anna Zaks4720a732011-11-05 05:20:48 +0000378 # Build and call the analyzer command.
Devin Coughlinab95cd22016-01-22 07:08:06 +0000379 OutputOption = "-o '%s.plist' " % os.path.join(PlistPath, FileName)
380 Command = CmdPrefix + OutputOption + ("'%s'" % FileName)
Valeriy Savchenkoc98872e2020-05-14 13:31:01 +0300381 LogFile = open(os.path.join(FailPath, FileName + ".stderr.txt"), "w+")
Anna Zaks4720a732011-11-05 05:20:48 +0000382 try:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000383 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000384 Local.stdout.write(" Executing: %s\n" % (Command,))
George Karpenkova8076602017-10-02 17:59:12 +0000385 check_call(Command, cwd=Dir, stderr=LogFile,
386 stdout=LogFile,
387 shell=True)
Serge Guelton3de41082018-12-03 12:11:21 +0000388 except CalledProcessError as e:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000389 Local.stderr.write("Error: Analyzes of %s failed. "
390 "See %s for details."
391 "Error code %d.\n" % (
392 FullFileName, LogFile.name, e.returncode))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000393 Failed = True
Anna Zaks4720a732011-11-05 05:20:48 +0000394 finally:
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000395 LogFile.close()
396
Anna Zaks4720a732011-11-05 05:20:48 +0000397 # If command did not fail, erase the log file.
George Karpenkova8076602017-10-02 17:59:12 +0000398 if not Failed:
399 os.remove(LogFile.name)
400
Anna Zaks4720a732011-11-05 05:20:48 +0000401
Devin Coughlin9ea80332016-01-23 01:09:07 +0000402def getBuildLogPath(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000403 return os.path.join(SBOutputDir, LogFolderName, BuildLogName)
404
Devin Coughlin9ea80332016-01-23 01:09:07 +0000405
406def removeLogFile(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000407 BuildLogPath = getBuildLogPath(SBOutputDir)
408 # Clean up the log file.
409 if (os.path.exists(BuildLogPath)):
410 RmCommand = "rm '%s'" % BuildLogPath
411 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000412 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
George Karpenkova8076602017-10-02 17:59:12 +0000413 check_call(RmCommand, shell=True)
414
Devin Coughlin9ea80332016-01-23 01:09:07 +0000415
George Karpenkov47e54932018-09-27 01:10:59 +0000416def buildProject(Args, Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000417 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000418
Devin Coughlin9ea80332016-01-23 01:09:07 +0000419 BuildLogPath = getBuildLogPath(SBOutputDir)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000420 Local.stdout.write("Log file: %s\n" % (BuildLogPath,))
421 Local.stdout.write("Output directory: %s\n" % (SBOutputDir, ))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000422
Devin Coughlin9ea80332016-01-23 01:09:07 +0000423 removeLogFile(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000424
Anna Zaks4720a732011-11-05 05:20:48 +0000425 # Clean up scan build results.
George Karpenkova8076602017-10-02 17:59:12 +0000426 if (os.path.exists(SBOutputDir)):
Devin Coughlinab95cd22016-01-22 07:08:06 +0000427 RmCommand = "rm -r '%s'" % SBOutputDir
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000428 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000429 Local.stdout.write(" Executing: %s\n" % (RmCommand,))
430 check_call(RmCommand, shell=True, stdout=Local.stdout,
431 stderr=Local.stderr)
Anna Zaks4720a732011-11-05 05:20:48 +0000432 assert(not os.path.exists(SBOutputDir))
433 os.makedirs(os.path.join(SBOutputDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000434
Anna Zaks4720a732011-11-05 05:20:48 +0000435 # Build and analyze the project.
Valeriy Savchenkoc98872e2020-05-14 13:31:01 +0300436 with open(BuildLogPath, "w+") as PBuildLogFile:
Anna Zaksa2f970b2012-09-06 23:30:27 +0000437 if (ProjectBuildMode == 1):
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000438 downloadAndPatch(Dir, PBuildLogFile)
439 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov47e54932018-09-27 01:10:59 +0000440 runScanBuild(Args, Dir, SBOutputDir, PBuildLogFile)
Anna Zaks4720a732011-11-05 05:20:48 +0000441 else:
George Karpenkovac986832018-10-02 21:19:23 +0000442 runAnalyzePreprocessed(Args, Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000443
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000444 if IsReferenceBuild:
Anna Zaks42a44632011-11-02 20:46:50 +0000445 runCleanupScript(Dir, PBuildLogFile)
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000446 normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000447
George Karpenkovf37d3a52018-02-08 21:22:42 +0000448 Local.stdout.write("Build complete (time: %.2f). "
449 "See the log for more details: %s\n" % (
450 (time.time() - TBegin), BuildLogPath))
George Karpenkova8076602017-10-02 17:59:12 +0000451
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000452
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000453def normalizeReferenceResults(Dir, SBOutputDir, ProjectBuildMode):
454 """
455 Make the absolute paths relative in the reference results.
456 """
457 for (DirPath, Dirnames, Filenames) in os.walk(SBOutputDir):
458 for F in Filenames:
459 if (not F.endswith('plist')):
460 continue
461 Plist = os.path.join(DirPath, F)
462 Data = plistlib.readPlist(Plist)
463 PathPrefix = Dir
464 if (ProjectBuildMode == 1):
465 PathPrefix = os.path.join(Dir, PatchedSourceDirName)
George Karpenkova8076602017-10-02 17:59:12 +0000466 Paths = [SourceFile[len(PathPrefix) + 1:]
467 if SourceFile.startswith(PathPrefix)
468 else SourceFile for SourceFile in Data['files']]
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000469 Data['files'] = Paths
George Karpenkov318cd1f2017-10-24 23:52:46 +0000470
471 # Remove transient fields which change from run to run.
472 for Diag in Data['diagnostics']:
473 if 'HTMLDiagnostics_files' in Diag:
474 Diag.pop('HTMLDiagnostics_files')
475 if 'clang_version' in Data:
476 Data.pop('clang_version')
477
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000478 plistlib.writePlist(Data, Plist)
479
George Karpenkova8076602017-10-02 17:59:12 +0000480
Anna Zaksf0c41162011-10-06 23:26:27 +0000481def CleanUpEmptyPlists(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000482 """
483 A plist file is created for each call to the analyzer(each source file).
484 We are only interested on the once that have bug reports,
485 so delete the rest.
486 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000487 for F in glob.glob(SBOutputDir + "/*/*.plist"):
488 P = os.path.join(SBOutputDir, F)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000489
Artem Dergachev8cbd3f42020-05-13 14:06:44 +0300490 try:
491 Data = plistlib.readPlist(P)
492 # Delete empty reports.
493 if not Data['files']:
494 os.remove(P)
495 continue
Artem Dergachev1a3b8012020-05-15 14:27:30 +0300496 except plistlib.InvalidFileException as e:
Artem Dergachev8cbd3f42020-05-13 14:06:44 +0300497 print('Error parsing plist file %s: %s' % (P, str(e)))
Anna Zaksf0c41162011-10-06 23:26:27 +0000498 continue
499
George Karpenkova8076602017-10-02 17:59:12 +0000500
George Karpenkov3c128cb2017-10-30 19:40:33 +0000501def CleanUpEmptyFolders(SBOutputDir):
502 """
503 Remove empty folders from results, as git would not store them.
504 """
505 Subfolders = glob.glob(SBOutputDir + "/*")
506 for Folder in Subfolders:
507 if not os.listdir(Folder):
508 os.removedirs(Folder)
509
510
Anna Zaksf0c41162011-10-06 23:26:27 +0000511def checkBuild(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000512 """
513 Given the scan-build output directory, checks if the build failed
514 (by searching for the failures directories). If there are failures, it
515 creates a summary file in the output directory.
516
517 """
Anna Zaksf0c41162011-10-06 23:26:27 +0000518 # Check if there are failures.
519 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt")
George Karpenkova8076602017-10-02 17:59:12 +0000520 TotalFailed = len(Failures)
Anna Zaksf0c41162011-10-06 23:26:27 +0000521 if TotalFailed == 0:
Jordan Rose9858b122012-08-31 00:36:30 +0000522 CleanUpEmptyPlists(SBOutputDir)
George Karpenkov3c128cb2017-10-30 19:40:33 +0000523 CleanUpEmptyFolders(SBOutputDir)
Jordan Rose9858b122012-08-31 00:36:30 +0000524 Plists = glob.glob(SBOutputDir + "/*/*.plist")
George Karpenkovf37d3a52018-02-08 21:22:42 +0000525 Local.stdout.write(
526 "Number of bug reports (non-empty plist files) produced: %d\n" %
527 len(Plists))
George Karpenkova8076602017-10-02 17:59:12 +0000528 return
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000529
George Karpenkovf37d3a52018-02-08 21:22:42 +0000530 Local.stderr.write("Error: analysis failed.\n")
531 Local.stderr.write("Total of %d failures discovered.\n" % TotalFailed)
George Karpenkovff555ce2017-10-26 19:00:22 +0000532 if TotalFailed > NumOfFailuresInSummary:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000533 Local.stderr.write(
534 "See the first %d below.\n" % NumOfFailuresInSummary)
Anna Zaksf0c41162011-10-06 23:26:27 +0000535 # TODO: Add a line "See the results folder for more."
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000536
George Karpenkovff555ce2017-10-26 19:00:22 +0000537 Idx = 0
538 for FailLogPathI in Failures:
539 if Idx >= NumOfFailuresInSummary:
540 break
541 Idx += 1
George Karpenkovf37d3a52018-02-08 21:22:42 +0000542 Local.stderr.write("\n-- Error #%d -----------\n" % Idx)
George Karpenkovff555ce2017-10-26 19:00:22 +0000543 with open(FailLogPathI, "r") as FailLogI:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000544 shutil.copyfileobj(FailLogI, Local.stdout)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000545
George Karpenkov65839bd2017-10-26 01:13:22 +0000546 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000547
Anna Zaksf0c41162011-10-06 23:26:27 +0000548
George Karpenkova8076602017-10-02 17:59:12 +0000549def runCmpResults(Dir, Strictness=0):
550 """
551 Compare the warnings produced by scan-build.
552 Strictness defines the success criteria for the test:
553 0 - success if there are no crashes or analyzer failure.
554 1 - success if there are no difference in the number of reported bugs.
555 2 - success if all the bug reports are identical.
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000556
557 :return success: Whether tests pass according to the Strictness
558 criteria.
George Karpenkova8076602017-10-02 17:59:12 +0000559 """
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000560 TestsPassed = True
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000561 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000562
563 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName)
564 NewDir = os.path.join(Dir, SBOutputDirName)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000565
Anna Zaksf0c41162011-10-06 23:26:27 +0000566 # We have to go one level down the directory tree.
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000567 RefList = glob.glob(RefDir + "/*")
Anna Zaksf0c41162011-10-06 23:26:27 +0000568 NewList = glob.glob(NewDir + "/*")
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000569
Jordan Rosec7b992e2013-06-10 19:34:30 +0000570 # Log folders are also located in the results dir, so ignore them.
571 RefLogDir = os.path.join(RefDir, LogFolderName)
572 if RefLogDir in RefList:
573 RefList.remove(RefLogDir)
Anna Zaks4720a732011-11-05 05:20:48 +0000574 NewList.remove(os.path.join(NewDir, LogFolderName))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000575
George Karpenkov65839bd2017-10-26 01:13:22 +0000576 if len(RefList) != len(NewList):
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000577 print("Mismatch in number of results folders: %s vs %s" % (
578 RefList, NewList))
George Karpenkov65839bd2017-10-26 01:13:22 +0000579 sys.exit(1)
Anna Zaksf0c41162011-10-06 23:26:27 +0000580
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000581 # There might be more then one folder underneath - one per each scan-build
Anna Zaksf0c41162011-10-06 23:26:27 +0000582 # command (Ex: one for configure and one for make).
583 if (len(RefList) > 1):
584 # Assume that the corresponding folders have the same names.
585 RefList.sort()
586 NewList.sort()
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000587
Anna Zaksf0c41162011-10-06 23:26:27 +0000588 # Iterate and find the differences.
Anna Zaks767d3562011-11-08 19:56:31 +0000589 NumDiffs = 0
Serge Gueltond4589742018-12-18 16:04:21 +0000590 for P in zip(RefList, NewList):
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000591 RefDir = P[0]
Anna Zaksf0c41162011-10-06 23:26:27 +0000592 NewDir = P[1]
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000593
594 assert(RefDir != NewDir)
595 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000596 Local.stdout.write(" Comparing Results: %s %s\n" % (
597 RefDir, NewDir))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000598
Devin Coughlin2cb767d2015-11-07 18:27:35 +0000599 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName)
George Karpenkovfc782a32018-02-09 18:39:47 +0000600 Opts, Args = CmpRuns.generate_option_parser().parse_args(
George Karpenkov192d9a12018-02-12 22:13:01 +0000601 ["--rootA", "", "--rootB", PatchedSourceDirPath])
Anna Zaksf0c41162011-10-06 23:26:27 +0000602 # Scan the results, delete empty plist files.
Gabor Horvath93fde942015-06-30 15:31:17 +0000603 NumDiffs, ReportsInRef, ReportsInNew = \
George Karpenkovb7120c92018-02-13 23:36:01 +0000604 CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts,
605 deleteEmpty=False,
606 Stdout=Local.stdout)
George Karpenkova8076602017-10-02 17:59:12 +0000607 if (NumDiffs > 0):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000608 Local.stdout.write("Warning: %s differences in diagnostics.\n"
609 % NumDiffs)
Gabor Horvath93fde942015-06-30 15:31:17 +0000610 if Strictness >= 2 and NumDiffs > 0:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000611 Local.stdout.write("Error: Diffs found in strict mode (2).\n")
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000612 TestsPassed = False
Gabor Horvath93fde942015-06-30 15:31:17 +0000613 elif Strictness >= 1 and ReportsInRef != ReportsInNew:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000614 Local.stdout.write("Error: The number of results are different " +
615 " strict mode (1).\n")
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000616 TestsPassed = False
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000617
George Karpenkovf37d3a52018-02-08 21:22:42 +0000618 Local.stdout.write("Diagnostic comparison complete (time: %.2f).\n" % (
619 time.time() - TBegin))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000620 return TestsPassed
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000621
George Karpenkova8076602017-10-02 17:59:12 +0000622
Devin Coughlin9ea80332016-01-23 01:09:07 +0000623def cleanupReferenceResults(SBOutputDir):
George Karpenkova8076602017-10-02 17:59:12 +0000624 """
625 Delete html, css, and js files from reference results. These can
626 include multiple copies of the benchmark source and so get very large.
627 """
Devin Coughlin9ea80332016-01-23 01:09:07 +0000628 Extensions = ["html", "css", "js"]
629 for E in Extensions:
630 for F in glob.glob("%s/*/*.%s" % (SBOutputDir, E)):
631 P = os.path.join(SBOutputDir, F)
632 RmCommand = "rm '%s'" % P
633 check_call(RmCommand, shell=True)
634
635 # Remove the log file. It leaks absolute path names.
636 removeLogFile(SBOutputDir)
637
George Karpenkova8076602017-10-02 17:59:12 +0000638
George Karpenkovf37d3a52018-02-08 21:22:42 +0000639class TestProjectThread(threading.Thread):
George Karpenkov47e54932018-09-27 01:10:59 +0000640 def __init__(self, Args, TasksQueue, ResultsDiffer, FailureFlag):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000641 """
642 :param ResultsDiffer: Used to signify that results differ from
643 the canonical ones.
644 :param FailureFlag: Used to signify a failure during the run.
645 """
George Karpenkov47e54932018-09-27 01:10:59 +0000646 self.Args = Args
George Karpenkovf37d3a52018-02-08 21:22:42 +0000647 self.TasksQueue = TasksQueue
648 self.ResultsDiffer = ResultsDiffer
649 self.FailureFlag = FailureFlag
Valeriy Savchenkoc98872e2020-05-14 13:31:01 +0300650 super().__init__()
George Karpenkovf37d3a52018-02-08 21:22:42 +0000651
652 # Needed to gracefully handle interrupts with Ctrl-C
653 self.daemon = True
654
655 def run(self):
656 while not self.TasksQueue.empty():
657 try:
658 ProjArgs = self.TasksQueue.get()
659 Logger = logging.getLogger(ProjArgs[0])
660 Local.stdout = StreamToLogger(Logger, logging.INFO)
661 Local.stderr = StreamToLogger(Logger, logging.ERROR)
George Karpenkov47e54932018-09-27 01:10:59 +0000662 if not testProject(Args, *ProjArgs):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000663 self.ResultsDiffer.set()
664 self.TasksQueue.task_done()
665 except:
666 self.FailureFlag.set()
667 raise
668
669
George Karpenkov47e54932018-09-27 01:10:59 +0000670def testProject(Args, ID, ProjectBuildMode, IsReferenceBuild=False, Strictness=0):
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000671 """
672 Test a given project.
673 :return TestsPassed: Whether tests have passed according
674 to the :param Strictness: criteria.
675 """
George Karpenkovf37d3a52018-02-08 21:22:42 +0000676 Local.stdout.write(" \n\n--- Building project %s\n" % (ID,))
Anna Zaks4720a732011-11-05 05:20:48 +0000677
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000678 TBegin = time.time()
Anna Zaksf0c41162011-10-06 23:26:27 +0000679
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000680 Dir = getProjectDir(ID)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000681 if Verbose == 1:
George Karpenkovf37d3a52018-02-08 21:22:42 +0000682 Local.stdout.write(" Build directory: %s.\n" % (Dir,))
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000683
Anna Zaksf0c41162011-10-06 23:26:27 +0000684 # Set the build results directory.
Jordan Rose01ac5722012-06-01 16:24:38 +0000685 RelOutputDir = getSBOutputDirName(IsReferenceBuild)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000686 SBOutputDir = os.path.join(Dir, RelOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000687
George Karpenkov47e54932018-09-27 01:10:59 +0000688 buildProject(Args, Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild)
Anna Zaksf0c41162011-10-06 23:26:27 +0000689
690 checkBuild(SBOutputDir)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000691
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000692 if IsReferenceBuild:
Devin Coughlin9ea80332016-01-23 01:09:07 +0000693 cleanupReferenceResults(SBOutputDir)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000694 TestsPassed = True
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000695 else:
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000696 TestsPassed = runCmpResults(Dir, Strictness)
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000697
George Karpenkovf37d3a52018-02-08 21:22:42 +0000698 Local.stdout.write("Completed tests for project %s (time: %.2f).\n" % (
699 ID, (time.time() - TBegin)))
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000700 return TestsPassed
George Karpenkova8076602017-10-02 17:59:12 +0000701
Ted Kremenek3a0678e2015-09-08 03:50:52 +0000702
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000703def projectFileHandler():
Valeriy Savchenkoc98872e2020-05-14 13:31:01 +0300704 return open(getProjectMapPath(), "r")
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000705
George Karpenkova8076602017-10-02 17:59:12 +0000706
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000707def iterateOverProjects(PMapFile):
708 """
709 Iterate over all projects defined in the project file handler `PMapFile`
710 from the start.
711 """
712 PMapFile.seek(0)
Valeriy Savchenkoc98872e2020-05-14 13:31:01 +0300713 for ProjectInfo in csv.reader(PMapFile):
714 if (SATestUtils.isCommentCSVLine(ProjectInfo)):
George Karpenkova8076602017-10-02 17:59:12 +0000715 continue
Valeriy Savchenkoc98872e2020-05-14 13:31:01 +0300716 yield ProjectInfo
George Karpenkova8076602017-10-02 17:59:12 +0000717
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000718
719def validateProjectFile(PMapFile):
720 """
721 Validate project file.
722 """
Valeriy Savchenkoc98872e2020-05-14 13:31:01 +0300723 for ProjectInfo in iterateOverProjects(PMapFile):
724 if len(ProjectInfo) != 2:
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000725 print("Error: Rows in the ProjectMapFile should have 2 entries.")
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000726 raise Exception()
Valeriy Savchenkoc98872e2020-05-14 13:31:01 +0300727 if ProjectInfo[1] not in ('0', '1', '2'):
728 print("Error: Second entry in the ProjectMapFile should be 0"
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000729 " (single file), 1 (project), or 2(single file c++11).")
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000730 raise Exception()
731
Valeriy Savchenkoc98872e2020-05-14 13:31:01 +0300732
George Karpenkov47e54932018-09-27 01:10:59 +0000733def singleThreadedTestAll(Args, ProjectsToTest):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000734 """
735 Run all projects.
736 :return: whether tests have passed.
737 """
738 Success = True
739 for ProjArgs in ProjectsToTest:
George Karpenkov47e54932018-09-27 01:10:59 +0000740 Success &= testProject(Args, *ProjArgs)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000741 return Success
George Karpenkova8076602017-10-02 17:59:12 +0000742
Valeriy Savchenkoc98872e2020-05-14 13:31:01 +0300743
George Karpenkov47e54932018-09-27 01:10:59 +0000744def multiThreadedTestAll(Args, ProjectsToTest, Jobs):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000745 """
746 Run each project in a separate thread.
747
748 This is OK despite GIL, as testing is blocked
749 on launching external processes.
750
751 :return: whether tests have passed.
752 """
Serge Guelton1f88dc52018-12-13 07:44:19 +0000753 TasksQueue = queue.Queue()
George Karpenkovf37d3a52018-02-08 21:22:42 +0000754
755 for ProjArgs in ProjectsToTest:
756 TasksQueue.put(ProjArgs)
757
758 ResultsDiffer = threading.Event()
759 FailureFlag = threading.Event()
760
761 for i in range(Jobs):
George Karpenkov47e54932018-09-27 01:10:59 +0000762 T = TestProjectThread(Args, TasksQueue, ResultsDiffer, FailureFlag)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000763 T.start()
764
765 # Required to handle Ctrl-C gracefully.
766 while TasksQueue.unfinished_tasks:
767 time.sleep(0.1) # Seconds.
768 if FailureFlag.is_set():
769 Local.stderr.write("Test runner crashed\n")
770 sys.exit(1)
771 return not ResultsDiffer.is_set()
772
773
774def testAll(Args):
775 ProjectsToTest = []
776
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000777 with projectFileHandler() as PMapFile:
778 validateProjectFile(PMapFile)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000779
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000780 # Test the projects.
George Karpenkov3abfc3b2017-09-22 01:41:16 +0000781 for (ProjName, ProjBuildMode) in iterateOverProjects(PMapFile):
George Karpenkovf37d3a52018-02-08 21:22:42 +0000782 ProjectsToTest.append((ProjName,
783 int(ProjBuildMode),
784 Args.regenerate,
785 Args.strictness))
786 if Args.jobs <= 1:
George Karpenkov47e54932018-09-27 01:10:59 +0000787 return singleThreadedTestAll(Args, ProjectsToTest)
George Karpenkovf37d3a52018-02-08 21:22:42 +0000788 else:
George Karpenkov47e54932018-09-27 01:10:59 +0000789 return multiThreadedTestAll(Args, ProjectsToTest, Args.jobs)
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000790
Anna Zaks4c1ef9762012-02-03 06:35:23 +0000791
Anna Zaksf0c41162011-10-06 23:26:27 +0000792if __name__ == '__main__':
Gabor Horvath93fde942015-06-30 15:31:17 +0000793 # Parse command line arguments.
George Karpenkova8076602017-10-02 17:59:12 +0000794 Parser = argparse.ArgumentParser(
795 description='Test the Clang Static Analyzer.')
Gabor Horvath93fde942015-06-30 15:31:17 +0000796 Parser.add_argument('--strictness', dest='strictness', type=int, default=0,
George Karpenkova8076602017-10-02 17:59:12 +0000797 help='0 to fail on runtime errors, 1 to fail when the \
798 number of found bugs are different from the \
799 reference, 2 to fail on any difference from the \
800 reference. Default is 0.')
801 Parser.add_argument('-r', dest='regenerate', action='store_true',
802 default=False, help='Regenerate reference output.')
George Karpenkovf37d3a52018-02-08 21:22:42 +0000803 Parser.add_argument('-j', '--jobs', dest='jobs', type=int,
804 default=0,
805 help='Number of projects to test concurrently')
George Karpenkovac986832018-10-02 21:19:23 +0000806 Parser.add_argument('--extra-analyzer-config', dest='extra_analyzer_config',
807 type=str,
808 default="",
809 help="Arguments passed to to -analyzer-config")
Gabor Horvath93fde942015-06-30 15:31:17 +0000810 Args = Parser.parse_args()
811
George Karpenkovf37d3a52018-02-08 21:22:42 +0000812 TestsPassed = testAll(Args)
George Karpenkov1b51cbd2017-10-05 17:32:06 +0000813 if not TestsPassed:
Serge Gueltonc0ebe772018-12-18 08:36:33 +0000814 print("ERROR: Tests failed.")
George Karpenkov65839bd2017-10-26 01:13:22 +0000815 sys.exit(42)