Teach GlobalOpt to delete aliases with internal linkage (after
[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     if test.index is not None:
371         tmpBase += '_%d' % test.index
372
373     # We use #_MARKER_# to hide %% while we do the other substitutions.
374     substitutions = [('%%', '#_MARKER_#')]
375     substitutions.extend(test.config.substitutions)
376     substitutions.extend([('%s', sourcepath),
377                           ('%S', os.path.dirname(sourcepath)),
378                           ('%p', os.path.dirname(sourcepath)),
379                           ('%t', tmpBase + '.tmp'),
380                           # FIXME: Remove this once we kill DejaGNU.
381                           ('%abs_tmp', tmpBase + '.tmp'),
382                           ('#_MARKER_#', '%')])
383
384     # Collect the test lines from the script.
385     script = []
386     xfails = []
387     xtargets = []
388     for ln in open(sourcepath):
389         if 'RUN:' in ln:
390             # Isolate the command to run.
391             index = ln.index('RUN:')
392             ln = ln[index+4:]
393
394             # Trim trailing whitespace.
395             ln = ln.rstrip()
396
397             # Collapse lines with trailing '\\'.
398             if script and script[-1][-1] == '\\':
399                 script[-1] = script[-1][:-1] + ln
400             else:
401                 script.append(ln)
402         elif 'XFAIL:' in ln:
403             items = ln[ln.index('XFAIL:') + 6:].split(',')
404             xfails.extend([s.strip() for s in items])
405         elif 'XTARGET:' in ln:
406             items = ln[ln.index('XTARGET:') + 8:].split(',')
407             xtargets.extend([s.strip() for s in items])
408         elif 'END.' in ln:
409             # Check for END. lines.
410             if ln[ln.index('END.'):].strip() == 'END.':
411                 break
412
413     # Apply substitutions to the script.
414     def processLine(ln):
415         # Apply substitutions
416         for a,b in substitutions:
417             ln = ln.replace(a,b)
418
419         # Strip the trailing newline and any extra whitespace.
420         return ln.strip()
421     script = map(processLine, script)
422
423     # Verify the script contains a run line.
424     if not script:
425         return (Test.UNRESOLVED, "Test has no run line!")
426
427     if script[-1][-1] == '\\':
428         return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
429
430     isXFail = isExpectedFail(xfails, xtargets, test.suite.config.target_triple)
431     return script,isXFail,tmpBase,execdir
432
433 def formatTestOutput(status, out, err, exitCode, script):
434     output = StringIO.StringIO()
435     print >>output, "Script:"
436     print >>output, "--"
437     print >>output, '\n'.join(script)
438     print >>output, "--"
439     print >>output, "Exit Code: %r" % exitCode
440     print >>output, "Command Output (stdout):"
441     print >>output, "--"
442     output.write(out)
443     print >>output, "--"
444     print >>output, "Command Output (stderr):"
445     print >>output, "--"
446     output.write(err)
447     print >>output, "--"
448     return (status, output.getvalue())
449
450 def executeTclTest(test, litConfig):
451     if test.config.unsupported:
452         return (Test.UNSUPPORTED, 'Test is unsupported')
453
454     res = parseIntegratedTestScript(test)
455     if len(res) == 2:
456         return res
457
458     script, isXFail, tmpBase, execdir = res
459
460     if litConfig.noExecute:
461         return (Test.PASS, '')
462
463     # Create the output directory if it does not already exist.
464     Util.mkdir_p(os.path.dirname(tmpBase))
465
466     res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
467     if len(res) == 2:
468         return res
469
470     out,err,exitCode = res
471     if isXFail:
472         ok = exitCode != 0
473         status = (Test.XPASS, Test.XFAIL)[ok]
474     else:
475         ok = exitCode == 0
476         status = (Test.FAIL, Test.PASS)[ok]
477
478     if ok:
479         return (status,'')
480
481     return formatTestOutput(status, out, err, exitCode, script)
482
483 def executeShTest(test, litConfig, useExternalSh):
484     if test.config.unsupported:
485         return (Test.UNSUPPORTED, 'Test is unsupported')
486
487     res = parseIntegratedTestScript(test)
488     if len(res) == 2:
489         return res
490
491     script, isXFail, tmpBase, execdir = res
492
493     if litConfig.noExecute:
494         return (Test.PASS, '')
495
496     # Create the output directory if it does not already exist.
497     Util.mkdir_p(os.path.dirname(tmpBase))
498
499     if useExternalSh:
500         res = executeScript(test, litConfig, tmpBase, script, execdir)
501     else:
502         res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
503     if len(res) == 2:
504         return res
505
506     out,err,exitCode = res
507     if isXFail:
508         ok = exitCode != 0
509         status = (Test.XPASS, Test.XFAIL)[ok]
510     else:
511         ok = exitCode == 0
512         status = (Test.FAIL, Test.PASS)[ok]
513
514     if ok:
515         return (status,'')
516
517     return formatTestOutput(status, out, err, exitCode, script)