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