[cfe-commits] r66293 - in /cfe/trunk/utils/test: ./ Makefile.multi MultiTestRunner.py ProgressBar.py TestRunner.py

Daniel Dunbar daniel at zuster.org
Fri Mar 6 14:20:40 PST 2009


Author: ddunbar
Date: Fri Mar  6 16:20:40 2009
New Revision: 66293

URL: http://llvm.org/viewvc/llvm-project?rev=66293&view=rev
Log:
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.

Added:
    cfe/trunk/utils/test/
    cfe/trunk/utils/test/Makefile.multi
    cfe/trunk/utils/test/MultiTestRunner.py   (with props)
    cfe/trunk/utils/test/ProgressBar.py
    cfe/trunk/utils/test/TestRunner.py   (with props)

Added: cfe/trunk/utils/test/Makefile.multi
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/utils/test/Makefile.multi?rev=66293&view=auto

==============================================================================
--- cfe/trunk/utils/test/Makefile.multi (added)
+++ cfe/trunk/utils/test/Makefile.multi Fri Mar  6 16:20:40 2009
@@ -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

Added: cfe/trunk/utils/test/MultiTestRunner.py
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/utils/test/MultiTestRunner.py?rev=66293&view=auto

==============================================================================
--- cfe/trunk/utils/test/MultiTestRunner.py (added)
+++ cfe/trunk/utils/test/MultiTestRunner.py Fri Mar  6 16:20:40 2009
@@ -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()

Propchange: cfe/trunk/utils/test/MultiTestRunner.py

------------------------------------------------------------------------------
    svn:executable = *

Added: cfe/trunk/utils/test/ProgressBar.py
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/utils/test/ProgressBar.py?rev=66293&view=auto

==============================================================================
--- cfe/trunk/utils/test/ProgressBar.py (added)
+++ cfe/trunk/utils/test/ProgressBar.py Fri Mar  6 16:20:40 2009
@@ -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()

Added: cfe/trunk/utils/test/TestRunner.py
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/utils/test/TestRunner.py?rev=66293&view=auto

==============================================================================
--- cfe/trunk/utils/test/TestRunner.py (added)
+++ cfe/trunk/utils/test/TestRunner.py Fri Mar  6 16:20:40 2009
@@ -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()

Propchange: cfe/trunk/utils/test/TestRunner.py

------------------------------------------------------------------------------
    svn:executable = *





More information about the cfe-commits mailing list