lit: Propagate TERM variable in environment, some tools can do really obscure
[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(test, xfails, xtargets):
374     # If the xfail matches an available feature, it always fails.
375     for item in xfails:
376         if item in test.config.available_features:
377             return True
378
379     # Otherwise, check if any xfail matches this target.
380     for item in xfails:
381         if item == '*' or item in test.suite.config.target_triple:
382             break
383     else:
384         return False
385
386     # If so, see if it is expected to pass on this target.
387     #
388     # FIXME: Rename XTARGET to something that makes sense, like XPASS.
389     for item in xtargets:
390         if item == '*' or item in test.suite.config.target_triple:
391             return False
392
393     return True
394
395 def parseIntegratedTestScript(test, normalize_slashes=False,
396                               extra_substitutions=[]):
397     """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
398     script and extract the lines to 'RUN' as well as 'XFAIL' and 'XTARGET'
399     information. The RUN lines also will have variable substitution performed.
400     """
401
402     # Get the temporary location, this is always relative to the test suite
403     # root, not test source root.
404     #
405     # FIXME: This should not be here?
406     sourcepath = test.getSourcePath()
407     sourcedir = os.path.dirname(sourcepath)
408     execpath = test.getExecPath()
409     execdir,execbase = os.path.split(execpath)
410     tmpDir = os.path.join(execdir, 'Output')
411     tmpBase = os.path.join(tmpDir, execbase)
412     if test.index is not None:
413         tmpBase += '_%d' % test.index
414
415     # Normalize slashes, if requested.
416     if normalize_slashes:
417         sourcepath = sourcepath.replace('\\', '/')
418         sourcedir = sourcedir.replace('\\', '/')
419         tmpDir = tmpDir.replace('\\', '/')
420         tmpBase = tmpBase.replace('\\', '/')
421
422     # We use #_MARKER_# to hide %% while we do the other substitutions.
423     substitutions = list(extra_substitutions)
424     substitutions.extend([('%%', '#_MARKER_#')])
425     substitutions.extend(test.config.substitutions)
426     substitutions.extend([('%s', sourcepath),
427                           ('%S', sourcedir),
428                           ('%p', sourcedir),
429                           ('%{pathsep}', os.pathsep),
430                           ('%t', tmpBase + '.tmp'),
431                           ('%T', tmpDir),
432                           # FIXME: Remove this once we kill DejaGNU.
433                           ('%abs_tmp', tmpBase + '.tmp'),
434                           ('#_MARKER_#', '%')])
435
436     # Collect the test lines from the script.
437     script = []
438     xfails = []
439     xtargets = []
440     requires = []
441     for ln in open(sourcepath):
442         if 'RUN:' in ln:
443             # Isolate the command to run.
444             index = ln.index('RUN:')
445             ln = ln[index+4:]
446
447             # Trim trailing whitespace.
448             ln = ln.rstrip()
449
450             # Collapse lines with trailing '\\'.
451             if script and script[-1][-1] == '\\':
452                 script[-1] = script[-1][:-1] + ln
453             else:
454                 script.append(ln)
455         elif 'XFAIL:' in ln:
456             items = ln[ln.index('XFAIL:') + 6:].split(',')
457             xfails.extend([s.strip() for s in items])
458         elif 'XTARGET:' in ln:
459             items = ln[ln.index('XTARGET:') + 8:].split(',')
460             xtargets.extend([s.strip() for s in items])
461         elif 'REQUIRES:' in ln:
462             items = ln[ln.index('REQUIRES:') + 9:].split(',')
463             requires.extend([s.strip() for s in items])
464         elif 'END.' in ln:
465             # Check for END. lines.
466             if ln[ln.index('END.'):].strip() == 'END.':
467                 break
468
469     # Apply substitutions to the script.  Allow full regular
470     # expression syntax.  Replace each matching occurrence of regular
471     # expression pattern a with substitution b in line ln.
472     def processLine(ln):
473         # Apply substitutions
474         for a,b in substitutions:
475             if kIsWindows:
476                 b = b.replace("\\","\\\\")
477             ln = re.sub(a, b, ln)
478
479         # Strip the trailing newline and any extra whitespace.
480         return ln.strip()
481     script = map(processLine, script)
482
483     # Verify the script contains a run line.
484     if not script:
485         return (Test.UNRESOLVED, "Test has no run line!")
486
487     # Check for unterminated run lines.
488     if script[-1][-1] == '\\':
489         return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
490
491     # Check that we have the required features:
492     missing_required_features = [f for f in requires
493                                  if f not in test.config.available_features]
494     if missing_required_features:
495         msg = ', '.join(missing_required_features)
496         return (Test.UNSUPPORTED,
497                 "Test requires the following features: %s" % msg)
498
499     isXFail = isExpectedFail(test, xfails, xtargets)
500     return script,isXFail,tmpBase,execdir
501
502 def formatTestOutput(status, out, err, exitCode, failDueToStderr, script):
503     output = StringIO.StringIO()
504     print >>output, "Script:"
505     print >>output, "--"
506     print >>output, '\n'.join(script)
507     print >>output, "--"
508     print >>output, "Exit Code: %r" % exitCode,
509     if failDueToStderr:
510         print >>output, "(but there was output on stderr)"
511     else:
512         print >>output
513     if out:
514         print >>output, "Command Output (stdout):"
515         print >>output, "--"
516         output.write(out)
517         print >>output, "--"
518     if err:
519         print >>output, "Command Output (stderr):"
520         print >>output, "--"
521         output.write(err)
522         print >>output, "--"
523     return (status, output.getvalue())
524
525 def executeTclTest(test, litConfig):
526     if test.config.unsupported:
527         return (Test.UNSUPPORTED, 'Test is unsupported')
528
529     # Parse the test script, normalizing slashes in substitutions on Windows
530     # (since otherwise Tcl style lexing will treat them as escapes).
531     res = parseIntegratedTestScript(test, normalize_slashes=kIsWindows)
532     if len(res) == 2:
533         return res
534
535     script, isXFail, tmpBase, execdir = res
536
537     if litConfig.noExecute:
538         return (Test.PASS, '')
539
540     # Create the output directory if it does not already exist.
541     Util.mkdir_p(os.path.dirname(tmpBase))
542
543     res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
544     if len(res) == 2:
545         return res
546
547     # Test for failure. In addition to the exit code, Tcl commands are
548     # considered to fail if there is any standard error output.
549     out,err,exitCode = res
550     if isXFail:
551         ok = exitCode != 0 or err and not litConfig.ignoreStdErr
552         if ok:
553             status = Test.XFAIL
554         else:
555             status = Test.XPASS
556     else:
557         ok = exitCode == 0 and (not err or litConfig.ignoreStdErr)
558         if ok:
559             status = Test.PASS
560         else:
561             status = Test.FAIL
562
563     if ok:
564         return (status,'')
565
566     # Set a flag for formatTestOutput so it can explain why the test was
567     # considered to have failed, despite having an exit code of 0.
568     failDueToStderr = exitCode == 0 and err and not litConfig.ignoreStdErr
569
570     return formatTestOutput(status, out, err, exitCode, failDueToStderr, script)
571
572 def executeShTest(test, litConfig, useExternalSh,
573                   extra_substitutions=[]):
574     if test.config.unsupported:
575         return (Test.UNSUPPORTED, 'Test is unsupported')
576
577     res = parseIntegratedTestScript(test, useExternalSh, extra_substitutions)
578     if len(res) == 2:
579         return res
580
581     script, isXFail, tmpBase, execdir = res
582
583     if litConfig.noExecute:
584         return (Test.PASS, '')
585
586     # Create the output directory if it does not already exist.
587     Util.mkdir_p(os.path.dirname(tmpBase))
588
589     if useExternalSh:
590         res = executeScript(test, litConfig, tmpBase, script, execdir)
591     else:
592         res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
593     if len(res) == 2:
594         return res
595
596     out,err,exitCode = res
597     if isXFail:
598         ok = exitCode != 0
599         if ok:
600             status = Test.XFAIL
601         else:
602             status = Test.XPASS
603     else:
604         ok = exitCode == 0
605         if ok:
606             status = Test.PASS
607         else:
608             status = Test.FAIL
609
610     if ok:
611         return (status,'')
612
613     # Sh tests are not considered to fail just from stderr output.
614     failDueToStderr = False
615
616     return formatTestOutput(status, out, err, exitCode, failDueToStderr, script)