Index: utils/analyzer/SATestBuild.py =================================================================== --- utils/analyzer/SATestBuild.py +++ utils/analyzer/SATestBuild.py @@ -45,24 +45,42 @@ import CmpRuns import SATestUtils -import os +from subprocess import CalledProcessError, check_call +import argparse import csv -import sys import glob +import logging import math +import multiprocessing +import operator +import os +import plistlib import shutil +import sys +import threading import time -import plistlib -import argparse -from subprocess import check_call, CalledProcessError -import multiprocessing #------------------------------------------------------------------------------ # Helper functions. #------------------------------------------------------------------------------ +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s:%(levelname)s:%(name)s:%(message)s') + +class StreamToLogger(object): + def __init__(self, logger, log_level=logging.INFO): + self.logger = logger + self.log_level = log_level + + def write(self, buf): + # Rstrip in order not to write an extra newline. + self.logger.log(self.log_level, buf.rstrip()) -sys.stdout = SATestUtils.flushfile(sys.stdout) + def flush(self): + pass + +# sys.stdout = SATestUtils.flushfile(sys.stdout) def getProjectMapPath(): @@ -565,6 +583,22 @@ removeLogFile(SBOutputDir) +class TestProjectThread(threading.Thread): + def __init__(self, ProjArgs): + self.ProjArgs = ProjArgs + self.Name = ProjArgs[0] + super(TestProjectThread, self).__init__(name=self.Name) + + # Needed to gracefully handle interrupts with Ctrl-C + self.daemon = True + + def run(self): + Logger = logging.getLogger(self.Name) + sys.stdout = StreamToLogger(Logger, logging.INFO) + sys.stderr = StreamToLogger(Logger, logging.ERROR) + self.status = testProject(*self.ProjArgs) + + def testProject(ID, ProjectBuildMode, IsReferenceBuild=False, Strictness=0): """ Test a given project. @@ -628,16 +662,45 @@ raise Exception() -def testAll(IsReferenceBuild=False, Strictness=0): - TestsPassed = True +def testAll(Args): + IsReferenceBuild = Args.regenerate + Strictness = Args.strictness + Jobs = Args.jobs + + ProjToTest = [] + with projectFileHandler() as PMapFile: validateProjectFile(PMapFile) # Test the projects. for (ProjName, ProjBuildMode) in iterateOverProjects(PMapFile): - TestsPassed &= testProject( - ProjName, int(ProjBuildMode), IsReferenceBuild, Strictness) - return TestsPassed + ProjToTest.append((ProjName, + int(ProjBuildMode), + IsReferenceBuild, + Strictness)) + if Jobs <= 1: + Out = [] + for ProjArgs in ProjToTest: + Out.append(testProject(*ProjArgs)) + return reduce(operator.and_, Out, True) + + # Run each project in a separate thread. + # This is OK despite GIL, as testing is blocked + # on launching external processes. + Threads = [] + for ProjArgs in ProjToTest: + T = TestProjectThread(ProjArgs) + T.start() + Threads.append(T) + Out = [] + for T in Threads: + while True: + T.join(100) + if not T.isAlive(): + break + Out.append(T.status) + + return reduce(operator.and_, Out, True) if __name__ == '__main__': @@ -651,14 +714,12 @@ reference. Default is 0.') Parser.add_argument('-r', dest='regenerate', action='store_true', default=False, help='Regenerate reference output.') + Parser.add_argument('-j', '--jobs', dest='jobs', type=int, + default=0, + help='Number of projects to test concurrently') Args = Parser.parse_args() - IsReference = False - Strictness = Args.strictness - if Args.regenerate: - IsReference = True - - TestsPassed = testAll(IsReference, Strictness) + TestsPassed = testAll(Args) if not TestsPassed: print "ERROR: Tests failed." sys.exit(42)