1 #!/usr/bin/env python 2 3 """ 4 Static Analyzer qualification infrastructure. 5 6 The goal is to test the analyzer against different projects, check for failures, 7 compare results, and measure performance. 8 9 Repository Directory will contain sources of the projects as well as the 10 information on how to build them and the expected output. 11 Repository Directory structure: 12 - ProjectMap file 13 - Historical Performance Data 14 - Project Dir1 15 - ReferenceOutput 16 - Project Dir2 17 - ReferenceOutput 18 .. 19 Note that the build tree must be inside the project dir. 20 21 To 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 27 The files which should be kept around for failure investigations: 28 RepositoryCopy/Project DirI/ScanBuildResults 29 RepositoryCopy/Project DirI/run_static_analyzer.log 30 31 Assumptions (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 36 For 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 40 The list of checkers tested are hardcoded in the Checkers variable. 41 For testing additional checkers, use the SA_ADDITIONAL_CHECKERS environment 42 variable. It should contain a comma separated list. 43 """ 44 import CmpRuns 45 46 import os 47 import csv 48 import sys 49 import glob 50 import math 51 import shutil 52 import time 53 import plistlib 54 import argparse 55 from subprocess import check_call, check_output, CalledProcessError 56 57 #------------------------------------------------------------------------------ 58 # Helper functions. 59 #------------------------------------------------------------------------------ 60 61 def 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 81 def 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. 113 class 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 120 sys.stdout = flushfile(sys.stdout) 121 122 def 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 131 def getProjectDir(ID): 132 return os.path.join(os.path.abspath(os.curdir), ID) 133 134 def 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. 145 Clang = which("clang", os.environ['PATH']) 146 if not Clang: 147 print "Error: cannot find 'clang' in PATH" 148 sys.exit(-1) 149 150 # Number of jobs. 151 Jobs = int(math.ceil(detectCPUs() * 0.75)) 152 153 # Project map stores info about all the "registered" projects. 154 ProjectMapFile = "projectMap.csv" 155 156 # Names of the project specific scripts. 157 # The script that downloads the project. 158 DownloadScript = "download_project.sh" 159 # The script that needs to be executed before the build can start. 160 CleanupScript = "cleanup_run_static_analyzer.sh" 161 # This is a file containing commands for scan-build. 162 BuildScript = "run_static_analyzer.cmd" 163 164 # The log file name. 165 LogFolderName = "Logs" 166 BuildLogName = "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. 169 NumOfFailuresInSummary = 10 170 FailuresSummaryFileName = "failures.txt" 171 # Summary of the result diffs. 172 DiffsSummaryFileName = "diffs.txt" 173 174 # The scan-build result directory. 175 SBOutputDirName = "ScanBuildResults" 176 SBOutputDirReferencePrefix = "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. 181 CachedSourceDirName = "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). 187 PatchedSourceDirName = "PatchedSource" 188 189 # The name of the patchfile specifying any changes that should be applied 190 # to the CachedSource before analyzing. 191 PatchfileName = "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. 196 Checkers="alpha.unix.SimpleStream,alpha.security.taint,cplusplus.NewDeleteLeaks,core,cplusplus,deadcode,security,unix,osx" 197 198 Verbose = 1 199 200 #------------------------------------------------------------------------------ 201 # Test harness logic. 202 #------------------------------------------------------------------------------ 203 204 # Run pre-processing script if any. 205 def 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. 211 def runDownloadScript(Dir, PBuildLogFile): 212 ScriptPath = os.path.join(Dir, DownloadScript) 213 runScript(ScriptPath, PBuildLogFile, Dir) 214 215 # Run the provided script if it exists. 216 def 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(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. 234 def 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 255 def 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. 275 def 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 " + Clang + " " 289 SBOptions += "-plist-html -o " + 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 319 def hasNoExtension(FileName): 320 (Root, Ext) = os.path.splitext(FileName) 321 if ((Ext == "")) : 322 return True 323 return False 324 325 def 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. 335 def 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. 343 def 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 " + os.path.join(PlistPath, FileName) + ".plist " 380 Command = CmdPrefix + OutputOption + 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 400 def buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild): 401 TBegin = time.time() 402 403 BuildLogPath = os.path.join(SBOutputDir, LogFolderName, BuildLogName) 404 print "Log file: %s" % (BuildLogPath,) 405 print "Output directory: %s" %(SBOutputDir, ) 406 407 # Clean up the log file. 408 if (os.path.exists(BuildLogPath)) : 409 RmCommand = "rm " + BuildLogPath 410 if Verbose == 1: 411 print " Executing: %s" % (RmCommand,) 412 check_call(RmCommand, shell=True) 413 414 # Clean up scan build results. 415 if (os.path.exists(SBOutputDir)) : 416 RmCommand = "rm -r " + SBOutputDir 417 if Verbose == 1: 418 print " Executing: %s" % (RmCommand,) 419 check_call(RmCommand, shell=True) 420 assert(not os.path.exists(SBOutputDir)) 421 os.makedirs(os.path.join(SBOutputDir, LogFolderName)) 422 423 # Open the log file. 424 PBuildLogFile = open(BuildLogPath, "wb+") 425 426 # Build and analyze the project. 427 try: 428 if (ProjectBuildMode == 1): 429 downloadAndPatch(Dir, PBuildLogFile) 430 runCleanupScript(Dir, PBuildLogFile) 431 runScanBuild(Dir, SBOutputDir, PBuildLogFile) 432 else: 433 runAnalyzePreprocessed(Dir, SBOutputDir, ProjectBuildMode) 434 435 if IsReferenceBuild : 436 runCleanupScript(Dir, PBuildLogFile) 437 438 # Make the absolute paths relative in the reference results. 439 for (DirPath, Dirnames, Filenames) in os.walk(SBOutputDir): 440 for F in Filenames: 441 if (not F.endswith('plist')): 442 continue 443 Plist = os.path.join(DirPath, F) 444 Data = plistlib.readPlist(Plist) 445 PathPrefix = Dir 446 if (ProjectBuildMode == 1): 447 PathPrefix = os.path.join(Dir, PatchedSourceDirName) 448 Paths = [SourceFile[len(PathPrefix)+1:]\ 449 if SourceFile.startswith(PathPrefix)\ 450 else SourceFile for SourceFile in Data['files']] 451 Data['files'] = Paths 452 plistlib.writePlist(Data, Plist) 453 454 finally: 455 PBuildLogFile.close() 456 457 print "Build complete (time: %.2f). See the log for more details: %s" % \ 458 ((time.time()-TBegin), BuildLogPath) 459 460 # A plist file is created for each call to the analyzer(each source file). 461 # We are only interested on the once that have bug reports, so delete the rest. 462 def CleanUpEmptyPlists(SBOutputDir): 463 for F in glob.glob(SBOutputDir + "/*/*.plist"): 464 P = os.path.join(SBOutputDir, F) 465 466 Data = plistlib.readPlist(P) 467 # Delete empty reports. 468 if not Data['files']: 469 os.remove(P) 470 continue 471 472 # Given the scan-build output directory, checks if the build failed 473 # (by searching for the failures directories). If there are failures, it 474 # creates a summary file in the output directory. 475 def checkBuild(SBOutputDir): 476 # Check if there are failures. 477 Failures = glob.glob(SBOutputDir + "/*/failures/*.stderr.txt") 478 TotalFailed = len(Failures); 479 if TotalFailed == 0: 480 CleanUpEmptyPlists(SBOutputDir) 481 Plists = glob.glob(SBOutputDir + "/*/*.plist") 482 print "Number of bug reports (non-empty plist files) produced: %d" %\ 483 len(Plists) 484 return; 485 486 # Create summary file to display when the build fails. 487 SummaryPath = os.path.join(SBOutputDir, LogFolderName, FailuresSummaryFileName) 488 if (Verbose > 0): 489 print " Creating the failures summary file %s" % (SummaryPath,) 490 491 SummaryLog = open(SummaryPath, "w+") 492 try: 493 SummaryLog.write("Total of %d failures discovered.\n" % (TotalFailed,)) 494 if TotalFailed > NumOfFailuresInSummary: 495 SummaryLog.write("See the first %d below.\n" 496 % (NumOfFailuresInSummary,)) 497 # TODO: Add a line "See the results folder for more." 498 499 FailuresCopied = NumOfFailuresInSummary 500 Idx = 0 501 for FailLogPathI in Failures: 502 if Idx >= NumOfFailuresInSummary: 503 break; 504 Idx += 1 505 SummaryLog.write("\n-- Error #%d -----------\n" % (Idx,)); 506 FailLogI = open(FailLogPathI, "r"); 507 try: 508 shutil.copyfileobj(FailLogI, SummaryLog); 509 finally: 510 FailLogI.close() 511 finally: 512 SummaryLog.close() 513 514 print "Error: analysis failed. See ", SummaryPath 515 sys.exit(-1) 516 517 # Auxiliary object to discard stdout. 518 class Discarder(object): 519 def write(self, text): 520 pass # do nothing 521 522 # Compare the warnings produced by scan-build. 523 # Strictness defines the success criteria for the test: 524 # 0 - success if there are no crashes or analyzer failure. 525 # 1 - success if there are no difference in the number of reported bugs. 526 # 2 - success if all the bug reports are identical. 527 def runCmpResults(Dir, Strictness = 0): 528 TBegin = time.time() 529 530 RefDir = os.path.join(Dir, SBOutputDirReferencePrefix + SBOutputDirName) 531 NewDir = os.path.join(Dir, SBOutputDirName) 532 533 # We have to go one level down the directory tree. 534 RefList = glob.glob(RefDir + "/*") 535 NewList = glob.glob(NewDir + "/*") 536 537 # Log folders are also located in the results dir, so ignore them. 538 RefLogDir = os.path.join(RefDir, LogFolderName) 539 if RefLogDir in RefList: 540 RefList.remove(RefLogDir) 541 NewList.remove(os.path.join(NewDir, LogFolderName)) 542 543 if len(RefList) == 0 or len(NewList) == 0: 544 return False 545 assert(len(RefList) == len(NewList)) 546 547 # There might be more then one folder underneath - one per each scan-build 548 # command (Ex: one for configure and one for make). 549 if (len(RefList) > 1): 550 # Assume that the corresponding folders have the same names. 551 RefList.sort() 552 NewList.sort() 553 554 # Iterate and find the differences. 555 NumDiffs = 0 556 PairList = zip(RefList, NewList) 557 for P in PairList: 558 RefDir = P[0] 559 NewDir = P[1] 560 561 assert(RefDir != NewDir) 562 if Verbose == 1: 563 print " Comparing Results: %s %s" % (RefDir, NewDir) 564 565 DiffsPath = os.path.join(NewDir, DiffsSummaryFileName) 566 PatchedSourceDirPath = os.path.join(Dir, PatchedSourceDirName) 567 Opts = CmpRuns.CmpOptions(DiffsPath, "", PatchedSourceDirPath) 568 # Discard everything coming out of stdout (CmpRun produces a lot of them). 569 OLD_STDOUT = sys.stdout 570 sys.stdout = Discarder() 571 # Scan the results, delete empty plist files. 572 NumDiffs, ReportsInRef, ReportsInNew = \ 573 CmpRuns.dumpScanBuildResultsDiff(RefDir, NewDir, Opts, False) 574 sys.stdout = OLD_STDOUT 575 if (NumDiffs > 0) : 576 print "Warning: %r differences in diagnostics. See %s" % \ 577 (NumDiffs, DiffsPath,) 578 if Strictness >= 2 and NumDiffs > 0: 579 print "Error: Diffs found in strict mode (2)." 580 sys.exit(-1) 581 elif Strictness >= 1 and ReportsInRef != ReportsInNew: 582 print "Error: The number of results are different in strict mode (1)." 583 sys.exit(-1) 584 585 print "Diagnostic comparison complete (time: %.2f)." % (time.time()-TBegin) 586 return (NumDiffs > 0) 587 588 def updateSVN(Mode, ProjectsMap): 589 try: 590 ProjectsMap.seek(0) 591 for I in csv.reader(ProjectsMap): 592 ProjName = I[0] 593 Path = os.path.join(ProjName, getSBOutputDirName(True)) 594 595 if Mode == "delete": 596 Command = "svn delete %s" % (Path,) 597 else: 598 Command = "svn add %s" % (Path,) 599 600 if Verbose == 1: 601 print " Executing: %s" % (Command,) 602 check_call(Command, shell=True) 603 604 if Mode == "delete": 605 CommitCommand = "svn commit -m \"[analyzer tests] Remove " \ 606 "reference results.\"" 607 else: 608 CommitCommand = "svn commit -m \"[analyzer tests] Add new " \ 609 "reference results.\"" 610 if Verbose == 1: 611 print " Executing: %s" % (CommitCommand,) 612 check_call(CommitCommand, shell=True) 613 except: 614 print "Error: SVN update failed." 615 sys.exit(-1) 616 617 def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Dir=None, Strictness = 0): 618 print " \n\n--- Building project %s" % (ID,) 619 620 TBegin = time.time() 621 622 if Dir is None : 623 Dir = getProjectDir(ID) 624 if Verbose == 1: 625 print " Build directory: %s." % (Dir,) 626 627 # Set the build results directory. 628 RelOutputDir = getSBOutputDirName(IsReferenceBuild) 629 SBOutputDir = os.path.join(Dir, RelOutputDir) 630 631 buildProject(Dir, SBOutputDir, ProjectBuildMode, IsReferenceBuild) 632 633 checkBuild(SBOutputDir) 634 635 if IsReferenceBuild == False: 636 runCmpResults(Dir, Strictness) 637 638 print "Completed tests for project %s (time: %.2f)." % \ 639 (ID, (time.time()-TBegin)) 640 641 def testAll(IsReferenceBuild = False, UpdateSVN = False, Strictness = 0): 642 PMapFile = open(getProjectMapPath(), "rb") 643 try: 644 # Validate the input. 645 for I in csv.reader(PMapFile): 646 if (len(I) != 2) : 647 print "Error: Rows in the ProjectMapFile should have 3 entries." 648 raise Exception() 649 if (not ((I[1] == "0") | (I[1] == "1") | (I[1] == "2"))): 650 print "Error: Second entry in the ProjectMapFile should be 0" \ 651 " (single file), 1 (project), or 2(single file c++11)." 652 raise Exception() 653 654 # When we are regenerating the reference results, we might need to 655 # update svn. Remove reference results from SVN. 656 if UpdateSVN == True: 657 assert(IsReferenceBuild == True); 658 updateSVN("delete", PMapFile); 659 660 # Test the projects. 661 PMapFile.seek(0) 662 for I in csv.reader(PMapFile): 663 testProject(I[0], int(I[1]), IsReferenceBuild, None, Strictness) 664 665 # Add reference results to SVN. 666 if UpdateSVN == True: 667 updateSVN("add", PMapFile); 668 669 except: 670 print "Error occurred. Premature termination." 671 raise 672 finally: 673 PMapFile.close() 674 675 if __name__ == '__main__': 676 # Parse command line arguments. 677 Parser = argparse.ArgumentParser(description='Test the Clang Static Analyzer.') 678 Parser.add_argument('--strictness', dest='strictness', type=int, default=0, 679 help='0 to fail on runtime errors, 1 to fail when the number\ 680 of found bugs are different from the reference, 2 to \ 681 fail on any difference from the reference. Default is 0.') 682 Parser.add_argument('-r', dest='regenerate', action='store_true', default=False, 683 help='Regenerate reference output.') 684 Parser.add_argument('-rs', dest='update_reference', action='store_true', 685 default=False, help='Regenerate reference output and update svn.') 686 Args = Parser.parse_args() 687 688 IsReference = False 689 UpdateSVN = False 690 Strictness = Args.strictness 691 if Args.regenerate: 692 IsReference = True 693 elif Args.update_reference: 694 IsReference = True 695 UpdateSVN = True 696 697 testAll(IsReference, UpdateSVN, Strictness) 698