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.

llvm-svn: 77784
This commit is contained in:
Daniel Dunbar 2009-08-01 10:18:01 +00:00
parent 82fa6f5e9d
commit 590c358681
3 changed files with 137 additions and 6 deletions

View File

@ -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))

View File

@ -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]

View File

@ -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