lit: When running Tcl scripts via shell, try harder to find 'bash', but fall
[oota-llvm.git] / utils / lit / TestRunner.py
1 import os, signal, subprocess, sys
2 import StringIO
3
4 import ShUtil
5 import Test
6 import Util
7
8 import platform
9 import tempfile
10
11 class InternalShellError(Exception):
12     def __init__(self, command, message):
13         self.command = command
14         self.message = message
15
16 # Don't use close_fds on Windows.
17 kUseCloseFDs = platform.system() != 'Windows'
18 def executeCommand(command, cwd=None, env=None):
19     p = subprocess.Popen(command, cwd=cwd,
20                          stdin=subprocess.PIPE,
21                          stdout=subprocess.PIPE,
22                          stderr=subprocess.PIPE,
23                          env=env)
24     out,err = p.communicate()
25     exitCode = p.wait()
26
27     # Detect Ctrl-C in subprocess.
28     if exitCode == -signal.SIGINT:
29         raise KeyboardInterrupt
30
31     return out, err, exitCode
32
33 def executeShCmd(cmd, cfg, cwd, results):
34     if isinstance(cmd, ShUtil.Seq):
35         if cmd.op == ';':
36             res = executeShCmd(cmd.lhs, cfg, cwd, results)
37             return executeShCmd(cmd.rhs, cfg, cwd, results)
38
39         if cmd.op == '&':
40             raise NotImplementedError,"unsupported test command: '&'"
41
42         if cmd.op == '||':
43             res = executeShCmd(cmd.lhs, cfg, cwd, results)
44             if res != 0:
45                 res = executeShCmd(cmd.rhs, cfg, cwd, results)
46             return res
47         if cmd.op == '&&':
48             res = executeShCmd(cmd.lhs, cfg, cwd, results)
49             if res is None:
50                 return res
51
52             if res == 0:
53                 res = executeShCmd(cmd.rhs, cfg, cwd, results)
54             return res
55
56         raise ValueError,'Unknown shell command: %r' % cmd.op
57
58     assert isinstance(cmd, ShUtil.Pipeline)
59     procs = []
60     input = subprocess.PIPE
61     stderrTempFiles = []
62     # To avoid deadlock, we use a single stderr stream for piped
63     # output. This is null until we have seen some output using
64     # stderr.
65     for i,j in enumerate(cmd.commands):
66         redirects = [(0,), (1,), (2,)]
67         for r in j.redirects:
68             if r[0] == ('>',2):
69                 redirects[2] = [r[1], 'w', None]
70             elif r[0] == ('>&',2) and r[1] in '012':
71                 redirects[2] = redirects[int(r[1])]
72             elif r[0] == ('>&',) or r[0] == ('&>',):
73                 redirects[1] = redirects[2] = [r[1], 'w', None]
74             elif r[0] == ('>',):
75                 redirects[1] = [r[1], 'w', None]
76             elif r[0] == ('<',):
77                 redirects[0] = [r[1], 'r', None]
78             else:
79                 raise NotImplementedError,"Unsupported redirect: %r" % (r,)
80
81         final_redirects = []
82         for index,r in enumerate(redirects):
83             if r == (0,):
84                 result = input
85             elif r == (1,):
86                 if index == 0:
87                     raise NotImplementedError,"Unsupported redirect for stdin"
88                 elif index == 1:
89                     result = subprocess.PIPE
90                 else:
91                     result = subprocess.STDOUT
92             elif r == (2,):
93                 if index != 2:
94                     raise NotImplementedError,"Unsupported redirect on stdout"
95                 result = subprocess.PIPE
96             else:
97                 if r[2] is None:
98                     r[2] = open(r[0], r[1])
99                 result = r[2]
100             final_redirects.append(result)
101
102         stdin, stdout, stderr = final_redirects
103
104         # If stderr wants to come from stdout, but stdout isn't a pipe, then put
105         # stderr on a pipe and treat it as stdout.
106         if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE):
107             stderr = subprocess.PIPE
108             stderrIsStdout = True
109         else:
110             stderrIsStdout = False
111
112             # Don't allow stderr on a PIPE except for the last
113             # process, this could deadlock.
114             #
115             # FIXME: This is slow, but so is deadlock.
116             if stderr == subprocess.PIPE and j != cmd.commands[-1]:
117                 stderr = tempfile.TemporaryFile(mode='w+b')
118                 stderrTempFiles.append((i, stderr))
119
120         # Resolve the executable path ourselves.
121         args = list(j.args)
122         args[0] = Util.which(args[0], cfg.environment['PATH'])
123         if not args[0]:
124             raise InternalShellError(j, '%r: command not found' % j.args[0])
125
126         procs.append(subprocess.Popen(args, cwd=cwd,
127                                       stdin = stdin,
128                                       stdout = stdout,
129                                       stderr = stderr,
130                                       env = cfg.environment,
131                                       close_fds = kUseCloseFDs))
132
133         # Immediately close stdin for any process taking stdin from us.
134         if stdin == subprocess.PIPE:
135             procs[-1].stdin.close()
136             procs[-1].stdin = None
137
138         # Update the current stdin source.
139         if stdout == subprocess.PIPE:
140             input = procs[-1].stdout
141         elif stderrIsStdout:
142             input = procs[-1].stderr
143         else:
144             input = subprocess.PIPE
145
146     # FIXME: There is probably still deadlock potential here. Yawn.
147     procData = [None] * len(procs)
148     procData[-1] = procs[-1].communicate()
149
150     for i in range(len(procs) - 1):
151         if procs[i].stdout is not None:
152             out = procs[i].stdout.read()
153         else:
154             out = ''
155         if procs[i].stderr is not None:
156             err = procs[i].stderr.read()
157         else:
158             err = ''
159         procData[i] = (out,err)
160         
161     # Read stderr out of the temp files.
162     for i,f in stderrTempFiles:
163         f.seek(0, 0)
164         procData[i] = (procData[i][0], f.read())
165
166     exitCode = None
167     for i,(out,err) in enumerate(procData):
168         res = procs[i].wait()
169         # Detect Ctrl-C in subprocess.
170         if res == -signal.SIGINT:
171             raise KeyboardInterrupt
172
173         results.append((cmd.commands[i], out, err, res))
174         if cmd.pipe_err:
175             # Python treats the exit code as a signed char.
176             if res < 0:
177                 exitCode = min(exitCode, res)
178             else:
179                 exitCode = max(exitCode, res)
180         else:
181             exitCode = res
182
183     if cmd.negate:
184         exitCode = not exitCode
185
186     return exitCode
187
188 def executeScriptInternal(test, litConfig, tmpBase, commands, cwd):
189     ln = ' &&\n'.join(commands)
190     try:
191         cmd = ShUtil.ShParser(ln, litConfig.isWindows).parse()
192     except:
193         return (Test.FAIL, "shell parser error on: %r" % ln)
194
195     results = []
196     try:
197         exitCode = executeShCmd(cmd, test.config, cwd, results)
198     except InternalShellError,e:
199         out = ''
200         err = e.message
201         exitCode = 255
202
203     out = err = ''
204     for i,(cmd, cmd_out,cmd_err,res) in enumerate(results):
205         out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
206         out += 'Command %d Result: %r\n' % (i, res)
207         out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
208         out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
209
210     return out, err, exitCode
211
212 def executeTclScriptInternal(test, litConfig, tmpBase, commands, cwd):
213     import TclUtil
214     cmds = []
215     for ln in commands:
216         # Given the unfortunate way LLVM's test are written, the line gets
217         # backslash substitution done twice.
218         ln = TclUtil.TclLexer(ln).lex_unquoted(process_all = True)
219
220         try:
221             tokens = list(TclUtil.TclLexer(ln).lex())
222         except:
223             return (Test.FAIL, "Tcl lexer error on: %r" % ln)
224
225         # Validate there are no control tokens.
226         for t in tokens:
227             if not isinstance(t, str):
228                 return (Test.FAIL,
229                         "Invalid test line: %r containing %r" % (ln, t))
230
231         try:
232             cmds.append(TclUtil.TclExecCommand(tokens).parse_pipeline())
233         except:
234             return (Test.FAIL, "Tcl 'exec' parse error on: %r" % ln)
235
236     cmd = cmds[0]
237     for c in cmds[1:]:
238         cmd = ShUtil.Seq(cmd, '&&', c)
239
240     # FIXME: This is lame, we shouldn't need bash. See PR5240.
241     bashPath = litConfig.getBashPath()
242     if litConfig.useTclAsSh and bashPath:
243         script = tmpBase + '.script'
244
245         # Write script file
246         f = open(script,'w')
247         print >>f, 'set -o pipefail'
248         cmd.toShell(f, pipefail = True)
249         f.close()
250
251         if 0:
252             print >>sys.stdout, cmd
253             print >>sys.stdout, open(script).read()
254             print >>sys.stdout
255             return '', '', 0
256
257         command = [litConfig.getBashPath(), script]
258         out,err,exitCode = executeCommand(command, cwd=cwd,
259                                           env=test.config.environment)
260
261         # Tcl commands fail on standard error output.
262         if err:
263             exitCode = 1
264             out = 'Command has output on stderr!\n\n' + out
265
266         return out,err,exitCode
267     else:
268         results = []
269         try:
270             exitCode = executeShCmd(cmd, test.config, cwd, results)
271         except InternalShellError,e:
272             results.append((e.command, '', e.message + '\n', 255))
273             exitCode = 255
274
275     out = err = ''
276
277     # Tcl commands fail on standard error output.
278     if [True for _,_,err,res in results if err]:
279         exitCode = 1
280         out += 'Command has output on stderr!\n\n'
281
282     for i,(cmd, cmd_out, cmd_err, res) in enumerate(results):
283         out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
284         out += 'Command %d Result: %r\n' % (i, res)
285         out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
286         out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
287
288     return out, err, exitCode
289
290 def executeScript(test, litConfig, tmpBase, commands, cwd):
291     script = tmpBase + '.script'
292     if litConfig.isWindows:
293         script += '.bat'
294
295     # Write script file
296     f = open(script,'w')
297     if litConfig.isWindows:
298         f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
299     else:
300         f.write(' &&\n'.join(commands))
301     f.write('\n')
302     f.close()
303
304     if litConfig.isWindows:
305         command = ['cmd','/c', script]
306     else:
307         command = ['/bin/sh', script]
308         if litConfig.useValgrind:
309             # FIXME: Running valgrind on sh is overkill. We probably could just
310             # run on clang with no real loss.
311             valgrindArgs = ['valgrind', '-q',
312                             '--tool=memcheck', '--trace-children=yes',
313                             '--error-exitcode=123']
314             valgrindArgs.extend(litConfig.valgrindArgs)
315
316             command = valgrindArgs + command
317
318     return executeCommand(command, cwd=cwd, env=test.config.environment)
319
320 def parseIntegratedTestScript(test, xfailHasColon, requireAndAnd):
321     """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
322     script and extract the lines to 'RUN' as well as 'XFAIL' and 'XTARGET'
323     information. The RUN lines also will have variable substitution performed.
324     """
325
326     # Get the temporary location, this is always relative to the test suite
327     # root, not test source root.
328     #
329     # FIXME: This should not be here?
330     sourcepath = test.getSourcePath()
331     execpath = test.getExecPath()
332     execdir,execbase = os.path.split(execpath)
333     tmpBase = os.path.join(execdir, 'Output', execbase)
334
335     # We use #_MARKER_# to hide %% while we do the other substitutions.
336     substitutions = [('%%', '#_MARKER_#')]
337     substitutions.extend(test.config.substitutions)
338     substitutions.extend([('%s', sourcepath),
339                           ('%S', os.path.dirname(sourcepath)),
340                           ('%p', os.path.dirname(sourcepath)),
341                           ('%t', tmpBase + '.tmp'),
342                           # FIXME: Remove this once we kill DejaGNU.
343                           ('%abs_tmp', tmpBase + '.tmp'),
344                           ('#_MARKER_#', '%')])
345
346     # Collect the test lines from the script.
347     script = []
348     xfails = []
349     xtargets = []
350     for ln in open(sourcepath):
351         if 'RUN:' in ln:
352             # Isolate the command to run.
353             index = ln.index('RUN:')
354             ln = ln[index+4:]
355
356             # Trim trailing whitespace.
357             ln = ln.rstrip()
358
359             # Collapse lines with trailing '\\'.
360             if script and script[-1][-1] == '\\':
361                 script[-1] = script[-1][:-1] + ln
362             else:
363                 script.append(ln)
364         elif xfailHasColon and 'XFAIL:' in ln:
365             items = ln[ln.index('XFAIL:') + 6:].split(',')
366             xfails.extend([s.strip() for s in items])
367         elif not xfailHasColon and 'XFAIL' in ln:
368             items = ln[ln.index('XFAIL') + 5:].split(',')
369             xfails.extend([s.strip() for s in items])
370         elif 'XTARGET:' in ln:
371             items = ln[ln.index('XTARGET:') + 8:].split(',')
372             xtargets.extend([s.strip() for s in items])
373         elif 'END.' in ln:
374             # Check for END. lines.
375             if ln[ln.index('END.'):].strip() == 'END.':
376                 break
377
378     # Apply substitutions to the script.
379     def processLine(ln):
380         # Apply substitutions
381         for a,b in substitutions:
382             ln = ln.replace(a,b)
383
384         # Strip the trailing newline and any extra whitespace.
385         return ln.strip()
386     script = map(processLine, script)
387
388     # Verify the script contains a run line.
389     if not script:
390         return (Test.UNRESOLVED, "Test has no run line!")
391
392     if script[-1][-1] == '\\':
393         return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
394
395     # Validate interior lines for '&&', a lovely historical artifact.
396     if requireAndAnd:
397         for i in range(len(script) - 1):
398             ln = script[i]
399
400             if not ln.endswith('&&'):
401                 return (Test.FAIL,
402                         ("MISSING \'&&\': %s\n"  +
403                          "FOLLOWED BY   : %s\n") % (ln, script[i + 1]))
404
405             # Strip off '&&'
406             script[i] = ln[:-2]
407
408     return script,xfails,xtargets,tmpBase,execdir
409
410 def formatTestOutput(status, out, err, exitCode, script):
411     output = StringIO.StringIO()
412     print >>output, "Script:"
413     print >>output, "--"
414     print >>output, '\n'.join(script)
415     print >>output, "--"
416     print >>output, "Exit Code: %r" % exitCode
417     print >>output, "Command Output (stdout):"
418     print >>output, "--"
419     output.write(out)
420     print >>output, "--"
421     print >>output, "Command Output (stderr):"
422     print >>output, "--"
423     output.write(err)
424     print >>output, "--"
425     return (status, output.getvalue())
426
427 def executeTclTest(test, litConfig):
428     if test.config.unsupported:
429         return (Test.UNSUPPORTED, 'Test is unsupported')
430
431     res = parseIntegratedTestScript(test, True, False)
432     if len(res) == 2:
433         return res
434
435     script, xfails, xtargets, tmpBase, execdir = res
436
437     if litConfig.noExecute:
438         return (Test.PASS, '')
439
440     # Create the output directory if it does not already exist.
441     Util.mkdir_p(os.path.dirname(tmpBase))
442
443     res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
444     if len(res) == 2:
445         return res
446
447     isXFail = False
448     for item in xfails:
449         if item == '*' or item in test.suite.config.target_triple:
450             isXFail = True
451             break
452
453     # If this is XFAIL, see if it is expected to pass on this target.
454     if isXFail:
455         for item in xtargets:
456             if item == '*' or item in test.suite.config.target_triple:
457                 isXFail = False
458                 break
459
460     out,err,exitCode = res
461     if isXFail:
462         ok = exitCode != 0
463         status = (Test.XPASS, Test.XFAIL)[ok]
464     else:
465         ok = exitCode == 0
466         status = (Test.FAIL, Test.PASS)[ok]
467
468     if ok:
469         return (status,'')
470
471     return formatTestOutput(status, out, err, exitCode, script)
472
473 def executeShTest(test, litConfig, useExternalSh, requireAndAnd):
474     if test.config.unsupported:
475         return (Test.UNSUPPORTED, 'Test is unsupported')
476
477     res = parseIntegratedTestScript(test, False, requireAndAnd)
478     if len(res) == 2:
479         return res
480
481     script, xfails, xtargets, tmpBase, execdir = res
482
483     if litConfig.noExecute:
484         return (Test.PASS, '')
485
486     # Create the output directory if it does not already exist.
487     Util.mkdir_p(os.path.dirname(tmpBase))
488
489     if useExternalSh:
490         res = executeScript(test, litConfig, tmpBase, script, execdir)
491     else:
492         res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
493     if len(res) == 2:
494         return res
495
496     out,err,exitCode = res
497     if xfails:
498         ok = exitCode != 0
499         status = (Test.XPASS, Test.XFAIL)[ok]
500     else:
501         ok = exitCode == 0
502         status = (Test.FAIL, Test.PASS)[ok]
503
504     if ok:
505         return (status,'')
506
507     return formatTestOutput(status, out, err, exitCode, script)