From 6fc0bdfc3227da0c9a019a5ae282b2ed5dc82254 Mon Sep 17 00:00:00 2001 From: Daniel Dunbar Date: Fri, 6 Mar 2009 22:20:40 +0000 Subject: [PATCH] Add my "clang compatible" test runner. Sorry for keeping this to myself... Usage: from clang/test, 'make -f ../utils/test/Makefile.multi' Pros: About 20% faster than the Makefile based version on my 8 core box (way faster on Windows I imagine, if it works). Cons: Needs some cleanup. Ctrl-C works quite poorly on Darwin; more Python's fault than mine. Future: Support config definitions so we can use this for running LLVM tests instead of dejagnu. Parallel testing goodness? Indeed. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@66293 91177308-0d34-0410-b5e6-96231b3b80d8 --- utils/test/Makefile.multi | 21 ++ utils/test/MultiTestRunner.py | 397 ++++++++++++++++++++++++++++++++++ utils/test/ProgressBar.py | 227 +++++++++++++++++++ utils/test/TestRunner.py | 208 ++++++++++++++++++ 4 files changed, 853 insertions(+) create mode 100644 utils/test/Makefile.multi create mode 100755 utils/test/MultiTestRunner.py create mode 100644 utils/test/ProgressBar.py create mode 100755 utils/test/TestRunner.py diff --git a/utils/test/Makefile.multi b/utils/test/Makefile.multi new file mode 100644 index 0000000000..3e9cd5669a --- /dev/null +++ b/utils/test/Makefile.multi @@ -0,0 +1,21 @@ +LEVEL = ../../.. +include $(LEVEL)/Makefile.common + +# Test in all immediate subdirectories if unset. +TESTDIRS ?= $(shell echo $(PROJ_SRC_DIR)/*/) + +ifndef TESTARGS +ifdef VERBOSE +TESTARGS = -v +else +TESTARGS = -s +endif +endif + +all:: + @ PATH=$(ToolDir):$(LLVM_SRC_ROOT)/test/Scripts:$$PATH VG=$(VG) ../utils/test/MultiTestRunner.py $(TESTARGS) $(TESTDIRS) + +clean:: + @ rm -rf Output/ + +.PHONY: all report clean diff --git a/utils/test/MultiTestRunner.py b/utils/test/MultiTestRunner.py new file mode 100755 index 0000000000..db5c560c03 --- /dev/null +++ b/utils/test/MultiTestRunner.py @@ -0,0 +1,397 @@ +#!/usr/bin/python + +""" +MultiTestRunner - Harness for running multiple tests in the simple clang style. + +TODO +-- + - Fix Ctrl-c issues + - Use a timeout + - Detect signalled failures (abort) + - Better support for finding tests +""" + +# TOD +import os, sys, re, random, time +import threading +import ProgressBar +import TestRunner +from TestRunner import TestStatus +from Queue import Queue + +kTestFileExtensions = set(['.mi','.i','.c','.cpp','.m','.mm','.ll']) + +kClangErrorRE = re.compile('(.*):([0-9]+):([0-9]+): error: (.*)') +kClangWarningRE = re.compile('(.*):([0-9]+):([0-9]+): warning: (.*)') +kAssertionRE = re.compile('Assertion failed: (.*, function .*, file .*, line [0-9]+\\.)') + +def getTests(inputs): + for path in inputs: + if not os.path.exists(path): + print >>sys.stderr,"WARNING: Invalid test \"%s\""%(path,) + continue + + if os.path.isdir(path): + for dirpath,dirnames,filenames in os.walk(path): + dotTests = os.path.join(dirpath,'.tests') + if os.path.exists(dotTests): + for ln in open(dotTests): + if ln.strip(): + yield os.path.join(dirpath,ln.strip()) + else: + # FIXME: This doesn't belong here + if 'Output' in dirnames: + dirnames.remove('Output') + for f in filenames: + base,ext = os.path.splitext(f) + if ext in kTestFileExtensions: + yield os.path.join(dirpath,f) + else: + yield path + +class TestingProgressDisplay: + def __init__(self, opts, numTests, progressBar=None): + self.opts = opts + self.numTests = numTests + self.digits = len(str(self.numTests)) + self.current = None + self.lock = threading.Lock() + self.progressBar = progressBar + self.progress = 0. + + def update(self, index, tr): + # Avoid locking overhead in quiet mode + if self.opts.quiet and not tr.failed(): + return + + # Output lock + self.lock.acquire() + try: + self.handleUpdate(index, tr) + finally: + self.lock.release() + + def finish(self): + if self.progressBar: + self.progressBar.clear() + elif self.opts.succinct: + sys.stdout.write('\n') + + def handleUpdate(self, index, tr): + if self.progressBar: + if tr.failed(): + self.progressBar.clear() + else: + # Force monotonicity + self.progress = max(self.progress, float(index)/self.numTests) + self.progressBar.update(self.progress, tr.path) + return + elif self.opts.succinct: + if not tr.failed(): + sys.stdout.write('.') + sys.stdout.flush() + return + else: + sys.stdout.write('\n') + + extra = '' + if tr.code==TestStatus.Invalid: + extra = ' - (Invalid test)' + elif tr.code==TestStatus.NoRunLine: + extra = ' - (No RUN line)' + elif tr.failed(): + extra = ' - %s'%(TestStatus.getName(tr.code).upper(),) + print '%*d/%*d - %s%s'%(self.digits, index+1, self.digits, + self.numTests, tr.path, extra) + + if tr.failed(): + msgs = [] + if tr.warnings: + msgs.append('%d warnings'%(len(tr.warnings),)) + if tr.errors: + msgs.append('%d errors'%(len(tr.errors),)) + if tr.assertions: + msgs.append('%d assertions'%(len(tr.assertions),)) + + if msgs: + print '\tFAIL (%s)'%(', '.join(msgs)) + for i,error in enumerate(set([e for (_,_,_,e) in tr.errors])): + print '\t\tERROR: %s'%(error,) + if i>20: + print '\t\t\t(too many errors, skipping)' + break + for assertion in set(tr.assertions): + print '\t\tASSERTION: %s'%(assertion,) + if self.opts.showOutput: + TestRunner.cat(tr.testResults, sys.stdout) + +class TestResult: + def __init__(self, path, code, testResults): + self.path = path + self.code = code + self.testResults = testResults + self.warnings = [] + self.errors = [] + self.assertions = [] + + if self.failed(): + f = open(self.testResults) + data = f.read() + f.close() + self.warnings = [m.groups() for m in kClangWarningRE.finditer(data)] + self.errors = [m.groups() for m in kClangErrorRE.finditer(data)] + self.assertions = [m.group(1) for m in kAssertionRE.finditer(data)] + + def failed(self): + return self.code in (TestStatus.Fail,TestStatus.XPass) + +class TestProvider: + def __init__(self, opts, tests, display): + self.opts = opts + self.tests = tests + self.index = 0 + self.lock = threading.Lock() + self.results = [None]*len(self.tests) + self.startTime = time.time() + self.progress = display + + def get(self): + self.lock.acquire() + try: + if self.opts.maxTime is not None: + if time.time() - self.startTime > self.opts.maxTime: + return None + if self.index >= len(self.tests): + return None + item = self.tests[self.index],self.index + self.index += 1 + return item + finally: + self.lock.release() + + def setResult(self, index, result): + self.results[index] = result + self.progress.update(index, result) + +class Tester(threading.Thread): + def __init__(self, provider): + threading.Thread.__init__(self) + self.provider = provider + + def run(self): + while 1: + item = self.provider.get() + if item is None: + break + self.runTest(item) + + def runTest(self, (path,index)): + command = path + # Use hand concatentation here because we want to override + # absolute paths. + output = 'Output/' + path + '.out' + testname = path + testresults = 'Output/' + path + '.testresults' + TestRunner.mkdir_p(os.path.dirname(testresults)) + numTests = len(self.provider.tests) + digits = len(str(numTests)) + code = None + try: + opts = self.provider.opts + if opts.debugDoNotTest: + code = None + else: + code = TestRunner.runOneTest(path, command, output, testname, + opts.clang, + useValgrind=opts.useValgrind, + useDGCompat=opts.useDGCompat, + useScript=opts.testScript, + output=open(testresults,'w')) + except KeyboardInterrupt: + # This is a sad hack. Unfortunately subprocess goes + # bonkers with ctrl-c and we start forking merrily. + print 'Ctrl-C detected, goodbye.' + os.kill(0,9) + + self.provider.setResult(index, TestResult(path, code, testresults)) + +def detectCPUs(): + """ + Detects the number of CPUs on a system. Cribbed from pp. + """ + # Linux, Unix and MacOS: + if hasattr(os, "sysconf"): + if os.sysconf_names.has_key("SC_NPROCESSORS_ONLN"): + # Linux & Unix: + ncpus = os.sysconf("SC_NPROCESSORS_ONLN") + if isinstance(ncpus, int) and ncpus > 0: + return ncpus + else: # OSX: + return int(os.popen2("sysctl -n hw.ncpu")[1].read()) + # Windows: + if os.environ.has_key("NUMBER_OF_PROCESSORS"): + ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]); + if ncpus > 0: + return ncpus + return 1 # Default + +def main(): + global options + from optparse import OptionParser + parser = OptionParser("usage: %prog [options] {inputs}") + parser.add_option("-j", "--threads", dest="numThreads", + help="Number of testing threads", + type=int, action="store", + default=detectCPUs()) + parser.add_option("", "--clang", dest="clang", + help="Program to use as \"clang\"", + action="store", default="clang") + parser.add_option("", "--vg", dest="useValgrind", + help="Run tests under valgrind", + action="store_true", default=False) + parser.add_option("", "--dg", dest="useDGCompat", + help="Use llvm dejagnu compatibility mode", + action="store_true", default=False) + parser.add_option("", "--script", dest="testScript", + help="Default script to use", + action="store", default=None) + parser.add_option("-v", "--verbose", dest="showOutput", + help="Show all test output", + action="store_true", default=False) + parser.add_option("-q", "--quiet", dest="quiet", + help="Suppress no error output", + action="store_true", default=False) + parser.add_option("-s", "--succinct", dest="succinct", + help="Reduce amount of output", + action="store_true", default=False) + parser.add_option("", "--max-tests", dest="maxTests", + help="Maximum number of tests to run", + action="store", type=int, default=None) + parser.add_option("", "--max-time", dest="maxTime", + help="Maximum time to spend testing (in seconds)", + action="store", type=float, default=None) + parser.add_option("", "--shuffle", dest="shuffle", + help="Run tests in random order", + action="store_true", default=False) + parser.add_option("", "--seed", dest="seed", + help="Seed for random number generator (default: random).", + action="store", default=None) + parser.add_option("", "--no-progress-bar", dest="useProgressBar", + help="Do not use curses based progress bar", + action="store_false", default=True) + parser.add_option("", "--debug-do-not-test", dest="debugDoNotTest", + help="DEBUG: Skip running actual test script", + action="store_true", default=False) + (opts, args) = parser.parse_args() + + if not args: + parser.error('No inputs specified') + + allTests = list(getTests(args)) + allTests.sort() + + tests = allTests + if opts.seed is not None: + try: + seed = int(opts.seed) + except: + parser.error('--seed argument should be an integer') + random.seed(seed) + if opts.shuffle: + random.shuffle(tests) + if opts.maxTests is not None: + tests = tests[:opts.maxTests] + + extra = '' + if len(tests) != len(allTests): + extra = ' of %d'%(len(allTests),) + header = '-- Testing: %d%s tests, %d threads --'%(len(tests),extra,opts.numThreads) + + progressBar = None + if not opts.quiet: + if opts.useProgressBar: + try: + tc = ProgressBar.TerminalController() + progressBar = ProgressBar.ProgressBar(tc, header) + except ValueError: + pass + + if not progressBar: + print header + + display = TestingProgressDisplay(opts, len(tests), progressBar) + provider = TestProvider(opts, tests, display) + + testers = [Tester(provider) for i in range(opts.numThreads)] + startTime = time.time() + for t in testers: + t.start() + try: + for t in testers: + t.join() + except KeyboardInterrupt: + sys.exit(1) + + display.finish() + + if not opts.quiet: + print 'Testing Time: %.2fs'%(time.time() - startTime) + + xfails = [i for i in provider.results if i and i.code==TestStatus.XFail] + if xfails: + print '*'*20 + print 'Expected Failures (%d):' % len(xfails) + for tr in xfails: + print '\t%s'%(tr.path,) + + xpasses = [i for i in provider.results if i and i.code==TestStatus.XPass] + if xpasses: + print '*'*20 + print 'Unexpected Passing Tests (%d):' % len(xpasses) + for tr in xpasses: + print '\t%s'%(tr.path,) + + failures = [i for i in provider.results if i and i.code==TestStatus.Fail] + if failures: + print '*'*20 + print 'Failing Tests (%d):' % len(failures) + for tr in failures: + if tr.code != TestStatus.XPass: + print '\t%s'%(tr.path,) + + print '\nFailures: %d'%(len(failures),) + + assertions = {} + errors = {} + errorFree = [] + for tr in failures: + if not tr.errors and not tr.assertions: + errorFree.append(tr) + for (_,_,_,error) in tr.errors: + errors[error] = errors.get(error,0) + 1 + for assertion in tr.assertions: + assertions[assertion] = assertions.get(assertion,0) + 1 + if errorFree: + print 'Failures w/o Errors (%d):' % len(errorFree) + for tr in errorFree: + print '\t%s'%(tr.path,) + + if errors: + print 'Error Summary (%d):' % sum(errors.values()) + items = errors.items() + items.sort(key = lambda (_,v): -v) + for i,(error,count) in enumerate(items): + print '\t%3d: %s'%(count,error) + if i>100: + print '\t\t(too many errors, skipping)' + break + + if assertions: + print 'Assertion Summary (%d):' % sum(assertions.values()) + items = assertions.items() + items.sort(key = lambda (_,v): -v) + for assertion,count in items: + print '\t%3d: %s'%(count,assertion) + +if __name__=='__main__': + main() diff --git a/utils/test/ProgressBar.py b/utils/test/ProgressBar.py new file mode 100644 index 0000000000..2e1f24ae79 --- /dev/null +++ b/utils/test/ProgressBar.py @@ -0,0 +1,227 @@ +#!/usr/bin/python + +# Source: http://code.activestate.com/recipes/475116/, with +# modifications by Daniel Dunbar. + +import sys, re, time + +class TerminalController: + """ + A class that can be used to portably generate formatted output to + a terminal. + + `TerminalController` defines a set of instance variables whose + values are initialized to the control sequence necessary to + perform a given action. These can be simply included in normal + output to the terminal: + + >>> term = TerminalController() + >>> print 'This is '+term.GREEN+'green'+term.NORMAL + + Alternatively, the `render()` method can used, which replaces + '${action}' with the string required to perform 'action': + + >>> term = TerminalController() + >>> print term.render('This is ${GREEN}green${NORMAL}') + + If the terminal doesn't support a given action, then the value of + the corresponding instance variable will be set to ''. As a + result, the above code will still work on terminals that do not + support color, except that their output will not be colored. + Also, this means that you can test whether the terminal supports a + given action by simply testing the truth value of the + corresponding instance variable: + + >>> term = TerminalController() + >>> if term.CLEAR_SCREEN: + ... print 'This terminal supports clearning the screen.' + + Finally, if the width and height of the terminal are known, then + they will be stored in the `COLS` and `LINES` attributes. + """ + # Cursor movement: + BOL = '' #: Move the cursor to the beginning of the line + UP = '' #: Move the cursor up one line + DOWN = '' #: Move the cursor down one line + LEFT = '' #: Move the cursor left one char + RIGHT = '' #: Move the cursor right one char + + # Deletion: + CLEAR_SCREEN = '' #: Clear the screen and move to home position + CLEAR_EOL = '' #: Clear to the end of the line. + CLEAR_BOL = '' #: Clear to the beginning of the line. + CLEAR_EOS = '' #: Clear to the end of the screen + + # Output modes: + BOLD = '' #: Turn on bold mode + BLINK = '' #: Turn on blink mode + DIM = '' #: Turn on half-bright mode + REVERSE = '' #: Turn on reverse-video mode + NORMAL = '' #: Turn off all modes + + # Cursor display: + HIDE_CURSOR = '' #: Make the cursor invisible + SHOW_CURSOR = '' #: Make the cursor visible + + # Terminal size: + COLS = None #: Width of the terminal (None for unknown) + LINES = None #: Height of the terminal (None for unknown) + + # Foreground colors: + BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = '' + + # Background colors: + BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = '' + BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = '' + + _STRING_CAPABILITIES = """ + BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1 + CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold + BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0 + HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split() + _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split() + _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split() + + def __init__(self, term_stream=sys.stdout): + """ + Create a `TerminalController` and initialize its attributes + with appropriate values for the current terminal. + `term_stream` is the stream that will be used for terminal + output; if this stream is not a tty, then the terminal is + assumed to be a dumb terminal (i.e., have no capabilities). + """ + # Curses isn't available on all platforms + try: import curses + except: return + + # If the stream isn't a tty, then assume it has no capabilities. + if not term_stream.isatty(): return + + # Check the terminal type. If we fail, then assume that the + # terminal has no capabilities. + try: curses.setupterm() + except: return + + # Look up numeric capabilities. + self.COLS = curses.tigetnum('cols') + self.LINES = curses.tigetnum('lines') + + # Look up string capabilities. + for capability in self._STRING_CAPABILITIES: + (attrib, cap_name) = capability.split('=') + setattr(self, attrib, self._tigetstr(cap_name) or '') + + # Colors + set_fg = self._tigetstr('setf') + if set_fg: + for i,color in zip(range(len(self._COLORS)), self._COLORS): + setattr(self, color, curses.tparm(set_fg, i) or '') + set_fg_ansi = self._tigetstr('setaf') + if set_fg_ansi: + for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS): + setattr(self, color, curses.tparm(set_fg_ansi, i) or '') + set_bg = self._tigetstr('setb') + if set_bg: + for i,color in zip(range(len(self._COLORS)), self._COLORS): + setattr(self, 'BG_'+color, curses.tparm(set_bg, i) or '') + set_bg_ansi = self._tigetstr('setab') + if set_bg_ansi: + for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS): + setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '') + + def _tigetstr(self, cap_name): + # String capabilities can include "delays" of the form "$<2>". + # For any modern terminal, we should be able to just ignore + # these, so strip them out. + import curses + cap = curses.tigetstr(cap_name) or '' + return re.sub(r'\$<\d+>[/*]?', '', cap) + + def render(self, template): + """ + Replace each $-substitutions in the given template string with + the corresponding terminal control string (if it's defined) or + '' (if it's not). + """ + return re.sub(r'\$\$|\${\w+}', self._render_sub, template) + + def _render_sub(self, match): + s = match.group() + if s == '$$': return s + else: return getattr(self, s[2:-1]) + +####################################################################### +# Example use case: progress bar +####################################################################### + +class ProgressBar: + """ + A 3-line progress bar, which looks like:: + + Header + 20% [===========----------------------------------] + progress message + + The progress bar is colored, if the terminal supports color + output; and adjusts to the width of the terminal. + """ + BAR = '%s${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}%s\n' + HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n' + + def __init__(self, term, header, useETA=True): + self.term = term + if not (self.term.CLEAR_EOL and self.term.UP and self.term.BOL): + raise ValueError("Terminal isn't capable enough -- you " + "should use a simpler progress dispaly.") + self.width = self.term.COLS or 75 + self.bar = term.render(self.BAR) + self.header = self.term.render(self.HEADER % header.center(self.width)) + self.cleared = 1 #: true if we haven't drawn the bar yet. + self.useETA = useETA + if self.useETA: + self.startTime = time.time() + self.update(0, '') + + def update(self, percent, message): + if self.cleared: + sys.stdout.write(self.header) + self.cleared = 0 + prefix = '%3d%% ' % (percent*100,) + suffix = '' + if self.useETA: + elapsed = time.time() - self.startTime + if percent > .0001 and elapsed > 1: + total = elapsed / percent + eta = int(total - elapsed) + h = eta//3600. + m = (eta//60) % 60 + s = eta % 60 + suffix = ' ETA: %02d:%02d:%02d'%(h,m,s) + barWidth = self.width - len(prefix) - len(suffix) - 2 + n = int(barWidth*percent) + if len(message) < self.width: + message = message + ' '*(self.width - len(message)) + else: + message = '... ' + message[-(self.width-4):] + sys.stdout.write( + self.term.BOL + self.term.UP + self.term.CLEAR_EOL + + (self.bar % (prefix, '='*n, '-'*(barWidth-n), suffix)) + + self.term.CLEAR_EOL + message) + + def clear(self): + if not self.cleared: + sys.stdout.write(self.term.BOL + self.term.CLEAR_EOL + + self.term.UP + self.term.CLEAR_EOL + + self.term.UP + self.term.CLEAR_EOL) + self.cleared = 1 + +def test(): + import time + tc = TerminalController() + p = ProgressBar(tc, 'Tests') + for i in range(101): + p.update(i/100., str(i)) + time.sleep(.3) + +if __name__=='__main__': + test() diff --git a/utils/test/TestRunner.py b/utils/test/TestRunner.py new file mode 100755 index 0000000000..6212f35572 --- /dev/null +++ b/utils/test/TestRunner.py @@ -0,0 +1,208 @@ +#!/usr/bin/python +# +# TestRunner.py - This script is used to run arbitrary unit tests. Unit +# tests must contain the command used to run them in the input file, starting +# immediately after a "RUN:" string. +# +# This runner recognizes and replaces the following strings in the command: +# +# %s - Replaced with the input name of the program, or the program to +# execute, as appropriate. +# %llvmgcc - llvm-gcc command +# %llvmgxx - llvm-g++ command +# %prcontext - prcontext.tcl script +# %t - temporary file name (derived from testcase name) +# + +import os +import sys +import subprocess +import errno +import re + +class TestStatus: + Pass = 0 + XFail = 1 + Fail = 2 + XPass = 3 + NoRunLine = 4 + Invalid = 5 + + kNames = ['Pass','XFail','Fail','XPass','NoRunLine','Invalid'] + @staticmethod + def getName(code): return TestStatus.kNames[code] + +def mkdir_p(path): + if not path: + pass + elif os.path.exists(path): + pass + else: + parent = os.path.dirname(path) + if parent != path: + mkdir_p(parent) + try: + os.mkdir(path) + except OSError,e: + if e.errno != errno.EEXIST: + raise + +def remove(path): + try: + os.remove(path) + except OSError: + pass + +def cat(path, output): + f = open(path) + output.writelines(f) + f.close() + +def runOneTest(FILENAME, SUBST, OUTPUT, TESTNAME, CLANG, + useValgrind=False, + useDGCompat=False, + useScript=None, + output=sys.stdout): + if useValgrind: + VG_OUTPUT = '%s.vg'%(OUTPUT,) + if os.path.exists: + remove(VG_OUTPUT) + CLANG = 'valgrind --leak-check=full --quiet --log-file=%s %s'%(VG_OUTPUT, CLANG) + + # Create the output directory if it does not already exist. + mkdir_p(os.path.dirname(OUTPUT)) + + # FIXME + #ulimit -t 40 + + # FIXME: Load script once + # FIXME: Support "short" script syntax + + if useScript: + scriptFile = useScript + else: + # See if we have a per-dir test script. + dirScriptFile = os.path.join(os.path.dirname(FILENAME), 'test.script') + if os.path.exists(dirScriptFile): + scriptFile = dirScriptFile + else: + scriptFile = FILENAME + + # Verify the script contains a run line. + for ln in open(scriptFile): + if 'RUN:' in ln: + break + else: + print >>output, "******************** TEST '%s' HAS NO RUN LINE! ********************"%(TESTNAME,) + output.flush() + return TestStatus.NoRunLine + + OUTPUT = os.path.abspath(OUTPUT) + FILENAME = os.path.abspath(FILENAME) + SCRIPT = OUTPUT + '.script' + TEMPOUTPUT = OUTPUT + '.tmp' + + substitutions = [('%s',SUBST), + ('%llvmgcc','llvm-gcc -emit-llvm -w'), + ('%llvmgxx','llvm-g++ -emit-llvm -w'), + ('%prcontext','prcontext.tcl'), + ('%t',TEMPOUTPUT), + ('clang',CLANG)] + scriptLines = [] + xfailLines = [] + for ln in open(scriptFile): + if 'RUN:' in ln: + # Isolate run parameters + index = ln.index('RUN:') + ln = ln[index+4:] + + # Apply substitutions + for a,b in substitutions: + ln = ln.replace(a,b) + + if useDGCompat: + ln = re.sub(r'\{(.*)\}', r'"\1"', ln) + scriptLines.append(ln) + elif 'XFAIL' in ln: + xfailLines.append(ln) + + if xfailLines: + print >>output, "XFAILED '%s':"%(TESTNAME,) + output.writelines(xfailLines) + + # Write script file + f = open(SCRIPT,'w') + f.write(''.join(scriptLines)) + f.close() + + outputFile = open(OUTPUT,'w') + p = None + try: + p = subprocess.Popen(["/bin/sh",SCRIPT], + cwd=os.path.dirname(FILENAME), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out,err = p.communicate() + outputFile.write(out) + outputFile.write(err) + SCRIPT_STATUS = p.wait() + except KeyboardInterrupt: + if p is not None: + os.kill(p.pid) + raise + outputFile.close() + + if xfailLines: + SCRIPT_STATUS = not SCRIPT_STATUS + + if useValgrind: + VG_STATUS = len(list(open(VG_OUTPUT))) + else: + VG_STATUS = 0 + + if SCRIPT_STATUS or VG_STATUS: + print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,) + print >>output, "Command: " + output.writelines(scriptLines) + if not SCRIPT_STATUS: + print >>output, "Output:" + else: + print >>output, "Incorrect Output:" + cat(OUTPUT, output) + if VG_STATUS: + print >>output, "Valgrind Output:" + cat(VG_OUTPUT, output) + print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,) + output.flush() + if xfailLines: + return TestStatus.XPass + else: + return TestStatus.Fail + + if xfailLines: + return TestStatus.XFail + else: + return TestStatus.Pass + +def main(): + _,path = sys.argv + command = path + # Use hand concatentation here because we want to override + # absolute paths. + output = 'Output/' + path + '.out' + testname = path + + # Determine which clang to use. + CLANG = os.getenv('CLANG') + if not CLANG: + CLANG = 'clang' + + res = runOneTest(path, command, output, testname, CLANG, + useValgrind=bool(os.getenv('VG')), + useDGCompat=bool(os.getenv('DG_COMPAT')), + useScript=os.getenv("TEST_SCRIPT")) + + sys.exit(res == TestStatus.Fail or res == TestStatus.XPass) + +if __name__=='__main__': + main() -- 2.40.0