From 0dec838af02e2296e755c397363d81a921dd5073 Mon Sep 17 00:00:00 2001 From: Daniel Dunbar Date: Sat, 1 Aug 2009 10:18:01 +0000 Subject: [PATCH] lit: Add internal script execution. - Off by default, you can test it with the --no-sh argument. - For me it works for all but 3 tests, but there a number of FIXMEs and QOI issues: o Redirection isn't completely accurate -- in practice it can't portably be, but I would like to error out if someone writes something which isn't going to work. This is the source of the 3 test failures. o Some pipe configurations have the potential to deadlock. o It is significantly slower when multithreaded. I believe this is due to locking happening under the hood, there is probably some kind of solution but I haven't investigated yet. o Log output is ugly. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@77784 91177308-0d34-0410-b5e6-96231b3b80d8 --- utils/test/MultiTestRunner.py | 4 + utils/test/TestRunner.py | 138 ++++++++++++++++++++++++++++++++-- utils/test/TestingConfig.py | 1 + 3 files changed, 137 insertions(+), 6 deletions(-) diff --git a/utils/test/MultiTestRunner.py b/utils/test/MultiTestRunner.py index 00cdfd4936..df67b0ef17 100755 --- a/utils/test/MultiTestRunner.py +++ b/utils/test/MultiTestRunner.py @@ -249,6 +249,9 @@ def main(): group.add_option("", "--path", dest="path", help="Additional paths to add to testing environment", action="append", type=str, default=[]) + group.add_option("", "--no-sh", dest="useExternalShell", + help="Run tests using an external shell", + action="store_false", default=True) group.add_option("", "--vg", dest="useValgrind", help="Run tests under valgrind", action="store_true", default=False) @@ -314,6 +317,7 @@ def main(): cfg.clang = opts.clang cfg.clangcc = opts.clangcc cfg.useValgrind = opts.useValgrind + cfg.useExternalShell = opts.useExternalShell # FIXME: It could be worth loading these in parallel with testing. allTests = list(getTests(cfg, args)) diff --git a/utils/test/TestRunner.py b/utils/test/TestRunner.py index 23038b3e21..e605ef6013 100755 --- a/utils/test/TestRunner.py +++ b/utils/test/TestRunner.py @@ -5,6 +5,7 @@ import signal import subprocess import sys +import ShUtil import Util kSystemName = platform.system() @@ -21,6 +22,117 @@ class TestStatus: def getName(code): return TestStatus.kNames[code] +def executeShCmd(cmd, cfg, cwd, results): + if isinstance(cmd, ShUtil.Seq): + if cmd.op == ';': + res = executeShCmd(cmd.lhs, cfg, cwd, results) + if res is None: + return res + + return executeShCmd(cmd.rhs, cfg, cwd, results) + + if cmd.op == '&': + Util.warning("unsupported test command: '&'") + return None + + 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 + 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 + for j in cmd.commands: + # FIXME: This is broken, it doesn't account for the accumulative nature + # of redirects. + stdin = input + stdout = stderr = subprocess.PIPE + for r in j.redirects: + if r[0] == ('>',2): + stderr = open(r[1], 'w') + elif r[0] == ('>&',2) and r[1] == '1': + stderr = subprocess.STDOUT + elif r[0] == ('>',): + stdout = open(r[1], 'w') + elif r[0] == ('<',): + stdin = open(r[1], 'r') + else: + return None + + procs.append(subprocess.Popen(j.args, cwd=cwd, + stdin = stdin, + stdout = stdout, + stderr = stderr, + env = cfg.environment)) + + # Immediately close stdin for any process taking stdin from us. + if stdin == subprocess.PIPE: + procs[-1].stdin.close() + procs[-1].stdin = None + + if stdout == subprocess.PIPE: + input = procs[-1].stdout + else: + input = subprocess.PIPE + + # FIXME: There is a potential for deadlock here, when we have a pipe and + # some process other than the last one ends up blocked on stderr. + 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) + + # FIXME: Fix tests to work with pipefail, and make exitCode max across + # procs. + for i,(out,err) in enumerate(procData): + exitCode = res = procs[i].wait() + results.append((cmd.commands[i], out, err, res)) + + if cmd.negate: + exitCode = not exitCode + + return exitCode + +def executeScriptInternal(cfg, commands, cwd): + cmd = ShUtil.ShParser(' &&\n'.join(commands)).parse() + + results = [] + exitCode = executeShCmd(cmd, cfg, cwd, results) + if exitCode is None: + return None + + 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 executeScript(cfg, script, commands, cwd): # Write script file f = open(script,'w') @@ -50,10 +162,6 @@ def executeScript(cfg, script, commands, cwd): out,err = p.communicate() exitCode = p.wait() - # Detect Ctrl-C in subprocess. - if exitCode == -signal.SIGINT: - raise KeyboardInterrupt - return out, err, exitCode import StringIO @@ -118,8 +226,26 @@ def runOneTest(cfg, testPath, tmpBase): # Strip off '&&' scriptLines[i] = ln[:-2] - out, err, exitCode = executeScript(cfg, script, scriptLines, - os.path.dirname(testPath)) + if not cfg.useExternalShell: + res = executeScriptInternal(cfg, scriptLines, os.path.dirname(testPath)) + + if res is not None: + out, err, exitCode = res + elif True: + return (TestStatus.Fail, + "Unable to execute internally:\n%s\n" + % '\n'.join(scriptLines)) + else: + out, err, exitCode = executeScript(cfg, script, scriptLines, + os.path.dirname(testPath)) + else: + out, err, exitCode = executeScript(cfg, script, scriptLines, + os.path.dirname(testPath)) + + # Detect Ctrl-C in subprocess. + if exitCode == -signal.SIGINT: + raise KeyboardInterrupt + if xfailLines: ok = exitCode != 0 status = (TestStatus.XPass, TestStatus.XFail)[ok] diff --git a/utils/test/TestingConfig.py b/utils/test/TestingConfig.py index 998642b47b..a43bd76560 100644 --- a/utils/test/TestingConfig.py +++ b/utils/test/TestingConfig.py @@ -19,6 +19,7 @@ class TestingConfig: # Variables set internally. self.root = None self.useValgrind = None + self.useExternalShell = None # FIXME: These need to move into a substitutions mechanism. self.clang = None -- 2.40.0