1 from __future__ import absolute_import
2 import os, signal, subprocess, sys
7 from io import StringIO
9 from StringIO import StringIO
11 import lit.ShUtil as ShUtil
12 import lit.Test as Test
13 import lit.Util as Util
15 class InternalShellError(Exception):
16 def __init__(self, command, message):
17 self.command = command
18 self.message = message
20 kIsWindows = platform.system() == 'Windows'
22 # Don't use close_fds on Windows.
23 kUseCloseFDs = not kIsWindows
25 # Use temporary files to replace /dev/null on Windows.
26 kAvoidDevNull = kIsWindows
28 def executeCommand(command, cwd=None, env=None):
29 # Close extra file handles on UNIX (on Windows this cannot be done while
30 # also redirecting input).
31 close_fds = not kIsWindows
33 p = subprocess.Popen(command, cwd=cwd,
34 stdin=subprocess.PIPE,
35 stdout=subprocess.PIPE,
36 stderr=subprocess.PIPE,
37 env=env, close_fds=close_fds)
38 out,err = p.communicate()
41 # Detect Ctrl-C in subprocess.
42 if exitCode == -signal.SIGINT:
43 raise KeyboardInterrupt
45 return out, err, exitCode
47 def executeShCmd(cmd, cfg, cwd, results):
48 if isinstance(cmd, ShUtil.Seq):
50 res = executeShCmd(cmd.lhs, cfg, cwd, results)
51 return executeShCmd(cmd.rhs, cfg, cwd, results)
54 raise InternalShellError(cmd,"unsupported shell operator: '&'")
57 res = executeShCmd(cmd.lhs, cfg, cwd, results)
59 res = executeShCmd(cmd.rhs, cfg, cwd, results)
63 res = executeShCmd(cmd.lhs, cfg, cwd, results)
68 res = executeShCmd(cmd.rhs, cfg, cwd, results)
71 raise ValueError('Unknown shell command: %r' % cmd.op)
73 assert isinstance(cmd, ShUtil.Pipeline)
75 input = subprocess.PIPE
79 # To avoid deadlock, we use a single stderr stream for piped
80 # output. This is null until we have seen some output using
82 for i,j in enumerate(cmd.commands):
83 # Apply the redirections, we use (N,) as a sentinel to indicate stdin,
84 # stdout, stderr for N equal to 0, 1, or 2 respectively. Redirects to or
85 # from a file are represented with a list [file, mode, file-object]
86 # where file-object is initially None.
87 redirects = [(0,), (1,), (2,)]
90 redirects[2] = [r[1], 'w', None]
91 elif r[0] == ('>>',2):
92 redirects[2] = [r[1], 'a', None]
93 elif r[0] == ('>&',2) and r[1] in '012':
94 redirects[2] = redirects[int(r[1])]
95 elif r[0] == ('>&',) or r[0] == ('&>',):
96 redirects[1] = redirects[2] = [r[1], 'w', None]
98 redirects[1] = [r[1], 'w', None]
100 redirects[1] = [r[1], 'a', None]
102 redirects[0] = [r[1], 'r', None]
104 raise InternalShellError(j,"Unsupported redirect: %r" % (r,))
106 # Map from the final redirections to something subprocess can handle.
108 for index,r in enumerate(redirects):
113 raise InternalShellError(j,"Unsupported redirect for stdin")
115 result = subprocess.PIPE
117 result = subprocess.STDOUT
120 raise InternalShellError(j,"Unsupported redirect on stdout")
121 result = subprocess.PIPE
124 if kAvoidDevNull and r[0] == '/dev/null':
125 r[2] = tempfile.TemporaryFile(mode=r[1])
127 r[2] = open(r[0], r[1])
128 # Workaround a Win32 and/or subprocess bug when appending.
130 # FIXME: Actually, this is probably an instance of PR6753.
133 opened_files.append(r[2])
135 final_redirects.append(result)
137 stdin, stdout, stderr = final_redirects
139 # If stderr wants to come from stdout, but stdout isn't a pipe, then put
140 # stderr on a pipe and treat it as stdout.
141 if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE):
142 stderr = subprocess.PIPE
143 stderrIsStdout = True
145 stderrIsStdout = False
147 # Don't allow stderr on a PIPE except for the last
148 # process, this could deadlock.
150 # FIXME: This is slow, but so is deadlock.
151 if stderr == subprocess.PIPE and j != cmd.commands[-1]:
152 stderr = tempfile.TemporaryFile(mode='w+b')
153 stderrTempFiles.append((i, stderr))
155 # Resolve the executable path ourselves.
157 args[0] = Util.which(args[0], cfg.environment['PATH'])
159 raise InternalShellError(j, '%r: command not found' % j.args[0])
161 # Replace uses of /dev/null with temporary files.
163 for i,arg in enumerate(args):
164 if arg == "/dev/null":
165 f = tempfile.NamedTemporaryFile(delete=False)
167 named_temp_files.append(f.name)
170 procs.append(subprocess.Popen(args, cwd=cwd,
174 env = cfg.environment,
175 close_fds = kUseCloseFDs))
177 # Immediately close stdin for any process taking stdin from us.
178 if stdin == subprocess.PIPE:
179 procs[-1].stdin.close()
180 procs[-1].stdin = None
182 # Update the current stdin source.
183 if stdout == subprocess.PIPE:
184 input = procs[-1].stdout
186 input = procs[-1].stderr
188 input = subprocess.PIPE
190 # Explicitly close any redirected files. We need to do this now because we
191 # need to release any handles we may have on the temporary files (important
192 # on Win32, for example). Since we have already spawned the subprocess, our
193 # handles have already been transferred so we do not need them anymore.
194 for f in opened_files:
197 # FIXME: There is probably still deadlock potential here. Yawn.
198 procData = [None] * len(procs)
199 procData[-1] = procs[-1].communicate()
201 for i in range(len(procs) - 1):
202 if procs[i].stdout is not None:
203 out = procs[i].stdout.read()
206 if procs[i].stderr is not None:
207 err = procs[i].stderr.read()
210 procData[i] = (out,err)
212 # Read stderr out of the temp files.
213 for i,f in stderrTempFiles:
215 procData[i] = (procData[i][0], f.read())
218 for i,(out,err) in enumerate(procData):
219 res = procs[i].wait()
220 # Detect Ctrl-C in subprocess.
221 if res == -signal.SIGINT:
222 raise KeyboardInterrupt
224 results.append((cmd.commands[i], out, err, res))
226 # Python treats the exit code as a signed char.
230 exitCode = min(exitCode, res)
232 exitCode = max(exitCode, res)
236 # Remove any named temporary files we created.
237 for f in named_temp_files:
244 exitCode = not exitCode
248 def executeScriptInternal(test, litConfig, tmpBase, commands, cwd):
252 cmds.append(ShUtil.ShParser(ln, litConfig.isWindows,
253 test.config.pipefail).parse())
255 return (Test.FAIL, "shell parser error on: %r" % ln)
259 cmd = ShUtil.Seq(cmd, '&&', c)
263 exitCode = executeShCmd(cmd, test.config, cwd, results)
264 except InternalShellError:
265 e = sys.exc_info()[1]
267 results.append((e.command, '', e.message, exitCode))
270 for i,(cmd, cmd_out,cmd_err,res) in enumerate(results):
271 out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
272 out += 'Command %d Result: %r\n' % (i, res)
273 out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
274 out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
276 return out, err, exitCode
278 def executeScript(test, litConfig, tmpBase, commands, cwd):
279 bashPath = litConfig.getBashPath();
280 isWin32CMDEXE = (litConfig.isWindows and not bashPath)
281 script = tmpBase + '.script'
287 if litConfig.isWindows and not isWin32CMDEXE:
288 mode += 'b' # Avoid CRLFs when writing bash scripts.
289 f = open(script, mode)
291 f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
293 if test.config.pipefail:
294 f.write('set -o pipefail;')
295 f.write('{ ' + '; } &&\n{ '.join(commands) + '; }')
300 command = ['cmd','/c', script]
303 command = [bashPath, script]
305 command = ['/bin/sh', script]
306 if litConfig.useValgrind:
307 # FIXME: Running valgrind on sh is overkill. We probably could just
308 # run on clang with no real loss.
309 command = litConfig.valgrindArgs + command
311 return executeCommand(command, cwd=cwd, env=test.config.environment)
313 def isExpectedFail(test, xfails):
314 # Check if any of the xfails match an available feature or the target.
316 # If this is the wildcard, it always fails.
320 # If this is an exact match for one of the features, it fails.
321 if item in test.config.available_features:
324 # If this is a part of the target triple, it fails.
325 if item in test.suite.config.target_triple:
330 def parseIntegratedTestScript(test, normalize_slashes=False,
331 extra_substitutions=[]):
332 """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
333 script and extract the lines to 'RUN' as well as 'XFAIL' and 'REQUIRES'
334 information. The RUN lines also will have variable substitution performed.
337 # Get the temporary location, this is always relative to the test suite
338 # root, not test source root.
340 # FIXME: This should not be here?
341 sourcepath = test.getSourcePath()
342 sourcedir = os.path.dirname(sourcepath)
343 execpath = test.getExecPath()
344 execdir,execbase = os.path.split(execpath)
345 tmpDir = os.path.join(execdir, 'Output')
346 tmpBase = os.path.join(tmpDir, execbase)
347 if test.index is not None:
348 tmpBase += '_%d' % test.index
350 # Normalize slashes, if requested.
351 if normalize_slashes:
352 sourcepath = sourcepath.replace('\\', '/')
353 sourcedir = sourcedir.replace('\\', '/')
354 tmpDir = tmpDir.replace('\\', '/')
355 tmpBase = tmpBase.replace('\\', '/')
357 # We use #_MARKER_# to hide %% while we do the other substitutions.
358 substitutions = list(extra_substitutions)
359 substitutions.extend([('%%', '#_MARKER_#')])
360 substitutions.extend(test.config.substitutions)
361 substitutions.extend([('%s', sourcepath),
364 ('%{pathsep}', os.pathsep),
365 ('%t', tmpBase + '.tmp'),
367 ('#_MARKER_#', '%')])
369 # Collect the test lines from the script.
374 for ln in open(sourcepath):
377 # Isolate the command to run.
378 index = ln.index('RUN:')
381 # Trim trailing whitespace.
384 # Substitute line number expressions
385 ln = re.sub('%\(line\)', str(line_number), ln)
386 def replace_line_number(match):
387 if match.group(1) == '+':
388 return str(line_number + int(match.group(2)))
389 if match.group(1) == '-':
390 return str(line_number - int(match.group(2)))
391 ln = re.sub('%\(line *([\+-]) *(\d+)\)', replace_line_number, ln)
393 # Collapse lines with trailing '\\'.
394 if script and script[-1][-1] == '\\':
395 script[-1] = script[-1][:-1] + ln
399 items = ln[ln.index('XFAIL:') + 6:].split(',')
400 xfails.extend([s.strip() for s in items])
401 elif 'REQUIRES:' in ln:
402 items = ln[ln.index('REQUIRES:') + 9:].split(',')
403 requires.extend([s.strip() for s in items])
405 # Check for END. lines.
406 if ln[ln.index('END.'):].strip() == 'END.':
409 # Apply substitutions to the script. Allow full regular
410 # expression syntax. Replace each matching occurrence of regular
411 # expression pattern a with substitution b in line ln.
413 # Apply substitutions
414 for a,b in substitutions:
416 b = b.replace("\\","\\\\")
417 ln = re.sub(a, b, ln)
419 # Strip the trailing newline and any extra whitespace.
421 script = [processLine(ln)
424 # Verify the script contains a run line.
426 return (Test.UNRESOLVED, "Test has no run line!")
428 # Check for unterminated run lines.
429 if script[-1][-1] == '\\':
430 return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
432 # Check that we have the required features:
433 missing_required_features = [f for f in requires
434 if f not in test.config.available_features]
435 if missing_required_features:
436 msg = ', '.join(missing_required_features)
437 return (Test.UNSUPPORTED,
438 "Test requires the following features: %s" % msg)
440 isXFail = isExpectedFail(test, xfails)
441 return script,isXFail,tmpBase,execdir
443 def formatTestOutput(status, out, err, exitCode, script):
445 output.write(u"Script:\n")
446 output.write(u"--\n")
447 output.write(u'\n'.join(script))
448 output.write(u"\n--\n")
449 output.write(u"Exit Code: %r\n\n" % exitCode)
451 output.write(u"Command Output (stdout):\n")
452 output.write(u"--\n")
453 output.write(unicode(out))
454 output.write(u"--\n")
456 output.write(u"Command Output (stderr):\n")
457 output.write(u"--\n")
458 output.write(unicode(err))
459 output.write(u"--\n")
460 return (status, output.getvalue())
462 def executeShTest(test, litConfig, useExternalSh,
463 extra_substitutions=[]):
464 if test.config.unsupported:
465 return (Test.UNSUPPORTED, 'Test is unsupported')
467 res = parseIntegratedTestScript(test, useExternalSh, extra_substitutions)
471 script, isXFail, tmpBase, execdir = res
473 if litConfig.noExecute:
474 return (Test.PASS, '')
476 # Create the output directory if it does not already exist.
477 Util.mkdir_p(os.path.dirname(tmpBase))
480 res = executeScript(test, litConfig, tmpBase, script, execdir)
482 res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
486 out,err,exitCode = res
503 return formatTestOutput(status, out, err, exitCode, script)