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