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