diff options
author | rdivacky <rdivacky@FreeBSD.org> | 2009-10-14 17:57:32 +0000 |
---|---|---|
committer | rdivacky <rdivacky@FreeBSD.org> | 2009-10-14 17:57:32 +0000 |
commit | cd749a9c07f1de2fb8affde90537efa4bc3e7c54 (patch) | |
tree | b21f6de4e08b89bb7931806bab798fc2a5e3a686 /utils/lit/TestRunner.py | |
parent | 72621d11de5b873f1695f391eb95f0b336c3d2d4 (diff) | |
download | FreeBSD-src-cd749a9c07f1de2fb8affde90537efa4bc3e7c54.zip FreeBSD-src-cd749a9c07f1de2fb8affde90537efa4bc3e7c54.tar.gz |
Update llvm to r84119.
Diffstat (limited to 'utils/lit/TestRunner.py')
-rw-r--r-- | utils/lit/TestRunner.py | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/utils/lit/TestRunner.py b/utils/lit/TestRunner.py new file mode 100644 index 0000000..7b549ac --- /dev/null +++ b/utils/lit/TestRunner.py @@ -0,0 +1,505 @@ +import os, signal, subprocess, sys +import StringIO + +import ShUtil +import Test +import Util + +import platform +import tempfile + +class InternalShellError(Exception): + def __init__(self, command, message): + self.command = command + self.message = message + +# Don't use close_fds on Windows. +kUseCloseFDs = platform.system() != 'Windows' +def executeCommand(command, cwd=None, env=None): + p = subprocess.Popen(command, cwd=cwd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env) + out,err = p.communicate() + exitCode = p.wait() + + # Detect Ctrl-C in subprocess. + if exitCode == -signal.SIGINT: + raise KeyboardInterrupt + + return out, err, exitCode + +def executeShCmd(cmd, cfg, cwd, results): + if isinstance(cmd, ShUtil.Seq): + if cmd.op == ';': + res = executeShCmd(cmd.lhs, cfg, cwd, results) + return executeShCmd(cmd.rhs, cfg, cwd, results) + + if cmd.op == '&': + raise NotImplementedError,"unsupported test command: '&'" + + if cmd.op == '||': + res = executeShCmd(cmd.lhs, cfg, cwd, results) + if res != 0: + res = executeShCmd(cmd.rhs, cfg, cwd, results) + return res + if cmd.op == '&&': + res = executeShCmd(cmd.lhs, cfg, cwd, results) + if res is None: + return res + + if res == 0: + res = executeShCmd(cmd.rhs, cfg, cwd, results) + return res + + raise ValueError,'Unknown shell command: %r' % cmd.op + + assert isinstance(cmd, ShUtil.Pipeline) + procs = [] + input = subprocess.PIPE + stderrTempFiles = [] + # To avoid deadlock, we use a single stderr stream for piped + # output. This is null until we have seen some output using + # stderr. + for i,j in enumerate(cmd.commands): + redirects = [(0,), (1,), (2,)] + for r in j.redirects: + if r[0] == ('>',2): + redirects[2] = [r[1], 'w', None] + elif r[0] == ('>&',2) and r[1] in '012': + redirects[2] = redirects[int(r[1])] + elif r[0] == ('>&',) or r[0] == ('&>',): + redirects[1] = redirects[2] = [r[1], 'w', None] + elif r[0] == ('>',): + redirects[1] = [r[1], 'w', None] + elif r[0] == ('<',): + redirects[0] = [r[1], 'r', None] + else: + raise NotImplementedError,"Unsupported redirect: %r" % (r,) + + final_redirects = [] + for index,r in enumerate(redirects): + if r == (0,): + result = input + elif r == (1,): + if index == 0: + raise NotImplementedError,"Unsupported redirect for stdin" + elif index == 1: + result = subprocess.PIPE + else: + result = subprocess.STDOUT + elif r == (2,): + if index != 2: + raise NotImplementedError,"Unsupported redirect on stdout" + result = subprocess.PIPE + else: + if r[2] is None: + r[2] = open(r[0], r[1]) + result = r[2] + final_redirects.append(result) + + stdin, stdout, stderr = final_redirects + + # If stderr wants to come from stdout, but stdout isn't a pipe, then put + # stderr on a pipe and treat it as stdout. + if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE): + stderr = subprocess.PIPE + stderrIsStdout = True + else: + stderrIsStdout = False + + # Don't allow stderr on a PIPE except for the last + # process, this could deadlock. + # + # FIXME: This is slow, but so is deadlock. + if stderr == subprocess.PIPE and j != cmd.commands[-1]: + stderr = tempfile.TemporaryFile(mode='w+b') + stderrTempFiles.append((i, stderr)) + + # Resolve the executable path ourselves. + args = list(j.args) + args[0] = Util.which(args[0], cfg.environment['PATH']) + if not args[0]: + raise InternalShellError(j, '%r: command not found' % j.args[0]) + + procs.append(subprocess.Popen(args, cwd=cwd, + stdin = stdin, + stdout = stdout, + stderr = stderr, + env = cfg.environment, + close_fds = kUseCloseFDs)) + + # Immediately close stdin for any process taking stdin from us. + if stdin == subprocess.PIPE: + procs[-1].stdin.close() + procs[-1].stdin = None + + # Update the current stdin source. + if stdout == subprocess.PIPE: + input = procs[-1].stdout + elif stderrIsStdout: + input = procs[-1].stderr + else: + input = subprocess.PIPE + + # FIXME: There is probably still deadlock potential here. Yawn. + procData = [None] * len(procs) + procData[-1] = procs[-1].communicate() + + for i in range(len(procs) - 1): + if procs[i].stdout is not None: + out = procs[i].stdout.read() + else: + out = '' + if procs[i].stderr is not None: + err = procs[i].stderr.read() + else: + err = '' + procData[i] = (out,err) + + # Read stderr out of the temp files. + for i,f in stderrTempFiles: + f.seek(0, 0) + procData[i] = (procData[i][0], f.read()) + + exitCode = None + for i,(out,err) in enumerate(procData): + res = procs[i].wait() + # Detect Ctrl-C in subprocess. + if res == -signal.SIGINT: + raise KeyboardInterrupt + + results.append((cmd.commands[i], out, err, res)) + if cmd.pipe_err: + # Python treats the exit code as a signed char. + if res < 0: + exitCode = min(exitCode, res) + else: + exitCode = max(exitCode, res) + else: + exitCode = res + + if cmd.negate: + exitCode = not exitCode + + return exitCode + +def executeScriptInternal(test, litConfig, tmpBase, commands, cwd): + ln = ' &&\n'.join(commands) + try: + cmd = ShUtil.ShParser(ln, litConfig.isWindows).parse() + except: + return (Test.FAIL, "shell parser error on: %r" % ln) + + results = [] + try: + exitCode = executeShCmd(cmd, test.config, cwd, results) + except InternalShellError,e: + out = '' + err = e.message + exitCode = 255 + + out = err = '' + for i,(cmd, cmd_out,cmd_err,res) in enumerate(results): + out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args)) + out += 'Command %d Result: %r\n' % (i, res) + out += 'Command %d Output:\n%s\n\n' % (i, cmd_out) + out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err) + + return out, err, exitCode + +def executeTclScriptInternal(test, litConfig, tmpBase, commands, cwd): + import TclUtil + cmds = [] + for ln in commands: + # Given the unfortunate way LLVM's test are written, the line gets + # backslash substitution done twice. + ln = TclUtil.TclLexer(ln).lex_unquoted(process_all = True) + + try: + tokens = list(TclUtil.TclLexer(ln).lex()) + except: + return (Test.FAIL, "Tcl lexer error on: %r" % ln) + + # Validate there are no control tokens. + for t in tokens: + if not isinstance(t, str): + return (Test.FAIL, + "Invalid test line: %r containing %r" % (ln, t)) + + try: + cmds.append(TclUtil.TclExecCommand(tokens).parse_pipeline()) + except: + return (Test.FAIL, "Tcl 'exec' parse error on: %r" % ln) + + cmd = cmds[0] + for c in cmds[1:]: + cmd = ShUtil.Seq(cmd, '&&', c) + + if litConfig.useTclAsSh: + script = tmpBase + '.script' + + # Write script file + f = open(script,'w') + print >>f, 'set -o pipefail' + cmd.toShell(f, pipefail = True) + f.close() + + if 0: + print >>sys.stdout, cmd + print >>sys.stdout, open(script).read() + print >>sys.stdout + return '', '', 0 + + command = ['/bin/bash', script] + out,err,exitCode = executeCommand(command, cwd=cwd, + env=test.config.environment) + + # Tcl commands fail on standard error output. + if err: + exitCode = 1 + out = 'Command has output on stderr!\n\n' + out + + return out,err,exitCode + else: + results = [] + try: + exitCode = executeShCmd(cmd, test.config, cwd, results) + except InternalShellError,e: + results.append((e.command, '', e.message + '\n', 255)) + exitCode = 255 + + out = err = '' + + # Tcl commands fail on standard error output. + if [True for _,_,err,res in results if err]: + exitCode = 1 + out += 'Command has output on stderr!\n\n' + + for i,(cmd, cmd_out, cmd_err, res) in enumerate(results): + out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args)) + out += 'Command %d Result: %r\n' % (i, res) + out += 'Command %d Output:\n%s\n\n' % (i, cmd_out) + out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err) + + return out, err, exitCode + +def executeScript(test, litConfig, tmpBase, commands, cwd): + script = tmpBase + '.script' + if litConfig.isWindows: + script += '.bat' + + # Write script file + f = open(script,'w') + if litConfig.isWindows: + f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands)) + else: + f.write(' &&\n'.join(commands)) + f.write('\n') + f.close() + + if litConfig.isWindows: + command = ['cmd','/c', script] + else: + command = ['/bin/sh', script] + if litConfig.useValgrind: + # FIXME: Running valgrind on sh is overkill. We probably could just + # run on clang with no real loss. + valgrindArgs = ['valgrind', '-q', + '--tool=memcheck', '--trace-children=yes', + '--error-exitcode=123'] + valgrindArgs.extend(litConfig.valgrindArgs) + + command = valgrindArgs + command + + return executeCommand(command, cwd=cwd, env=test.config.environment) + +def parseIntegratedTestScript(test, xfailHasColon, requireAndAnd): + """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test + script and extract the lines to 'RUN' as well as 'XFAIL' and 'XTARGET' + information. The RUN lines also will have variable substitution performed. + """ + + # Get the temporary location, this is always relative to the test suite + # root, not test source root. + # + # FIXME: This should not be here? + sourcepath = test.getSourcePath() + execpath = test.getExecPath() + execdir,execbase = os.path.split(execpath) + tmpBase = os.path.join(execdir, 'Output', execbase) + + # We use #_MARKER_# to hide %% while we do the other substitutions. + substitutions = [('%%', '#_MARKER_#')] + substitutions.extend(test.config.substitutions) + substitutions.extend([('%s', sourcepath), + ('%S', os.path.dirname(sourcepath)), + ('%p', os.path.dirname(sourcepath)), + ('%t', tmpBase + '.tmp'), + # FIXME: Remove this once we kill DejaGNU. + ('%abs_tmp', tmpBase + '.tmp'), + ('#_MARKER_#', '%')]) + + # Collect the test lines from the script. + script = [] + xfails = [] + xtargets = [] + for ln in open(sourcepath): + if 'RUN:' in ln: + # Isolate the command to run. + index = ln.index('RUN:') + ln = ln[index+4:] + + # Trim trailing whitespace. + ln = ln.rstrip() + + # Collapse lines with trailing '\\'. + if script and script[-1][-1] == '\\': + script[-1] = script[-1][:-1] + ln + else: + script.append(ln) + elif xfailHasColon and 'XFAIL:' in ln: + items = ln[ln.index('XFAIL:') + 6:].split(',') + xfails.extend([s.strip() for s in items]) + elif not xfailHasColon and 'XFAIL' in ln: + items = ln[ln.index('XFAIL') + 5:].split(',') + xfails.extend([s.strip() for s in items]) + elif 'XTARGET:' in ln: + items = ln[ln.index('XTARGET:') + 8:].split(',') + xtargets.extend([s.strip() for s in items]) + elif 'END.' in ln: + # Check for END. lines. + if ln[ln.index('END.'):].strip() == 'END.': + break + + # Apply substitutions to the script. + def processLine(ln): + # Apply substitutions + for a,b in substitutions: + ln = ln.replace(a,b) + + # Strip the trailing newline and any extra whitespace. + return ln.strip() + script = map(processLine, script) + + # Verify the script contains a run line. + if not script: + return (Test.UNRESOLVED, "Test has no run line!") + + if script[-1][-1] == '\\': + return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')") + + # Validate interior lines for '&&', a lovely historical artifact. + if requireAndAnd: + for i in range(len(script) - 1): + ln = script[i] + + if not ln.endswith('&&'): + return (Test.FAIL, + ("MISSING \'&&\': %s\n" + + "FOLLOWED BY : %s\n") % (ln, script[i + 1])) + + # Strip off '&&' + script[i] = ln[:-2] + + return script,xfails,xtargets,tmpBase,execdir + +def formatTestOutput(status, out, err, exitCode, script): + output = StringIO.StringIO() + print >>output, "Script:" + print >>output, "--" + print >>output, '\n'.join(script) + print >>output, "--" + print >>output, "Exit Code: %r" % exitCode + print >>output, "Command Output (stdout):" + print >>output, "--" + output.write(out) + print >>output, "--" + print >>output, "Command Output (stderr):" + print >>output, "--" + output.write(err) + print >>output, "--" + return (status, output.getvalue()) + +def executeTclTest(test, litConfig): + if test.config.unsupported: + return (Test.UNSUPPORTED, 'Test is unsupported') + + res = parseIntegratedTestScript(test, True, False) + if len(res) == 2: + return res + + script, xfails, xtargets, tmpBase, execdir = res + + if litConfig.noExecute: + return (Test.PASS, '') + + # Create the output directory if it does not already exist. + Util.mkdir_p(os.path.dirname(tmpBase)) + + res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir) + if len(res) == 2: + return res + + isXFail = False + for item in xfails: + if item == '*' or item in test.suite.config.target_triple: + isXFail = True + break + + # If this is XFAIL, see if it is expected to pass on this target. + if isXFail: + for item in xtargets: + if item == '*' or item in test.suite.config.target_triple: + isXFail = False + break + + out,err,exitCode = res + if isXFail: + ok = exitCode != 0 + status = (Test.XPASS, Test.XFAIL)[ok] + else: + ok = exitCode == 0 + status = (Test.FAIL, Test.PASS)[ok] + + if ok: + return (status,'') + + return formatTestOutput(status, out, err, exitCode, script) + +def executeShTest(test, litConfig, useExternalSh, requireAndAnd): + if test.config.unsupported: + return (Test.UNSUPPORTED, 'Test is unsupported') + + res = parseIntegratedTestScript(test, False, requireAndAnd) + if len(res) == 2: + return res + + script, xfails, xtargets, tmpBase, execdir = res + + if litConfig.noExecute: + return (Test.PASS, '') + + # Create the output directory if it does not already exist. + Util.mkdir_p(os.path.dirname(tmpBase)) + + if useExternalSh: + res = executeScript(test, litConfig, tmpBase, script, execdir) + else: + res = executeScriptInternal(test, litConfig, tmpBase, script, execdir) + if len(res) == 2: + return res + + out,err,exitCode = res + if xfails: + ok = exitCode != 0 + status = (Test.XPASS, Test.XFAIL)[ok] + else: + ok = exitCode == 0 + status = (Test.FAIL, Test.PASS)[ok] + + if ok: + return (status,'') + + return formatTestOutput(status, out, err, exitCode, script) |