1
import os, signal, subprocess, sys
11
class InternalShellError(Exception):
12
def __init__(self, command, message):
13
self.command = command
14
self.message = message
16
# Don't use close_fds on Windows.
17
kUseCloseFDs = platform.system() != 'Windows'
19
# Use temporary files to replace /dev/null on Windows.
20
kAvoidDevNull = platform.system() == 'Windows'
22
def executeCommand(command, cwd=None, env=None):
23
p = subprocess.Popen(command, cwd=cwd,
24
stdin=subprocess.PIPE,
25
stdout=subprocess.PIPE,
26
stderr=subprocess.PIPE,
28
out,err = p.communicate()
31
# Detect Ctrl-C in subprocess.
32
if exitCode == -signal.SIGINT:
33
raise KeyboardInterrupt
35
return out, err, exitCode
37
def executeShCmd(cmd, cfg, cwd, results):
38
if isinstance(cmd, ShUtil.Seq):
40
res = executeShCmd(cmd.lhs, cfg, cwd, results)
41
return executeShCmd(cmd.rhs, cfg, cwd, results)
44
raise NotImplementedError,"unsupported test command: '&'"
47
res = executeShCmd(cmd.lhs, cfg, cwd, results)
49
res = executeShCmd(cmd.rhs, cfg, cwd, results)
52
res = executeShCmd(cmd.lhs, cfg, cwd, results)
57
res = executeShCmd(cmd.rhs, cfg, cwd, results)
60
raise ValueError,'Unknown shell command: %r' % cmd.op
62
assert isinstance(cmd, ShUtil.Pipeline)
64
input = subprocess.PIPE
66
# To avoid deadlock, we use a single stderr stream for piped
67
# output. This is null until we have seen some output using
69
for i,j in enumerate(cmd.commands):
70
# Apply the redirections, we use (N,) as a sentinal to indicate stdin,
71
# stdout, stderr for N equal to 0, 1, or 2 respectively. Redirects to or
72
# from a file are represented with a list [file, mode, file-object]
73
# where file-object is initially None.
74
redirects = [(0,), (1,), (2,)]
77
redirects[2] = [r[1], 'w', None]
78
elif r[0] == ('>>',2):
79
redirects[2] = [r[1], 'a', None]
80
elif r[0] == ('>&',2) and r[1] in '012':
81
redirects[2] = redirects[int(r[1])]
82
elif r[0] == ('>&',) or r[0] == ('&>',):
83
redirects[1] = redirects[2] = [r[1], 'w', None]
85
redirects[1] = [r[1], 'w', None]
87
redirects[1] = [r[1], 'a', None]
89
redirects[0] = [r[1], 'r', None]
91
raise NotImplementedError,"Unsupported redirect: %r" % (r,)
93
# Map from the final redirections to something subprocess can handle.
95
for index,r in enumerate(redirects):
100
raise NotImplementedError,"Unsupported redirect for stdin"
102
result = subprocess.PIPE
104
result = subprocess.STDOUT
107
raise NotImplementedError,"Unsupported redirect on stdout"
108
result = subprocess.PIPE
111
if kAvoidDevNull and r[0] == '/dev/null':
112
r[2] = tempfile.TemporaryFile(mode=r[1])
114
r[2] = open(r[0], r[1])
115
# Workaround a Win32 and/or subprocess bug when appending.
119
final_redirects.append(result)
121
stdin, stdout, stderr = final_redirects
123
# If stderr wants to come from stdout, but stdout isn't a pipe, then put
124
# stderr on a pipe and treat it as stdout.
125
if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE):
126
stderr = subprocess.PIPE
127
stderrIsStdout = True
129
stderrIsStdout = False
131
# Don't allow stderr on a PIPE except for the last
132
# process, this could deadlock.
134
# FIXME: This is slow, but so is deadlock.
135
if stderr == subprocess.PIPE and j != cmd.commands[-1]:
136
stderr = tempfile.TemporaryFile(mode='w+b')
137
stderrTempFiles.append((i, stderr))
139
# Resolve the executable path ourselves.
141
args[0] = Util.which(args[0], cfg.environment['PATH'])
143
raise InternalShellError(j, '%r: command not found' % j.args[0])
145
procs.append(subprocess.Popen(args, cwd=cwd,
149
env = cfg.environment,
150
close_fds = kUseCloseFDs))
152
# Immediately close stdin for any process taking stdin from us.
153
if stdin == subprocess.PIPE:
154
procs[-1].stdin.close()
155
procs[-1].stdin = None
157
# Update the current stdin source.
158
if stdout == subprocess.PIPE:
159
input = procs[-1].stdout
161
input = procs[-1].stderr
163
input = subprocess.PIPE
165
# FIXME: There is probably still deadlock potential here. Yawn.
166
procData = [None] * len(procs)
167
procData[-1] = procs[-1].communicate()
169
for i in range(len(procs) - 1):
170
if procs[i].stdout is not None:
171
out = procs[i].stdout.read()
174
if procs[i].stderr is not None:
175
err = procs[i].stderr.read()
178
procData[i] = (out,err)
180
# Read stderr out of the temp files.
181
for i,f in stderrTempFiles:
183
procData[i] = (procData[i][0], f.read())
186
for i,(out,err) in enumerate(procData):
187
res = procs[i].wait()
188
# Detect Ctrl-C in subprocess.
189
if res == -signal.SIGINT:
190
raise KeyboardInterrupt
192
results.append((cmd.commands[i], out, err, res))
194
# Python treats the exit code as a signed char.
196
exitCode = min(exitCode, res)
198
exitCode = max(exitCode, res)
203
exitCode = not exitCode
207
def executeScriptInternal(test, litConfig, tmpBase, commands, cwd):
208
ln = ' &&\n'.join(commands)
210
cmd = ShUtil.ShParser(ln, litConfig.isWindows).parse()
212
return (Test.FAIL, "shell parser error on: %r" % ln)
216
exitCode = executeShCmd(cmd, test.config, cwd, results)
217
except InternalShellError,e:
223
for i,(cmd, cmd_out,cmd_err,res) in enumerate(results):
224
out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
225
out += 'Command %d Result: %r\n' % (i, res)
226
out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
227
out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
229
return out, err, exitCode
231
def executeTclScriptInternal(test, litConfig, tmpBase, commands, cwd):
235
# Given the unfortunate way LLVM's test are written, the line gets
236
# backslash substitution done twice.
237
ln = TclUtil.TclLexer(ln).lex_unquoted(process_all = True)
240
tokens = list(TclUtil.TclLexer(ln).lex())
242
return (Test.FAIL, "Tcl lexer error on: %r" % ln)
244
# Validate there are no control tokens.
246
if not isinstance(t, str):
248
"Invalid test line: %r containing %r" % (ln, t))
251
cmds.append(TclUtil.TclExecCommand(tokens).parse_pipeline())
253
return (Test.FAIL, "Tcl 'exec' parse error on: %r" % ln)
257
cmd = ShUtil.Seq(cmd, '&&', c)
259
# FIXME: This is lame, we shouldn't need bash. See PR5240.
260
bashPath = litConfig.getBashPath()
261
if litConfig.useTclAsSh and bashPath:
262
script = tmpBase + '.script'
266
print >>f, 'set -o pipefail'
267
cmd.toShell(f, pipefail = True)
271
print >>sys.stdout, cmd
272
print >>sys.stdout, open(script).read()
276
command = [litConfig.getBashPath(), script]
277
out,err,exitCode = executeCommand(command, cwd=cwd,
278
env=test.config.environment)
280
# Tcl commands fail on standard error output.
283
out = 'Command has output on stderr!\n\n' + out
285
return out,err,exitCode
289
exitCode = executeShCmd(cmd, test.config, cwd, results)
290
except InternalShellError,e:
291
results.append((e.command, '', e.message + '\n', 255))
296
# Tcl commands fail on standard error output.
297
if [True for _,_,err,res in results if err]:
299
out += 'Command has output on stderr!\n\n'
301
for i,(cmd, cmd_out, cmd_err, res) in enumerate(results):
302
out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
303
out += 'Command %d Result: %r\n' % (i, res)
304
out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
305
out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
307
return out, err, exitCode
309
def executeScript(test, litConfig, tmpBase, commands, cwd):
310
script = tmpBase + '.script'
311
if litConfig.isWindows:
316
if litConfig.isWindows:
317
f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
319
f.write(' &&\n'.join(commands))
323
if litConfig.isWindows:
324
command = ['cmd','/c', script]
326
command = ['/bin/sh', script]
327
if litConfig.useValgrind:
328
# FIXME: Running valgrind on sh is overkill. We probably could just
329
# run on clang with no real loss.
330
valgrindArgs = ['valgrind', '-q',
331
'--tool=memcheck', '--trace-children=yes',
332
'--error-exitcode=123']
333
valgrindArgs.extend(litConfig.valgrindArgs)
335
command = valgrindArgs + command
337
return executeCommand(command, cwd=cwd, env=test.config.environment)
339
def isExpectedFail(xfails, xtargets, target_triple):
340
# Check if any xfail matches this target.
342
if item == '*' or item in target_triple:
347
# If so, see if it is expected to pass on this target.
349
# FIXME: Rename XTARGET to something that makes sense, like XPASS.
350
for item in xtargets:
351
if item == '*' or item in target_triple:
358
def parseIntegratedTestScript(test):
359
"""parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
360
script and extract the lines to 'RUN' as well as 'XFAIL' and 'XTARGET'
361
information. The RUN lines also will have variable substitution performed.
364
# Get the temporary location, this is always relative to the test suite
365
# root, not test source root.
367
# FIXME: This should not be here?
368
sourcepath = test.getSourcePath()
369
execpath = test.getExecPath()
370
execdir,execbase = os.path.split(execpath)
371
tmpBase = os.path.join(execdir, 'Output', execbase)
372
if test.index is not None:
373
tmpBase += '_%d' % test.index
375
# We use #_MARKER_# to hide %% while we do the other substitutions.
376
substitutions = [('%%', '#_MARKER_#')]
377
substitutions.extend(test.config.substitutions)
378
substitutions.extend([('%s', sourcepath),
379
('%S', os.path.dirname(sourcepath)),
380
('%p', os.path.dirname(sourcepath)),
381
('%t', tmpBase + '.tmp'),
382
# FIXME: Remove this once we kill DejaGNU.
383
('%abs_tmp', tmpBase + '.tmp'),
384
('#_MARKER_#', '%')])
386
# Collect the test lines from the script.
391
for ln in open(sourcepath):
392
conditional = re.search('IF\((.+?)\((.+?)\)\):', ln)
394
ln = ln[conditional.end():]
395
condition = conditional.group(1)
396
value = conditional.group(2)
398
# Actually test the condition.
399
if condition not in test.config.conditions:
400
return (Test.UNRESOLVED, "unknown condition '"+condition+"'")
401
if not test.config.conditions[condition](value):
406
# Isolate the command to run.
407
index = ln.index('RUN:')
410
# Trim trailing whitespace.
413
# Collapse lines with trailing '\\'.
414
if script and script[-1][-1] == '\\':
415
script[-1] = script[-1][:-1] + ln
419
items = ln[ln.index('XFAIL:') + 6:].split(',')
420
xfails.extend([s.strip() for s in items])
421
elif 'XTARGET:' in ln:
422
items = ln[ln.index('XTARGET:') + 8:].split(',')
423
xtargets.extend([s.strip() for s in items])
425
# Check for END. lines.
426
if ln[ln.index('END.'):].strip() == 'END.':
429
# Apply substitutions to the script.
431
# Apply substitutions
432
for a,b in substitutions:
435
# Strip the trailing newline and any extra whitespace.
437
script = map(processLine, script)
439
# Verify the script contains a run line.
442
return (Test.UNSUPPORTED, "Test has only ignored run lines")
443
return (Test.UNRESOLVED, "Test has no run line!")
445
if script[-1][-1] == '\\':
446
return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
448
isXFail = isExpectedFail(xfails, xtargets, test.suite.config.target_triple)
449
return script,isXFail,tmpBase,execdir
451
def formatTestOutput(status, out, err, exitCode, script):
452
output = StringIO.StringIO()
453
print >>output, "Script:"
455
print >>output, '\n'.join(script)
457
print >>output, "Exit Code: %r" % exitCode
458
print >>output, "Command Output (stdout):"
462
print >>output, "Command Output (stderr):"
466
return (status, output.getvalue())
468
def executeTclTest(test, litConfig):
469
if test.config.unsupported:
470
return (Test.UNSUPPORTED, 'Test is unsupported')
472
res = parseIntegratedTestScript(test)
476
script, isXFail, tmpBase, execdir = res
478
if litConfig.noExecute:
479
return (Test.PASS, '')
481
# Create the output directory if it does not already exist.
482
Util.mkdir_p(os.path.dirname(tmpBase))
484
res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
488
out,err,exitCode = res
491
status = (Test.XPASS, Test.XFAIL)[ok]
494
status = (Test.FAIL, Test.PASS)[ok]
499
return formatTestOutput(status, out, err, exitCode, script)
501
def executeShTest(test, litConfig, useExternalSh):
502
if test.config.unsupported:
503
return (Test.UNSUPPORTED, 'Test is unsupported')
505
res = parseIntegratedTestScript(test)
509
script, isXFail, tmpBase, execdir = res
511
if litConfig.noExecute:
512
return (Test.PASS, '')
514
# Create the output directory if it does not already exist.
515
Util.mkdir_p(os.path.dirname(tmpBase))
518
res = executeScript(test, litConfig, tmpBase, script, execdir)
520
res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
524
out,err,exitCode = res
527
status = (Test.XPASS, Test.XFAIL)[ok]
530
status = (Test.FAIL, Test.PASS)[ok]
535
return formatTestOutput(status, out, err, exitCode, script)