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
32 def WinWaitReleased(f):
33 import time, win32file
37 h = win32file.CreateFile(
39 0, # Querying, neither GENERIC_READ nor GENERIC_WRITE
42 win32file.OPEN_EXISTING,
43 win32file.FILE_ATTRIBUTE_NORMAL,
47 except WindowsError, (winerror, strerror):
48 retry_cnt = retry_cnt - 1
51 elif winerror == 32: # ERROR_SHARING_VIOLATION
56 def executeCommand(command, cwd=None, env=None):
57 p = subprocess.Popen(command, cwd=cwd,
58 stdin=subprocess.PIPE,
59 stdout=subprocess.PIPE,
60 stderr=subprocess.PIPE,
62 out,err = p.communicate()
65 # Detect Ctrl-C in subprocess.
66 if exitCode == -signal.SIGINT:
67 raise KeyboardInterrupt
69 return out, err, exitCode
71 def executeShCmd(cmd, cfg, cwd, results):
72 if isinstance(cmd, ShUtil.Seq):
74 res = executeShCmd(cmd.lhs, cfg, cwd, results)
75 return executeShCmd(cmd.rhs, cfg, cwd, results)
78 raise NotImplementedError,"unsupported test command: '&'"
81 res = executeShCmd(cmd.lhs, cfg, cwd, results)
83 res = executeShCmd(cmd.rhs, cfg, cwd, results)
86 res = executeShCmd(cmd.lhs, cfg, cwd, results)
91 res = executeShCmd(cmd.rhs, cfg, cwd, results)
94 raise ValueError,'Unknown shell command: %r' % cmd.op
96 assert isinstance(cmd, ShUtil.Pipeline)
98 input = subprocess.PIPE
102 named_temp_files = []
103 # To avoid deadlock, we use a single stderr stream for piped
104 # output. This is null until we have seen some output using
106 for i,j in enumerate(cmd.commands):
107 # Apply the redirections, we use (N,) as a sentinal to indicate stdin,
108 # stdout, stderr for N equal to 0, 1, or 2 respectively. Redirects to or
109 # from a file are represented with a list [file, mode, file-object]
110 # where file-object is initially None.
111 redirects = [(0,), (1,), (2,)]
112 for r in j.redirects:
114 redirects[2] = [r[1], 'w', None]
115 elif r[0] == ('>>',2):
116 redirects[2] = [r[1], 'a', None]
117 elif r[0] == ('>&',2) and r[1] in '012':
118 redirects[2] = redirects[int(r[1])]
119 elif r[0] == ('>&',) or r[0] == ('&>',):
120 redirects[1] = redirects[2] = [r[1], 'w', None]
122 redirects[1] = [r[1], 'w', None]
123 elif r[0] == ('>>',):
124 redirects[1] = [r[1], 'a', None]
126 redirects[0] = [r[1], 'r', None]
128 raise NotImplementedError,"Unsupported redirect: %r" % (r,)
130 # Map from the final redirections to something subprocess can handle.
132 for index,r in enumerate(redirects):
137 raise NotImplementedError,"Unsupported redirect for stdin"
139 result = subprocess.PIPE
141 result = subprocess.STDOUT
144 raise NotImplementedError,"Unsupported redirect on stdout"
145 result = subprocess.PIPE
148 if kAvoidDevNull and r[0] == '/dev/null':
149 r[2] = tempfile.TemporaryFile(mode=r[1])
151 r[2] = open(r[0], r[1])
152 # Workaround a Win32 and/or subprocess bug when appending.
154 # FIXME: Actually, this is probably an instance of PR6753.
157 opened_files.append(r[2])
159 written_files.append(r[0])
161 final_redirects.append(result)
163 stdin, stdout, stderr = final_redirects
165 # If stderr wants to come from stdout, but stdout isn't a pipe, then put
166 # stderr on a pipe and treat it as stdout.
167 if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE):
168 stderr = subprocess.PIPE
169 stderrIsStdout = True
171 stderrIsStdout = False
173 # Don't allow stderr on a PIPE except for the last
174 # process, this could deadlock.
176 # FIXME: This is slow, but so is deadlock.
177 if stderr == subprocess.PIPE and j != cmd.commands[-1]:
178 stderr = tempfile.TemporaryFile(mode='w+b')
179 stderrTempFiles.append((i, stderr))
181 # Resolve the executable path ourselves.
183 args[0] = Util.which(args[0], cfg.environment['PATH'])
185 raise InternalShellError(j, '%r: command not found' % j.args[0])
187 # Replace uses of /dev/null with temporary files.
189 for i,arg in enumerate(args):
190 if arg == "/dev/null":
191 f = tempfile.NamedTemporaryFile(delete=False)
193 named_temp_files.append(f.name)
196 procs.append(subprocess.Popen(args, cwd=cwd,
200 env = cfg.environment,
201 close_fds = kUseCloseFDs))
203 # Immediately close stdin for any process taking stdin from us.
204 if stdin == subprocess.PIPE:
205 procs[-1].stdin.close()
206 procs[-1].stdin = None
208 # Update the current stdin source.
209 if stdout == subprocess.PIPE:
210 input = procs[-1].stdout
212 input = procs[-1].stderr
214 input = subprocess.PIPE
216 # Explicitly close any redirected files. We need to do this now because we
217 # need to release any handles we may have on the temporary files (important
218 # on Win32, for example). Since we have already spawned the subprocess, our
219 # handles have already been transferred so we do not need them anymore.
220 for f in opened_files:
223 # FIXME: There is probably still deadlock potential here. Yawn.
224 procData = [None] * len(procs)
225 procData[-1] = procs[-1].communicate()
227 for i in range(len(procs) - 1):
228 if procs[i].stdout is not None:
229 out = procs[i].stdout.read()
232 if procs[i].stderr is not None:
233 err = procs[i].stderr.read()
236 procData[i] = (out,err)
238 # Read stderr out of the temp files.
239 for i,f in stderrTempFiles:
241 procData[i] = (procData[i][0], f.read())
244 for i,(out,err) in enumerate(procData):
245 res = procs[i].wait()
246 # Detect Ctrl-C in subprocess.
247 if res == -signal.SIGINT:
248 raise KeyboardInterrupt
250 results.append((cmd.commands[i], out, err, res))
252 # Python treats the exit code as a signed char.
254 exitCode = min(exitCode, res)
256 exitCode = max(exitCode, res)
260 # Make sure written_files is released by other (child) processes.
262 for f in written_files:
265 # Remove any named temporary files we created.
266 for f in named_temp_files:
270 exitCode = not exitCode
274 def executeScriptInternal(test, litConfig, tmpBase, commands, cwd):
275 ln = ' &&\n'.join(commands)
277 cmd = ShUtil.ShParser(ln, litConfig.isWindows).parse()
279 return (Test.FAIL, "shell parser error on: %r" % ln)
283 exitCode = executeShCmd(cmd, test.config, cwd, results)
284 except InternalShellError,e:
290 for i,(cmd, cmd_out,cmd_err,res) in enumerate(results):
291 out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
292 out += 'Command %d Result: %r\n' % (i, res)
293 out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
294 out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
296 return out, err, exitCode
298 def executeTclScriptInternal(test, litConfig, tmpBase, commands, cwd):
302 # Given the unfortunate way LLVM's test are written, the line gets
303 # backslash substitution done twice.
304 ln = TclUtil.TclLexer(ln).lex_unquoted(process_all = True)
307 tokens = list(TclUtil.TclLexer(ln).lex())
309 return (Test.FAIL, "Tcl lexer error on: %r" % ln)
311 # Validate there are no control tokens.
313 if not isinstance(t, str):
315 "Invalid test line: %r containing %r" % (ln, t))
318 cmds.append(TclUtil.TclExecCommand(tokens).parse_pipeline())
320 return (Test.FAIL, "Tcl 'exec' parse error on: %r" % ln)
322 if litConfig.useValgrind:
323 for pipeline in cmds:
324 if pipeline.commands:
325 # Only valgrind the first command in each pipeline, to avoid
326 # valgrinding things like grep, not, and FileCheck.
327 cmd = pipeline.commands[0]
328 cmd.args = litConfig.valgrindArgs + cmd.args
332 cmd = ShUtil.Seq(cmd, '&&', c)
334 # FIXME: This is lame, we shouldn't need bash. See PR5240.
335 bashPath = litConfig.getBashPath()
336 if litConfig.useTclAsSh and bashPath:
337 script = tmpBase + '.script'
341 print >>f, 'set -o pipefail'
342 cmd.toShell(f, pipefail = True)
346 print >>sys.stdout, cmd
347 print >>sys.stdout, open(script).read()
351 command = [litConfig.getBashPath(), script]
352 out,err,exitCode = executeCommand(command, cwd=cwd,
353 env=test.config.environment)
355 return out,err,exitCode
359 exitCode = executeShCmd(cmd, test.config, cwd, results)
360 except InternalShellError,e:
361 results.append((e.command, '', e.message + '\n', 255))
366 for i,(cmd, cmd_out, cmd_err, res) in enumerate(results):
367 out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
368 out += 'Command %d Result: %r\n' % (i, res)
369 out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
370 out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
372 return out, err, exitCode
374 def executeScript(test, litConfig, tmpBase, commands, cwd):
375 bashPath = litConfig.getBashPath();
376 isWin32CMDEXE = (litConfig.isWindows and not bashPath)
377 script = tmpBase + '.script'
384 f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
386 f.write(' &&\n'.join(commands))
391 command = ['cmd','/c', script]
394 command = [bashPath, script]
396 command = ['/bin/sh', script]
397 if litConfig.useValgrind:
398 # FIXME: Running valgrind on sh is overkill. We probably could just
399 # run on clang with no real loss.
400 command = litConfig.valgrindArgs + command
402 return executeCommand(command, cwd=cwd, env=test.config.environment)
404 def isExpectedFail(xfails, xtargets, target_triple):
405 # Check if any xfail matches this target.
407 if item == '*' or item in target_triple:
412 # If so, see if it is expected to pass on this target.
414 # FIXME: Rename XTARGET to something that makes sense, like XPASS.
415 for item in xtargets:
416 if item == '*' or item in target_triple:
421 def parseIntegratedTestScript(test, normalize_slashes=False,
422 extra_substitutions=[]):
423 """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
424 script and extract the lines to 'RUN' as well as 'XFAIL' and 'XTARGET'
425 information. The RUN lines also will have variable substitution performed.
428 # Get the temporary location, this is always relative to the test suite
429 # root, not test source root.
431 # FIXME: This should not be here?
432 sourcepath = test.getSourcePath()
433 sourcedir = os.path.dirname(sourcepath)
434 execpath = test.getExecPath()
435 execdir,execbase = os.path.split(execpath)
436 tmpDir = os.path.join(execdir, 'Output')
437 tmpBase = os.path.join(tmpDir, execbase)
438 if test.index is not None:
439 tmpBase += '_%d' % test.index
441 # Normalize slashes, if requested.
442 if normalize_slashes:
443 sourcepath = sourcepath.replace('\\', '/')
444 sourcedir = sourcedir.replace('\\', '/')
445 tmpDir = tmpDir.replace('\\', '/')
446 tmpBase = tmpBase.replace('\\', '/')
448 # We use #_MARKER_# to hide %% while we do the other substitutions.
449 substitutions = list(extra_substitutions)
450 substitutions.extend([('%%', '#_MARKER_#')])
451 substitutions.extend(test.config.substitutions)
452 substitutions.extend([('%s', sourcepath),
455 ('%t', tmpBase + '.tmp'),
457 # FIXME: Remove this once we kill DejaGNU.
458 ('%abs_tmp', tmpBase + '.tmp'),
459 ('#_MARKER_#', '%')])
461 # Collect the test lines from the script.
466 for ln in open(sourcepath):
468 # Isolate the command to run.
469 index = ln.index('RUN:')
472 # Trim trailing whitespace.
475 # Collapse lines with trailing '\\'.
476 if script and script[-1][-1] == '\\':
477 script[-1] = script[-1][:-1] + ln
481 items = ln[ln.index('XFAIL:') + 6:].split(',')
482 xfails.extend([s.strip() for s in items])
483 elif 'XTARGET:' in ln:
484 items = ln[ln.index('XTARGET:') + 8:].split(',')
485 xtargets.extend([s.strip() for s in items])
486 elif 'REQUIRES:' in ln:
487 items = ln[ln.index('REQUIRES:') + 9:].split(',')
488 requires.extend([s.strip() for s in items])
490 # Check for END. lines.
491 if ln[ln.index('END.'):].strip() == 'END.':
494 # Apply substitutions to the script. Allow full regular
495 # expression syntax. Replace each matching occurrence of regular
496 # expression pattern a with substitution b in line ln.
498 # Apply substitutions
499 for a,b in substitutions:
501 b = b.replace("\\","\\\\")
502 ln = re.sub(a, b, ln)
504 # Strip the trailing newline and any extra whitespace.
506 script = map(processLine, script)
508 # Verify the script contains a run line.
510 return (Test.UNRESOLVED, "Test has no run line!")
512 # Check for unterminated run lines.
513 if script[-1][-1] == '\\':
514 return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
516 # Check that we have the required features:
517 missing_required_features = [f for f in requires
518 if f not in test.config.available_features]
519 if missing_required_features:
520 msg = ', '.join(missing_required_features)
521 return (Test.UNSUPPORTED,
522 "Test requires the following features: %s" % msg)
524 isXFail = isExpectedFail(xfails, xtargets, test.suite.config.target_triple)
525 return script,isXFail,tmpBase,execdir
527 def formatTestOutput(status, out, err, exitCode, failDueToStderr, script):
528 output = StringIO.StringIO()
529 print >>output, "Script:"
531 print >>output, '\n'.join(script)
533 print >>output, "Exit Code: %r" % exitCode,
535 print >>output, "(but there was output on stderr)"
539 print >>output, "Command Output (stdout):"
544 print >>output, "Command Output (stderr):"
548 return (status, output.getvalue())
550 def executeTclTest(test, litConfig):
551 if test.config.unsupported:
552 return (Test.UNSUPPORTED, 'Test is unsupported')
554 # Parse the test script, normalizing slashes in substitutions on Windows
555 # (since otherwise Tcl style lexing will treat them as escapes).
556 res = parseIntegratedTestScript(test, normalize_slashes=kIsWindows)
560 script, isXFail, tmpBase, execdir = res
562 if litConfig.noExecute:
563 return (Test.PASS, '')
565 # Create the output directory if it does not already exist.
566 Util.mkdir_p(os.path.dirname(tmpBase))
568 res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
572 # Test for failure. In addition to the exit code, Tcl commands are
573 # considered to fail if there is any standard error output.
574 out,err,exitCode = res
576 ok = exitCode != 0 or err and not litConfig.ignoreStdErr
582 ok = exitCode == 0 and (not err or litConfig.ignoreStdErr)
591 # Set a flag for formatTestOutput so it can explain why the test was
592 # considered to have failed, despite having an exit code of 0.
593 failDueToStderr = exitCode == 0 and err and not litConfig.ignoreStdErr
595 return formatTestOutput(status, out, err, exitCode, failDueToStderr, script)
597 def executeShTest(test, litConfig, useExternalSh,
598 extra_substitutions=[]):
599 if test.config.unsupported:
600 return (Test.UNSUPPORTED, 'Test is unsupported')
602 res = parseIntegratedTestScript(test, useExternalSh, extra_substitutions)
606 script, isXFail, tmpBase, execdir = res
608 if litConfig.noExecute:
609 return (Test.PASS, '')
611 # Create the output directory if it does not already exist.
612 Util.mkdir_p(os.path.dirname(tmpBase))
615 res = executeScript(test, litConfig, tmpBase, script, execdir)
617 res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
621 out,err,exitCode = res
638 # Sh tests are not considered to fail just from stderr output.
639 failDueToStderr = False
641 return formatTestOutput(status, out, err, exitCode, failDueToStderr, script)