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