Don't traverse into .svn directories.
[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     if litConfig.useTclAsSh:
241         script = tmpBase + '.script'
242
243         # Write script file
244         f = open(script,'w')
245         print >>f, 'set -o pipefail'
246         cmd.toShell(f, pipefail = True)
247         f.close()
248
249         if 0:
250             print >>sys.stdout, cmd
251             print >>sys.stdout, open(script).read()
252             print >>sys.stdout
253             return '', '', 0
254
255         command = ['/bin/bash', script]
256         out,err,exitCode = executeCommand(command, cwd=cwd,
257                                           env=test.config.environment)
258
259         # Tcl commands fail on standard error output.
260         if err:
261             exitCode = 1
262             out = 'Command has output on stderr!\n\n' + out
263
264         return out,err,exitCode
265     else:
266         results = []
267         try:
268             exitCode = executeShCmd(cmd, test.config, cwd, results)
269         except InternalShellError,e:
270             results.append((e.command, '', e.message + '\n', 255))
271             exitCode = 255
272
273     out = err = ''
274
275     # Tcl commands fail on standard error output.
276     if [True for _,_,err,res in results if err]:
277         exitCode = 1
278         out += 'Command has output on stderr!\n\n'
279
280     for i,(cmd, cmd_out, cmd_err, res) in enumerate(results):
281         out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
282         out += 'Command %d Result: %r\n' % (i, res)
283         out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
284         out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
285
286     return out, err, exitCode
287
288 def executeScript(test, litConfig, tmpBase, commands, cwd):
289     script = tmpBase + '.script'
290     if litConfig.isWindows:
291         script += '.bat'
292
293     # Write script file
294     f = open(script,'w')
295     if litConfig.isWindows:
296         f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
297     else:
298         f.write(' &&\n'.join(commands))
299     f.write('\n')
300     f.close()
301
302     if litConfig.isWindows:
303         command = ['cmd','/c', script]
304     else:
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             valgrindArgs = ['valgrind', '-q',
310                             '--tool=memcheck', '--trace-children=yes',
311                             '--error-exitcode=123']
312             valgrindArgs.extend(litConfig.valgrindArgs)
313
314             command = valgrindArgs + command
315
316     return executeCommand(command, cwd=cwd, env=test.config.environment)
317
318 def parseIntegratedTestScript(test, xfailHasColon, requireAndAnd):
319     """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
320     script and extract the lines to 'RUN' as well as 'XFAIL' and 'XTARGET'
321     information. The RUN lines also will have variable substitution performed.
322     """
323
324     # Get the temporary location, this is always relative to the test suite
325     # root, not test source root.
326     #
327     # FIXME: This should not be here?
328     sourcepath = test.getSourcePath()
329     execpath = test.getExecPath()
330     execdir,execbase = os.path.split(execpath)
331     tmpBase = os.path.join(execdir, 'Output', execbase)
332
333     # We use #_MARKER_# to hide %% while we do the other substitutions.
334     substitutions = [('%%', '#_MARKER_#')]
335     substitutions.extend(test.config.substitutions)
336     substitutions.extend([('%s', sourcepath),
337                           ('%S', os.path.dirname(sourcepath)),
338                           ('%p', os.path.dirname(sourcepath)),
339                           ('%t', tmpBase + '.tmp'),
340                           # FIXME: Remove this once we kill DejaGNU.
341                           ('%abs_tmp', tmpBase + '.tmp'),
342                           ('#_MARKER_#', '%')])
343
344     # Collect the test lines from the script.
345     script = []
346     xfails = []
347     xtargets = []
348     for ln in open(sourcepath):
349         if 'RUN:' in ln:
350             # Isolate the command to run.
351             index = ln.index('RUN:')
352             ln = ln[index+4:]
353
354             # Trim trailing whitespace.
355             ln = ln.rstrip()
356
357             # Collapse lines with trailing '\\'.
358             if script and script[-1][-1] == '\\':
359                 script[-1] = script[-1][:-1] + ln
360             else:
361                 script.append(ln)
362         elif xfailHasColon and 'XFAIL:' in ln:
363             items = ln[ln.index('XFAIL:') + 6:].split(',')
364             xfails.extend([s.strip() for s in items])
365         elif not xfailHasColon and 'XFAIL' in ln:
366             items = ln[ln.index('XFAIL') + 5:].split(',')
367             xfails.extend([s.strip() for s in items])
368         elif 'XTARGET:' in ln:
369             items = ln[ln.index('XTARGET:') + 8:].split(',')
370             xtargets.extend([s.strip() for s in items])
371         elif 'END.' in ln:
372             # Check for END. lines.
373             if ln[ln.index('END.'):].strip() == 'END.':
374                 break
375
376     # Apply substitutions to the script.
377     def processLine(ln):
378         # Apply substitutions
379         for a,b in substitutions:
380             ln = ln.replace(a,b)
381
382         # Strip the trailing newline and any extra whitespace.
383         return ln.strip()
384     script = map(processLine, script)
385
386     # Verify the script contains a run line.
387     if not script:
388         return (Test.UNRESOLVED, "Test has no run line!")
389
390     if script[-1][-1] == '\\':
391         return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
392
393     # Validate interior lines for '&&', a lovely historical artifact.
394     if requireAndAnd:
395         for i in range(len(script) - 1):
396             ln = script[i]
397
398             if not ln.endswith('&&'):
399                 return (Test.FAIL,
400                         ("MISSING \'&&\': %s\n"  +
401                          "FOLLOWED BY   : %s\n") % (ln, script[i + 1]))
402
403             # Strip off '&&'
404             script[i] = ln[:-2]
405
406     return script,xfails,xtargets,tmpBase,execdir
407
408 def formatTestOutput(status, out, err, exitCode, script):
409     output = StringIO.StringIO()
410     print >>output, "Script:"
411     print >>output, "--"
412     print >>output, '\n'.join(script)
413     print >>output, "--"
414     print >>output, "Exit Code: %r" % exitCode
415     print >>output, "Command Output (stdout):"
416     print >>output, "--"
417     output.write(out)
418     print >>output, "--"
419     print >>output, "Command Output (stderr):"
420     print >>output, "--"
421     output.write(err)
422     print >>output, "--"
423     return (status, output.getvalue())
424
425 def executeTclTest(test, litConfig):
426     if test.config.unsupported:
427         return (Test.UNSUPPORTED, 'Test is unsupported')
428
429     res = parseIntegratedTestScript(test, True, False)
430     if len(res) == 2:
431         return res
432
433     script, xfails, xtargets, tmpBase, execdir = res
434
435     if litConfig.noExecute:
436         return (Test.PASS, '')
437
438     # Create the output directory if it does not already exist.
439     Util.mkdir_p(os.path.dirname(tmpBase))
440
441     res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
442     if len(res) == 2:
443         return res
444
445     isXFail = False
446     for item in xfails:
447         if item == '*' or item in test.suite.config.target_triple:
448             isXFail = True
449             break
450
451     # If this is XFAIL, see if it is expected to pass on this target.
452     if isXFail:
453         for item in xtargets:
454             if item == '*' or item in test.suite.config.target_triple:
455                 isXFail = False
456                 break
457
458     out,err,exitCode = res
459     if isXFail:
460         ok = exitCode != 0
461         status = (Test.XPASS, Test.XFAIL)[ok]
462     else:
463         ok = exitCode == 0
464         status = (Test.FAIL, Test.PASS)[ok]
465
466     if ok:
467         return (status,'')
468
469     return formatTestOutput(status, out, err, exitCode, script)
470
471 def executeShTest(test, litConfig, useExternalSh, requireAndAnd):
472     if test.config.unsupported:
473         return (Test.UNSUPPORTED, 'Test is unsupported')
474
475     res = parseIntegratedTestScript(test, False, requireAndAnd)
476     if len(res) == 2:
477         return res
478
479     script, xfails, xtargets, tmpBase, execdir = res
480
481     if litConfig.noExecute:
482         return (Test.PASS, '')
483
484     # Create the output directory if it does not already exist.
485     Util.mkdir_p(os.path.dirname(tmpBase))
486
487     if useExternalSh:
488         res = executeScript(test, litConfig, tmpBase, script, execdir)
489     else:
490         res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
491     if len(res) == 2:
492         return res
493
494     out,err,exitCode = res
495     if xfails:
496         ok = exitCode != 0
497         status = (Test.XPASS, Test.XFAIL)[ok]
498     else:
499         ok = exitCode == 0
500         status = (Test.FAIL, Test.PASS)[ok]
501
502     if ok:
503         return (status,'')
504
505     return formatTestOutput(status, out, err, exitCode, script)