Expand uses of python 2.6's "A if B else C" syntax into regular
[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, failDueToStderr, 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 failDueToStderr:
481         print >>output, "(but there was output on stderr)"
482     else:
483         print >>output
484     if out:
485         print >>output, "Command Output (stdout):"
486         print >>output, "--"
487         output.write(out)
488         print >>output, "--"
489     if err:
490         print >>output, "Command Output (stderr):"
491         print >>output, "--"
492         output.write(err)
493         print >>output, "--"
494     return (status, output.getvalue())
495
496 def executeTclTest(test, litConfig):
497     if test.config.unsupported:
498         return (Test.UNSUPPORTED, 'Test is unsupported')
499
500     # Parse the test script, normalizing slashes in substitutions on Windows
501     # (since otherwise Tcl style lexing will treat them as escapes).
502     res = parseIntegratedTestScript(test, normalize_slashes=kIsWindows)
503     if len(res) == 2:
504         return res
505
506     script, isXFail, tmpBase, execdir = res
507
508     if litConfig.noExecute:
509         return (Test.PASS, '')
510
511     # Create the output directory if it does not already exist.
512     Util.mkdir_p(os.path.dirname(tmpBase))
513
514     res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
515     if len(res) == 2:
516         return res
517
518     # Test for failure. In addition to the exit code, Tcl commands are
519     # considered to fail if there is any standard error output.
520     out,err,exitCode = res
521     if isXFail:
522         ok = exitCode != 0 or err
523         if ok:
524             status = Test.XFAIL
525         else:
526             status = Test.XPASS
527     else:
528         ok = exitCode == 0 and not err
529         if ok:
530             status = Test.PASS
531         else:
532             status = Test.FAIL
533
534     if ok:
535         return (status,'')
536
537     # Set a flag for formatTestOutput so it can explain why the test was
538     # considered to have failed, despite having an exit code of 0.
539     failDueToStderr = exitCode == 0 and err
540
541     return formatTestOutput(status, out, err, exitCode, failDueToStderr, script)
542
543 def executeShTest(test, litConfig, useExternalSh):
544     if test.config.unsupported:
545         return (Test.UNSUPPORTED, 'Test is unsupported')
546
547     res = parseIntegratedTestScript(test)
548     if len(res) == 2:
549         return res
550
551     script, isXFail, tmpBase, execdir = res
552
553     if litConfig.noExecute:
554         return (Test.PASS, '')
555
556     # Create the output directory if it does not already exist.
557     Util.mkdir_p(os.path.dirname(tmpBase))
558
559     if useExternalSh:
560         res = executeScript(test, litConfig, tmpBase, script, execdir)
561     else:
562         res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
563     if len(res) == 2:
564         return res
565
566     out,err,exitCode = res
567     if isXFail:
568         ok = exitCode != 0
569         if ok:
570             status = Test.XFAIL
571         else:
572             status = Test.XPASS
573     else:
574         ok = exitCode == 0
575         if ok:
576             status = Test.PASS
577         else:
578             status = Test.FAIL
579
580     if ok:
581         return (status,'')
582
583     # Sh tests are not considered to fail just from stderr output.
584     failDueToStderr = False
585
586     return formatTestOutput(status, out, err, exitCode, failDueToStderr, script)