1#!/usr/bin/env python 2 3""" 4Static Analyzer qualification infrastructure. 5 6The goal is to test the analyzer against different projects, check for failures, 7compare results, and measure performance. 8 9Repository Directory will contain sources of the projects as well as the 10information on how to build them and the expected output. 11Repository Directory structure: 12 - ProjectMap file 13 - Historical Performance Data 14 - Project Dir1 15 - ReferenceOutput 16 - Project Dir2 17 - ReferenceOutput 18 .. 19Note that the build tree must be inside the project dir. 20 21To test the build of the analyzer one would: 22 - Copy over a copy of the Repository Directory. (TODO: Prefer to ensure that 23 the build directory does not pollute the repository to min network traffic). 24 - Build all projects, until error. Produce logs to report errors. 25 - Compare results. 26 27The files which should be kept around for failure investigations: 28 RepositoryCopy/Project DirI/ScanBuildResults 29 RepositoryCopy/Project DirI/run_static_analyzer.log 30 31Assumptions (TODO: shouldn't need to assume these.): 32 The script is being run from the Repository Directory. 33 The compiler for scan-build and scan-build are in the PATH. 34 export PATH=/Users/zaks/workspace/c2llvm/build/Release+Asserts/bin:$PATH 35 36For more logging, set the env variables: 37 zaks:TI zaks$ export CCC_ANALYZER_LOG=1 38 zaks:TI zaks$ export CCC_ANALYZER_VERBOSE=1 39 40The list of checkers tested are hardcoded in the Checkers variable. 41For testing additional checkers, use the SA_ADDITIONAL_CHECKERS environment 42variable. It should contain a comma separated list. 43""" 44import CmpRuns 45 46import os 47import csv 48import sys 49import glob 50import math 51import shutil 52import time 53import plistlib 54import argparse 55from subprocess import check_call, check_output, CalledProcessError 56 57#------------------------------------------------------------------------------ 58# Helper functions. 59#------------------------------------------------------------------------------ 60 61def detectCPUs(): 62 """ 63 Detects the number of CPUs on a system. Cribbed from pp. 64 """ 65 # Linux, Unix and MacOS: 66 if hasattr(os, "sysconf"): 67 if os.sysconf_names.has_key("SC_NPROCESSORS_ONLN"): 68 # Linux & Unix: 69 ncpus = os.sysconf("SC_NPROCESSORS_ONLN") 70 if isinstance(ncpus, int) and ncpus > 0: 71 return ncpus 72 else: # OSX: 73 return int(capture(['sysctl', '-n', 'hw.ncpu'])) 74 # Windows: 75 if os.environ.has_key("NUMBER_OF_PROCESSORS"): 76 ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]) 77 if ncpus > 0: 78 return ncpus 79 return 1 # Default 80 81def which(command, paths = None): 82 """which(command, [paths]) - Look up the given command in the paths string 83 (or the PATH environment variable, if unspecified).""" 84 85 if paths is None: 86 paths = os.environ.get('PATH','') 87 88 # Check for absolute match first. 89 if os.path.exists(command): 90 return command 91 92 # Would be nice if Python had a lib function for this. 93 if not paths: 94 paths = os.defpath 95 96 # Get suffixes to search. 97 # On Cygwin, 'PATHEXT' may exist but it should not be used. 98 if os.pathsep == ';': 99 pathext = os.environ.get('PATHEXT', '').split(';') 100 else: 101 pathext = [''] 102 103 # Search the paths... 104 for path in paths.split(os.pathsep): 105 for ext in pathext: 106 p = os.path.join(path, command + ext) 107 if os.path.exists(p): 108 return p 109 110 return None 111 112# Make sure we flush the output after every print statement. 113class flushfile(object): 114 def __init__(self, f): 115 self.f = f 116 def write(self, x): 117 self.f.write(x) 118 self.f.flush() 119 120sys.stdout = flushfile(sys.stdout) 121 122def getProjectMapPath(): 123 ProjectMapPath = os.path.join(os.path.abspath(os.curdir), 124 ProjectMapFile) 125 if not os.path.exists(ProjectMapPath): 126 print "Error: Cannot find the Project Map file " + ProjectMapPath +\ 127 "\nRunning script for the wrong directory?" 128 sys.exit(-1) 129 return ProjectMapPath 130 131def getProjectDir(ID): 132 return os.path.join(os.path.abspath(os.curdir), ID) 133 134def getSBOutputDirName(IsReferenceBuild) : 135 if IsReferenceBuild == True : 136 return SBOutputDirReferencePrefix + SBOutputDirName 137 else : 138 return SBOutputDirName 139 140#------------------------------------------------------------------------------ 141# Configuration setup. 142#------------------------------------------------------------------------------ 143 144# Find Clang for static analysis. 145Clang = which("clang", os.environ['PATH']) 146if not Clang: 147 print "Error: cannot find 'clang' in PATH" 148 sys.exit(-1) 149 150# Number of jobs. 151Jobs = int(math.ceil(detectCPUs() * 0.75)) 152 153# Project map stores info about all the "registered" projects. 154ProjectMapFile = "projectMap.csv" 155 156# Names of the project specific scripts. 157# The script that downloads the project. 158DownloadScript = "download_project.sh" 159# The script that needs to be executed before the build can start. 160CleanupScript = "cleanup_run_static_analyzer.sh" 161# This is a file containing commands for scan-build. 162BuildScript = "run_static_analyzer.cmd" 163 164# The log file name. 165LogFolderName = "Logs" 166BuildLogName = "run_static_analyzer.log" 167# Summary file - contains the summary of the failures. Ex: This info can be be 168# displayed when buildbot detects a build failure. 169NumOfFailuresInSummary = 10 170FailuresSummaryFileName = "failures.txt" 171# Summary of the result diffs. 172DiffsSummaryFileName = "diffs.txt" 173 174# The scan-build result directory. 175SBOutputDirName = "ScanBuildResults" 176SBOutputDirReferencePrefix = "Ref" 177 178# The name of the directory storing the cached project source. If this directory 179# does not exist, the download script will be executed. That script should 180# create the "CachedSource" directory and download the project source into it. 181CachedSourceDirName = "CachedSource" 182 183# The name of the directory containing the source code that will be analyzed. 184# Each time a project is analyzed, a fresh copy of its CachedSource directory 185# will be copied to the PatchedSource directory and then the local patches 186# in PatchfileName will be applied (if PatchfileName exists). 187PatchedSourceDirName = "PatchedSource" 188 189# The name of the patchfile specifying any changes that should be applied 190# to the CachedSource before analyzing. 191PatchfileName = "changes_for_analyzer.patch" 192 193# The list of checkers used during analyzes. 194# Currently, consists of all the non-experimental checkers, plus a few alpha 195# checkers we don't want to regress on. 196Checkers="alpha.unix.SimpleStream,alpha.security.taint,cplusplus.NewDeleteLeaks,core,cplusplus,deadcode,security,unix,osx" 197 198Verbose = 1 199 200#------------------------------------------------------------------------------ 201# Test harness logic. 202#------------------------------------------------------------------------------ 203 204# Run pre-processing script if any. 205def runCleanupScript(Dir, PBuildLogFile): 206 Cwd = os.path.join(Dir, PatchedSourceDirName) 207 ScriptPath = os.path.join(Dir, CleanupScript) 208 runScript(ScriptPath, PBuildLogFile, Cwd) 209 210# Run the script to download the project, if it exists. 211def runDownloadScript(Dir, PBuildLogFile): 212 ScriptPath = os.path.join(Dir, DownloadScript) 213 runScript(ScriptPath, PBuildLogFile, Dir) 214 215# Run the provided script if it exists. 216def runScript(ScriptPath, PBuildLogFile, Cwd): 217 if os.path.exists(ScriptPath): 218 try: 219 if Verbose == 1: 220 print " Executing: %s" % (ScriptPath,) 221 check_call("chmod +x '%s'" % ScriptPath, cwd = Cwd, 222 stderr=PBuildLogFile, 223 stdout=PBuildLogFile, 224 shell=True) 225 check_call("'%s'" % ScriptPath, cwd = Cwd, stderr=PBuildLogFile, 226 stdout=PBuildLogFile, 227 shell=True) 228 except: 229 print "Error: Running %s failed. See %s for details." % (ScriptPath, 230 PBuildLogFile.name) 231 sys.exit(-1) 232 233# Download the project and apply the local patchfile if it exists. 234def downloadAndPatch(Dir, PBuildLogFile): 235 CachedSourceDirPath = os.path.join(Dir, CachedSourceDirName) 236 237 # If the we don't already have the cached source, run the project's 238 # download script to download it. 239 if not os.path.exists(CachedSourceDirPath): 240 runDownloadScript(Dir, PBuildLogFile) 241 if not os.path.exists(CachedSourceDirPath): 242 print "Error: '%s' not found after download." % (CachedSourceDirPath) 243 exit(-1) 244 245 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName) 246 247 # Remove potentially stale patched source. 248 if os.path.exists(PatchedSourceDirPath): 249 shutil.rmtree(PatchedSourceDirPath) 250 251 # Copy the cached source and apply any patches to the copy. 252 shutil.copytree(CachedSourceDirPath, PatchedSourceDirPath, symlinks=True) 253 applyPatch(Dir, PBuildLogFile) 254 255def applyPatch(Dir, PBuildLogFile): 256 PatchfilePath = os.path.join(Dir, PatchfileName) 257 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName) 258 if not os.path.exists(PatchfilePath): 259 print " No local patches." 260 return 261 262 print " Applying patch." 263 try: 264 check_call("patch -p1 < '%s'" % (PatchfilePath), 265 cwd = PatchedSourceDirPath, 266 stderr=PBuildLogFile, 267 stdout=PBuildLogFile, 268 shell=True) 269 except: 270 print "Error: Patch failed. See %s for details." % (PBuildLogFile.name) 271 sys.exit(-1) 272 273# Build the project with scan-build by reading in the commands and 274# prefixing them with the scan-build options. 275def runScanBuild(Dir, SBOutputDir, PBuildLogFile): 276 BuildScriptPath = os.path.join(Dir, BuildScript) 277 if not os.path.exists(BuildScriptPath): 278 print "Error: build script is not defined: %s" % BuildScriptPath 279 sys.exit(-1) 280 281 AllCheckers = Checkers 282 if os.environ.has_key('SA_ADDITIONAL_CHECKERS'): 283 AllCheckers = AllCheckers + ',' + os.environ['SA_ADDITIONAL_CHECKERS'] 284 285 # Run scan-build from within the patched source directory. 286 SBCwd = os.path.join(Dir, PatchedSourceDirName) 287 288 SBOptions = "--use-analyzer '%s' " % Clang 289 SBOptions += "-plist-html -o '%s' " % SBOutputDir 290 SBOptions += "-enable-checker " + AllCheckers + " " 291 SBOptions += "--keep-empty " 292 # Always use ccc-analyze to ensure that we can locate the failures 293 # directory. 294 SBOptions += "--override-compiler " 295 try: 296 SBCommandFile = open(BuildScriptPath, "r") 297 SBPrefix = "scan-build " + SBOptions + " " 298 for Command in SBCommandFile: 299 Command = Command.strip() 300 if len(Command) == 0: 301 continue; 302 # If using 'make', auto imply a -jX argument 303 # to speed up analysis. xcodebuild will 304 # automatically use the maximum number of cores. 305 if (Command.startswith("make ") or Command == "make") and \ 306 "-j" not in Command: 307 Command += " -j%d" % Jobs 308 SBCommand = SBPrefix + Command 309 if Verbose == 1: 310 print " Executing: %s" % (SBCommand,) 311 check_call(SBCommand, cwd = SBCwd, stderr=PBuildLogFile, 312 stdout=PBuildLogFile, 313 shell=True) 314 except: 315 print "Error: scan-build failed. See ",PBuildLogFile.name,\ 316 " for details." 317 raise 318 319def hasNoExtension(FileName): 320 (Root, Ext) = os.path.splitext(FileName) 321 if ((Ext == "")) : 322 return True 323 return False 324 325def isValidSingleInputFile(FileName): 326 (Root, Ext) = os.path.splitext(FileName) 327 if ((Ext == ".i") | (Ext == ".ii") | 328 (Ext == ".c") | (Ext == ".cpp") | 329 (Ext == ".m") | (Ext == "")) : 330 return True 331 return False 332 333# Get the path to the SDK for the given SDK name. Returns None if 334# the path cannot be determined. 335def getSDKPath(SDKName): 336 if which("xcrun") is None: 337 return None 338 339 Cmd = "xcrun --sdk " + SDKName + " --show-sdk-path" 340 return check_output(Cmd, shell=True).rstrip() 341 342# Run analysis on a set of preprocessed files. 343def runAnalyzePreprocessed(Dir, SBOutputDir, Mode): 344 if os.path.exists(os.path.join(Dir, BuildScript)): 345 print "Error: The preprocessed files project should not contain %s" % \ 346 BuildScript 347 raise Exception() 348 349 CmdPrefix = Clang + " -cc1 " 350 351 # For now, we assume the preprocessed files should be analyzed 352 # with the OS X SDK. 353 SDKPath = getSDKPath("macosx") 354 if SDKPath is not None: 355 CmdPrefix += "-isysroot " + SDKPath + " " 356 357 CmdPrefix += "-analyze -analyzer-output=plist -w " 358 CmdPrefix += "-analyzer-checker=" + Checkers +" -fcxx-exceptions -fblocks " 359 360 if (Mode == 2) : 361 CmdPrefix += "-std=c++11 " 362 363 PlistPath = os.path.join(Dir, SBOutputDir, "date") 364 FailPath = os.path.join(PlistPath, "failures"); 365 os.makedirs(FailPath); 366 367 for FullFileName in glob.glob(Dir + "/*"): 368 FileName = os.path.basename(FullFileName) 369 Failed = False 370 371 # Only run the analyzes on supported files. 372 if (hasNoExtension(FileName)): 373 continue 374 if (isValidSingleInputFile(FileName) == False): 375 print "Error: Invalid single input file %s." % (FullFileName,) 376 raise Exception() 377 378 # Build and call the analyzer command. 379 OutputOption = "-o '%s.plist' " % os.path.join(PlistPath, FileName) 380 Command = CmdPrefix + OutputOption + ("'%s'" % FileName) 381 LogFile = open(os.path.join(FailPath, FileName + ".stderr.txt"), "w+b") 382 try: 383 if Verbose == 1: 384 print " Executing: %s" % (Command,) 385 check_call(Command, cwd = Dir, stderr=LogFile, 386 stdout=LogFile, 387 shell=True) 388 except CalledProcessError, e: 389 print "Error: Analyzes of %s failed. See %s for details." \ 390 "Error code %d." % \ 391 (FullFileName, LogFile.name, e.returncode) 392 Failed = True 393 finally: 394 LogFile.close() 395 396 # If command did not fail, erase the log file. 397 if Failed == False: 398 os.remove(LogFile.name); 399 400def getBuildLogPath(SBOutputDir): 401 return os.path.join(SBOutputDir, LogFolderName, BuildLogName) 402 403def removeLogFile(SBOutputDir): 404 BuildLogPath = getBuildLogPath(SBOutputDir) 405 # Clean up the log file. 406 if (os.path.exists(BuildLogPath)) : 407 RmCommand = "rm '%s'" % BuildLogPath 408 if Verbose == 1: 409 print " Executing: %s" % (RmCommand,) 410 check_call(RmCommand, shell=True) 411 412def buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild): 413 TBegin = time.time() 414 415 BuildLogPath = getBuildLogPath(SBOutputDir) 416 print "Log file: %s" % (BuildLogPath,) 417 print "Output directory: %s" %(SBOutputDir, ) 418 419 removeLogFile(SBOutputDir) 420 421 # Clean up scan build results. 422 if (os.path.exists(SBOutputDir)) : 423 RmCommand = "rm -r '%s'" % SBOutputDir 424 if Verbose == 1: 425 print " Executing: %s" % (RmCommand,) 426 check_call(RmCommand, shell=True) 427 assert(not os.path.exists(SBOutputDir)) 428 os.makedirs(os.path.join(SBOutputDir, LogFolderName)) 429 430 # Open the log file. 431 PBuildLogFile = open(BuildLogPath, "wb+") 432 433 # Build and analyze the project. 434 try: 435 if (ProjectBuildMode == 1): 436 downloadAndPatch(Dir, PBuildLogFile) 437 runCleanupScript(Dir, PBuildLogFile) 438 runScanBuild(Dir, SBOutputDir, PBuildLogFile) 439 else: 440 runAnalyzePreprocessed(Dir, SBOutputDir, ProjectBuildMode) 441 442 if IsReferenceBuild : 443 runCleanupScript(Dir, PBuildLogFile) 444 445 # Make the absolute paths relative in the reference results. 446 for (DirPath, Dirnames, Filenames) in os.walk(SBOutputDir): 447 for F in Filenames: 448 if (not F.endswith('plist')): 449 continue 450 Plist = os.path.join(DirPath, F) 451 Data = plistlib.readPlist(Plist) 452 PathPrefix = Dir 453 if (ProjectBuildMode == 1): 454 PathPrefix = os.path.join(Dir, PatchedSourceDirName) 455 Paths = [SourceFile[len(PathPrefix)+1:]\ 456 if SourceFile.startswith(PathPrefix)\ 457 else SourceFile for SourceFile in Data['files']] 458 Data['files'] = Paths 459 plistlib.writePlist(Data, Plist) 460 461 finally: 462 PBuildLogFile.close() 463 464 print "Build complete (time: %.2f). See the log for more details: %s" % \ 465 ((time.time()-TBegin), BuildLogPath) 466 467# A plist file is created for each call to the analyzer(each source file). 468# We are only interested on the once that have bug reports, so delete the rest. 469def CleanUpEmptyPlists(SBOutputDir): 470 for F in glob.glob(SBOutputDir + "/*/*.plist"): 471 P = os.path.join(SBOutputDir, F) 472 473 Data = plistlib.readPlist(P) 474 # Delete empty reports. 475 if not Data['files']: 476 os.remove(P) 477 continue 478 479# Given the scan-build output directory, checks if the build failed 480# (by searching for the failures directories). If there are failures, it 481# creates a summary file in the output directory. 482def checkBuild(SBOutputDir): 483 # Check if there are failures. 484 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt") 485 TotalFailed = len(Failures); 486 if TotalFailed == 0: 487 CleanUpEmptyPlists(SBOutputDir) 488 Plists = glob.glob(SBOutputDir + "/*/*.plist") 489 print "Number of bug reports (non-empty plist files) produced: %d" %\ 490 len(Plists) 491 return; 492 493 # Create summary file to display when the build fails. 494 SummaryPath = os.path.join(SBOutputDir, LogFolderName, FailuresSummaryFileName) 495 if (Verbose > 0): 496 print " Creating the failures summary file %s" % (SummaryPath,) 497 498 SummaryLog = open(SummaryPath, "w+") 499 try: 500 SummaryLog.write("Total of %d failures discovered.\n" % (TotalFailed,)) 501 if TotalFailed > NumOfFailuresInSummary: 502 SummaryLog.write("See the first %d below.\n" 503 % (NumOfFailuresInSummary,)) 504 # TODO: Add a line "See the results folder for more." 505 506 FailuresCopied = NumOfFailuresInSummary 507 Idx = 0 508 for FailLogPathI in Failures: 509 if Idx >= NumOfFailuresInSummary: 510 break; 511 Idx += 1 512 SummaryLog.write("\n-- Error #%d -----------\n" % (Idx,)); 513 FailLogI = open(FailLogPathI, "r"); 514 try: 515 shutil.copyfileobj(FailLogI, SummaryLog); 516 finally: 517 FailLogI.close() 518 finally: 519 SummaryLog.close() 520 521 print "Error: analysis failed. See ", SummaryPath 522 sys.exit(-1) 523 524# Auxiliary object to discard stdout. 525class Discarder(object): 526 def write(self, text): 527 pass # do nothing 528 529# Compare the warnings produced by scan-build. 530# Strictness defines the success criteria for the test: 531# 0 - success if there are no crashes or analyzer failure. 532# 1 - success if there are no difference in the number of reported bugs. 533# 2 - success if all the bug reports are identical. 534def runCmpResults(Dir, Strictness = 0): 535 TBegin = time.time() 536 537 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName) 538 NewDir = os.path.join(Dir, SBOutputDirName) 539 540 # We have to go one level down the directory tree. 541 RefList = glob.glob(RefDir + "/*") 542 NewList = glob.glob(NewDir + "/*") 543 544 # Log folders are also located in the results dir, so ignore them. 545 RefLogDir = os.path.join(RefDir, LogFolderName) 546 if RefLogDir in RefList: 547 RefList.remove(RefLogDir) 548 NewList.remove(os.path.join(NewDir, LogFolderName)) 549 550 if len(RefList) == 0 or len(NewList) == 0: 551 return False 552 assert(len(RefList) == len(NewList)) 553 554 # There might be more then one folder underneath - one per each scan-build 555 # command (Ex: one for configure and one for make). 556 if (len(RefList) > 1): 557 # Assume that the corresponding folders have the same names. 558 RefList.sort() 559 NewList.sort() 560 561 # Iterate and find the differences. 562 NumDiffs = 0 563 PairList = zip(RefList, NewList) 564 for P in PairList: 565 RefDir = P[0] 566 NewDir = P[1] 567 568 assert(RefDir != NewDir) 569 if Verbose == 1: 570 print " Comparing Results: %s %s" % (RefDir, NewDir) 571 572 DiffsPath = os.path.join(NewDir, DiffsSummaryFileName) 573 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName) 574 Opts = CmpRuns.CmpOptions(DiffsPath, "", PatchedSourceDirPath) 575 # Discard everything coming out of stdout (CmpRun produces a lot of them). 576 OLD_STDOUT = sys.stdout 577 sys.stdout = Discarder() 578 # Scan the results, delete empty plist files. 579 NumDiffs, ReportsInRef, ReportsInNew = \ 580 CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts, False) 581 sys.stdout = OLD_STDOUT 582 if (NumDiffs > 0) : 583 print "Warning: %r differences in diagnostics. See %s" % \ 584 (NumDiffs, DiffsPath,) 585 if Strictness >= 2 and NumDiffs > 0: 586 print "Error: Diffs found in strict mode (2)." 587 sys.exit(-1) 588 elif Strictness >= 1 and ReportsInRef != ReportsInNew: 589 print "Error: The number of results are different in strict mode (1)." 590 sys.exit(-1) 591 592 print "Diagnostic comparison complete (time: %.2f)." % (time.time()-TBegin) 593 return (NumDiffs > 0) 594 595def cleanupReferenceResults(SBOutputDir): 596 # Delete html, css, and js files from reference results. These can 597 # include multiple copies of the benchmark source and so get very large. 598 Extensions = ["html", "css", "js"] 599 for E in Extensions: 600 for F in glob.glob("%s/*/*.%s" % (SBOutputDir, E)): 601 P = os.path.join(SBOutputDir, F) 602 RmCommand = "rm '%s'" % P 603 check_call(RmCommand, shell=True) 604 605 # Remove the log file. It leaks absolute path names. 606 removeLogFile(SBOutputDir) 607 608def updateSVN(Mode, ProjectsMap): 609 try: 610 ProjectsMap.seek(0) 611 for I in csv.reader(ProjectsMap): 612 ProjName = I[0] 613 Path = os.path.join(ProjName, getSBOutputDirName(True)) 614 615 if Mode == "delete": 616 Command = "svn delete '%s'" % (Path,) 617 else: 618 Command = "svn add '%s'" % (Path,) 619 620 if Verbose == 1: 621 print " Executing: %s" % (Command,) 622 check_call(Command, shell=True) 623 624 if Mode == "delete": 625 CommitCommand = "svn commit -m \"[analyzer tests] Remove " \ 626 "reference results.\"" 627 else: 628 CommitCommand = "svn commit -m \"[analyzer tests] Add new " \ 629 "reference results.\"" 630 if Verbose == 1: 631 print " Executing: %s" % (CommitCommand,) 632 check_call(CommitCommand, shell=True) 633 except: 634 print "Error: SVN update failed." 635 sys.exit(-1) 636 637def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Dir=None, Strictness = 0): 638 print " \n\n--- Building project %s" % (ID,) 639 640 TBegin = time.time() 641 642 if Dir is None : 643 Dir = getProjectDir(ID) 644 if Verbose == 1: 645 print " Build directory: %s." % (Dir,) 646 647 # Set the build results directory. 648 RelOutputDir = getSBOutputDirName(IsReferenceBuild) 649 SBOutputDir = os.path.join(Dir, RelOutputDir) 650 651 buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild) 652 653 checkBuild(SBOutputDir) 654 655 if IsReferenceBuild == False: 656 runCmpResults(Dir, Strictness) 657 else: 658 cleanupReferenceResults(SBOutputDir) 659 660 print "Completed tests for project %s (time: %.2f)." % \ 661 (ID, (time.time()-TBegin)) 662 663def testAll(IsReferenceBuild = False, UpdateSVN = False, Strictness = 0): 664 PMapFile = open(getProjectMapPath(), "rb") 665 try: 666 # Validate the input. 667 for I in csv.reader(PMapFile): 668 if (len(I) != 2) : 669 print "Error: Rows in the ProjectMapFile should have 3 entries." 670 raise Exception() 671 if (not ((I[1] == "0") | (I[1] == "1") | (I[1] == "2"))): 672 print "Error: Second entry in the ProjectMapFile should be 0" \ 673 " (single file), 1 (project), or 2(single file c++11)." 674 raise Exception() 675 676 # When we are regenerating the reference results, we might need to 677 # update svn. Remove reference results from SVN. 678 if UpdateSVN == True: 679 assert(IsReferenceBuild == True); 680 updateSVN("delete", PMapFile); 681 682 # Test the projects. 683 PMapFile.seek(0) 684 for I in csv.reader(PMapFile): 685 testProject(I[0], int(I[1]), IsReferenceBuild, None, Strictness) 686 687 # Add reference results to SVN. 688 if UpdateSVN == True: 689 updateSVN("add", PMapFile); 690 691 except: 692 print "Error occurred. Premature termination." 693 raise 694 finally: 695 PMapFile.close() 696 697if __name__ == '__main__': 698 # Parse command line arguments. 699 Parser = argparse.ArgumentParser(description='Test the Clang Static Analyzer.') 700 Parser.add_argument('--strictness', dest='strictness', type=int, default=0, 701 help='0 to fail on runtime errors, 1 to fail when the number\ 702 of found bugs are different from the reference, 2 to \ 703 fail on any difference from the reference. Default is 0.') 704 Parser.add_argument('-r', dest='regenerate', action='store_true', default=False, 705 help='Regenerate reference output.') 706 Parser.add_argument('-rs', dest='update_reference', action='store_true', 707 default=False, help='Regenerate reference output and update svn.') 708 Args = Parser.parse_args() 709 710 IsReference = False 711 UpdateSVN = False 712 Strictness = Args.strictness 713 if Args.regenerate: 714 IsReference = True 715 elif Args.update_reference: 716 IsReference = True 717 UpdateSVN = True 718 719 testAll(IsReference, UpdateSVN, Strictness) 720