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