1 import os, signal, subprocess, sys
13 class InternalShellError(Exception):
14 def __init__(self, command, message):
15 self.command = command
16 self.message = message
18 kIsWindows = platform.system() == 'Windows'
20 # Don't use close_fds on Windows.
21 kUseCloseFDs = not kIsWindows
23 # Use temporary files to replace /dev/null on Windows.
24 kAvoidDevNull = kIsWindows
26 def executeCommand(command, cwd=None, env=None):
27 p = subprocess.Popen(command, cwd=cwd,
28 stdin=subprocess.PIPE,
29 stdout=subprocess.PIPE,
30 stderr=subprocess.PIPE,
32 out,err = p.communicate()
35 # Detect Ctrl-C in subprocess.
36 if exitCode == -signal.SIGINT:
37 raise KeyboardInterrupt
39 return out, err, exitCode
41 def executeShCmd(cmd, cfg, cwd, results):
42 if isinstance(cmd, ShUtil.Seq):
44 res = executeShCmd(cmd.lhs, cfg, cwd, results)
45 return executeShCmd(cmd.rhs, cfg, cwd, results)
48 raise NotImplementedError,"unsupported test command: '&'"
51 res = executeShCmd(cmd.lhs, cfg, cwd, results)
53 res = executeShCmd(cmd.rhs, cfg, cwd, results)
56 res = executeShCmd(cmd.lhs, cfg, cwd, results)
61 res = executeShCmd(cmd.rhs, cfg, cwd, results)
64 raise ValueError,'Unknown shell command: %r' % cmd.op
66 assert isinstance(cmd, ShUtil.Pipeline)
68 input = subprocess.PIPE
72 # To avoid deadlock, we use a single stderr stream for piped
73 # output. This is null until we have seen some output using
75 for i,j in enumerate(cmd.commands):
76 # Apply the redirections, we use (N,) as a sentinal to indicate stdin,
77 # stdout, stderr for N equal to 0, 1, or 2 respectively. Redirects to or
78 # from a file are represented with a list [file, mode, file-object]
79 # where file-object is initially None.
80 redirects = [(0,), (1,), (2,)]
83 redirects[2] = [r[1], 'w', None]
84 elif r[0] == ('>>',2):
85 redirects[2] = [r[1], 'a', None]
86 elif r[0] == ('>&',2) and r[1] in '012':
87 redirects[2] = redirects[int(r[1])]
88 elif r[0] == ('>&',) or r[0] == ('&>',):
89 redirects[1] = redirects[2] = [r[1], 'w', None]
91 redirects[1] = [r[1], 'w', None]
93 redirects[1] = [r[1], 'a', None]
95 redirects[0] = [r[1], 'r', None]
97 raise NotImplementedError,"Unsupported redirect: %r" % (r,)
99 # Map from the final redirections to something subprocess can handle.
101 for index,r in enumerate(redirects):
106 raise NotImplementedError,"Unsupported redirect for stdin"
108 result = subprocess.PIPE
110 result = subprocess.STDOUT
113 raise NotImplementedError,"Unsupported redirect on stdout"
114 result = subprocess.PIPE
117 if kAvoidDevNull and r[0] == '/dev/null':
118 r[2] = tempfile.TemporaryFile(mode=r[1])
120 r[2] = open(r[0], r[1])
121 # Workaround a Win32 and/or subprocess bug when appending.
123 # FIXME: Actually, this is probably an instance of PR6753.
126 opened_files.append(r[2])
128 final_redirects.append(result)
130 stdin, stdout, stderr = final_redirects
132 # If stderr wants to come from stdout, but stdout isn't a pipe, then put
133 # stderr on a pipe and treat it as stdout.
134 if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE):
135 stderr = subprocess.PIPE
136 stderrIsStdout = True
138 stderrIsStdout = False
140 # Don't allow stderr on a PIPE except for the last
141 # process, this could deadlock.
143 # FIXME: This is slow, but so is deadlock.
144 if stderr == subprocess.PIPE and j != cmd.commands[-1]:
145 stderr = tempfile.TemporaryFile(mode='w+b')
146 stderrTempFiles.append((i, stderr))
148 # Resolve the executable path ourselves.
150 args[0] = Util.which(args[0], cfg.environment['PATH'])
152 raise InternalShellError(j, '%r: command not found' % j.args[0])
154 # Replace uses of /dev/null with temporary files.
156 for i,arg in enumerate(args):
157 if arg == "/dev/null":
158 f = tempfile.NamedTemporaryFile(delete=False)
160 named_temp_files.append(f.name)
163 procs.append(subprocess.Popen(args, cwd=cwd,
167 env = cfg.environment,
168 close_fds = kUseCloseFDs))
170 # Immediately close stdin for any process taking stdin from us.
171 if stdin == subprocess.PIPE:
172 procs[-1].stdin.close()
173 procs[-1].stdin = None
175 # Update the current stdin source.
176 if stdout == subprocess.PIPE:
177 input = procs[-1].stdout
179 input = procs[-1].stderr
181 input = subprocess.PIPE
183 # Explicitly close any redirected files. We need to do this now because we
184 # need to release any handles we may have on the temporary files (important
185 # on Win32, for example). Since we have already spawned the subprocess, our
186 # handles have already been transferred so we do not need them anymore.
187 for f in opened_files:
190 # FIXME: There is probably still deadlock potential here. Yawn.
191 procData = [None] * len(procs)
192 procData[-1] = procs[-1].communicate()
194 for i in range(len(procs) - 1):
195 if procs[i].stdout is not None:
196 out = procs[i].stdout.read()
199 if procs[i].stderr is not None:
200 err = procs[i].stderr.read()
203 procData[i] = (out,err)
205 # Read stderr out of the temp files.
206 for i,f in stderrTempFiles:
208 procData[i] = (procData[i][0], f.read())
211 for i,(out,err) in enumerate(procData):
212 res = procs[i].wait()
213 # Detect Ctrl-C in subprocess.
214 if res == -signal.SIGINT:
215 raise KeyboardInterrupt
217 results.append((cmd.commands[i], out, err, res))
219 # Python treats the exit code as a signed char.
221 exitCode = min(exitCode, res)
223 exitCode = max(exitCode, res)
227 # Remove any named temporary files we created.
228 for f in named_temp_files:
235 exitCode = not exitCode
239 def executeScriptInternal(test, litConfig, tmpBase, commands, cwd):
240 ln = ' &&\n'.join(commands)
242 cmd = ShUtil.ShParser(ln, litConfig.isWindows).parse()
244 return (Test.FAIL, "shell parser error on: %r" % ln)
248 exitCode = executeShCmd(cmd, test.config, cwd, results)
249 except InternalShellError,e:
255 for i,(cmd, cmd_out,cmd_err,res) in enumerate(results):
256 out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
257 out += 'Command %d Result: %r\n' % (i, res)
258 out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
259 out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
261 return out, err, exitCode
263 def executeTclScriptInternal(test, litConfig, tmpBase, commands, cwd):
267 # Given the unfortunate way LLVM's test are written, the line gets
268 # backslash substitution done twice.
269 ln = TclUtil.TclLexer(ln).lex_unquoted(process_all = True)
272 tokens = list(TclUtil.TclLexer(ln).lex())
274 return (Test.FAIL, "Tcl lexer error on: %r" % ln)
276 # Validate there are no control tokens.
278 if not isinstance(t, str):
280 "Invalid test line: %r containing %r" % (ln, t))
283 cmds.append(TclUtil.TclExecCommand(tokens).parse_pipeline())
285 return (Test.FAIL, "Tcl 'exec' parse error on: %r" % ln)
287 if litConfig.useValgrind:
288 for pipeline in cmds:
289 if pipeline.commands:
290 # Only valgrind the first command in each pipeline, to avoid
291 # valgrinding things like grep, not, and FileCheck.
292 cmd = pipeline.commands[0]
293 cmd.args = litConfig.valgrindArgs + cmd.args
297 cmd = ShUtil.Seq(cmd, '&&', c)
299 # FIXME: This is lame, we shouldn't need bash. See PR5240.
300 bashPath = litConfig.getBashPath()
301 if litConfig.useTclAsSh and bashPath:
302 script = tmpBase + '.script'
306 print >>f, 'set -o pipefail'
307 cmd.toShell(f, pipefail = True)
311 print >>sys.stdout, cmd
312 print >>sys.stdout, open(script).read()
316 command = [litConfig.getBashPath(), script]
317 out,err,exitCode = executeCommand(command, cwd=cwd,
318 env=test.config.environment)
320 return out,err,exitCode
324 exitCode = executeShCmd(cmd, test.config, cwd, results)
325 except InternalShellError,e:
326 results.append((e.command, '', e.message + '\n', 255))
331 for i,(cmd, cmd_out, cmd_err, res) in enumerate(results):
332 out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
333 out += 'Command %d Result: %r\n' % (i, res)
334 out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
335 out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
337 return out, err, exitCode
339 def executeScript(test, litConfig, tmpBase, commands, cwd):
340 script = tmpBase + '.script'
341 if litConfig.isWindows:
346 if litConfig.isWindows:
347 f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
349 f.write(' &&\n'.join(commands))
353 if litConfig.isWindows:
354 command = ['cmd','/c', script]
356 command = ['/bin/sh', script]
357 if litConfig.useValgrind:
358 # FIXME: Running valgrind on sh is overkill. We probably could just
359 # run on clang with no real loss.
360 command = litConfig.valgrindArgs + command
362 return executeCommand(command, cwd=cwd, env=test.config.environment)
364 def isExpectedFail(xfails, xtargets, target_triple):
365 # Check if any xfail matches this target.
367 if item == '*' or item in target_triple:
372 # If so, see if it is expected to pass on this target.
374 # FIXME: Rename XTARGET to something that makes sense, like XPASS.
375 for item in xtargets:
376 if item == '*' or item in target_triple:
381 def parseIntegratedTestScript(test, normalize_slashes=False):
382 """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
383 script and extract the lines to 'RUN' as well as 'XFAIL' and 'XTARGET'
384 information. The RUN lines also will have variable substitution performed.
387 # Get the temporary location, this is always relative to the test suite
388 # root, not test source root.
390 # FIXME: This should not be here?
391 sourcepath = test.getSourcePath()
392 sourcedir = os.path.dirname(sourcepath)
393 execpath = test.getExecPath()
394 execdir,execbase = os.path.split(execpath)
395 tmpBase = os.path.join(execdir, 'Output', execbase)
396 if test.index is not None:
397 tmpBase += '_%d' % test.index
399 # Normalize slashes, if requested.
400 if normalize_slashes:
401 sourcepath = sourcepath.replace('\\', '/')
402 sourcedir = sourcedir.replace('\\', '/')
403 tmpBase = tmpBase.replace('\\', '/')
405 # We use #_MARKER_# to hide %% while we do the other substitutions.
406 substitutions = [('%%', '#_MARKER_#')]
407 substitutions.extend(test.config.substitutions)
408 substitutions.extend([('%s', sourcepath),
411 ('%t', tmpBase + '.tmp'),
412 # FIXME: Remove this once we kill DejaGNU.
413 ('%abs_tmp', tmpBase + '.tmp'),
414 ('#_MARKER_#', '%')])
416 # Collect the test lines from the script.
421 for ln in open(sourcepath):
423 # Isolate the command to run.
424 index = ln.index('RUN:')
427 # Trim trailing whitespace.
430 # Collapse lines with trailing '\\'.
431 if script and script[-1][-1] == '\\':
432 script[-1] = script[-1][:-1] + ln
436 items = ln[ln.index('XFAIL:') + 6:].split(',')
437 xfails.extend([s.strip() for s in items])
438 elif 'XTARGET:' in ln:
439 items = ln[ln.index('XTARGET:') + 8:].split(',')
440 xtargets.extend([s.strip() for s in items])
441 elif 'REQUIRES:' in ln:
442 items = ln[ln.index('REQUIRES:') + 9:].split(',')
443 requires.extend([s.strip() for s in items])
445 # Check for END. lines.
446 if ln[ln.index('END.'):].strip() == 'END.':
449 # Apply substitutions to the script. Allow full regular
450 # expression syntax. Replace each matching occurrence of regular
451 # expression pattern a with substitution b in line ln.
453 # Apply substitutions
454 # FIXME: Investigate why re.sub doesn't work on Windows
455 for a,b in substitutions:
459 ln = re.sub(a, b, ln)
461 # Strip the trailing newline and any extra whitespace.
463 script = map(processLine, script)
465 # Verify the script contains a run line.
467 return (Test.UNRESOLVED, "Test has no run line!")
469 # Check for unterminated run lines.
470 if script[-1][-1] == '\\':
471 return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
473 # Check that we have the required features:
474 missing_required_features = [f for f in requires
475 if f not in test.config.available_features]
476 if missing_required_features:
477 msg = ', '.join(missing_required_features)
478 return (Test.UNSUPPORTED,
479 "Test requires the following features: %s" % msg)
481 isXFail = isExpectedFail(xfails, xtargets, test.suite.config.target_triple)
482 return script,isXFail,tmpBase,execdir
484 def formatTestOutput(status, out, err, exitCode, failDueToStderr, script):
485 output = StringIO.StringIO()
486 print >>output, "Script:"
488 print >>output, '\n'.join(script)
490 print >>output, "Exit Code: %r" % exitCode,
492 print >>output, "(but there was output on stderr)"
496 print >>output, "Command Output (stdout):"
501 print >>output, "Command Output (stderr):"
505 return (status, output.getvalue())
507 def executeTclTest(test, litConfig):
508 if test.config.unsupported:
509 return (Test.UNSUPPORTED, 'Test is unsupported')
511 # Parse the test script, normalizing slashes in substitutions on Windows
512 # (since otherwise Tcl style lexing will treat them as escapes).
513 res = parseIntegratedTestScript(test, normalize_slashes=kIsWindows)
517 script, isXFail, tmpBase, execdir = res
519 if litConfig.noExecute:
520 return (Test.PASS, '')
522 # Create the output directory if it does not already exist.
523 Util.mkdir_p(os.path.dirname(tmpBase))
525 res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
529 # Test for failure. In addition to the exit code, Tcl commands are
530 # considered to fail if there is any standard error output.
531 out,err,exitCode = res
533 ok = exitCode != 0 or err
539 ok = exitCode == 0 and not err
548 # Set a flag for formatTestOutput so it can explain why the test was
549 # considered to have failed, despite having an exit code of 0.
550 failDueToStderr = exitCode == 0 and err
552 return formatTestOutput(status, out, err, exitCode, failDueToStderr, script)
554 def executeShTest(test, litConfig, useExternalSh):
555 if test.config.unsupported:
556 return (Test.UNSUPPORTED, 'Test is unsupported')
558 res = parseIntegratedTestScript(test)
562 script, isXFail, tmpBase, execdir = res
564 if litConfig.noExecute:
565 return (Test.PASS, '')
567 # Create the output directory if it does not already exist.
568 Util.mkdir_p(os.path.dirname(tmpBase))
571 res = executeScript(test, litConfig, tmpBase, script, execdir)
573 res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
577 out,err,exitCode = res
594 # Sh tests are not considered to fail just from stderr output.
595 failDueToStderr = False
597 return formatTestOutput(status, out, err, exitCode, failDueToStderr, script)