[lit] Remove --repeat option, which wasn't that useful.
[oota-llvm.git] / utils / lit / lit / TestRunner.py
1 from __future__ import absolute_import
2 import os, signal, subprocess, sys
3 import re
4 import platform
5 import tempfile
6 try:
7     from io import StringIO
8 except ImportError:
9     from StringIO import StringIO
10
11 import lit.ShUtil as ShUtil
12 import lit.Test as Test
13 import lit.Util as Util
14
15 class InternalShellError(Exception):
16     def __init__(self, command, message):
17         self.command = command
18         self.message = message
19
20 kIsWindows = platform.system() == 'Windows'
21
22 # Don't use close_fds on Windows.
23 kUseCloseFDs = not kIsWindows
24
25 # Use temporary files to replace /dev/null on Windows.
26 kAvoidDevNull = kIsWindows
27
28 def executeCommand(command, cwd=None, env=None):
29     # Close extra file handles on UNIX (on Windows this cannot be done while
30     # also redirecting input).
31     close_fds = not kIsWindows
32
33     p = subprocess.Popen(command, cwd=cwd,
34                          stdin=subprocess.PIPE,
35                          stdout=subprocess.PIPE,
36                          stderr=subprocess.PIPE,
37                          env=env, close_fds=close_fds)
38     out,err = p.communicate()
39     exitCode = p.wait()
40
41     # Detect Ctrl-C in subprocess.
42     if exitCode == -signal.SIGINT:
43         raise KeyboardInterrupt
44
45     return out, err, exitCode
46
47 def executeShCmd(cmd, cfg, cwd, results):
48     if isinstance(cmd, ShUtil.Seq):
49         if cmd.op == ';':
50             res = executeShCmd(cmd.lhs, cfg, cwd, results)
51             return executeShCmd(cmd.rhs, cfg, cwd, results)
52
53         if cmd.op == '&':
54             raise InternalShellError(cmd,"unsupported shell operator: '&'")
55
56         if cmd.op == '||':
57             res = executeShCmd(cmd.lhs, cfg, cwd, results)
58             if res != 0:
59                 res = executeShCmd(cmd.rhs, cfg, cwd, results)
60             return res
61
62         if cmd.op == '&&':
63             res = executeShCmd(cmd.lhs, cfg, cwd, results)
64             if res is None:
65                 return res
66
67             if res == 0:
68                 res = executeShCmd(cmd.rhs, cfg, cwd, results)
69             return res
70
71         raise ValueError('Unknown shell command: %r' % cmd.op)
72
73     assert isinstance(cmd, ShUtil.Pipeline)
74     procs = []
75     input = subprocess.PIPE
76     stderrTempFiles = []
77     opened_files = []
78     named_temp_files = []
79     # To avoid deadlock, we use a single stderr stream for piped
80     # output. This is null until we have seen some output using
81     # stderr.
82     for i,j in enumerate(cmd.commands):
83         # Apply the redirections, we use (N,) as a sentinel to indicate stdin,
84         # stdout, stderr for N equal to 0, 1, or 2 respectively. Redirects to or
85         # from a file are represented with a list [file, mode, file-object]
86         # where file-object is initially None.
87         redirects = [(0,), (1,), (2,)]
88         for r in j.redirects:
89             if r[0] == ('>',2):
90                 redirects[2] = [r[1], 'w', None]
91             elif r[0] == ('>>',2):
92                 redirects[2] = [r[1], 'a', None]
93             elif r[0] == ('>&',2) and r[1] in '012':
94                 redirects[2] = redirects[int(r[1])]
95             elif r[0] == ('>&',) or r[0] == ('&>',):
96                 redirects[1] = redirects[2] = [r[1], 'w', None]
97             elif r[0] == ('>',):
98                 redirects[1] = [r[1], 'w', None]
99             elif r[0] == ('>>',):
100                 redirects[1] = [r[1], 'a', None]
101             elif r[0] == ('<',):
102                 redirects[0] = [r[1], 'r', None]
103             else:
104                 raise InternalShellError(j,"Unsupported redirect: %r" % (r,))
105
106         # Map from the final redirections to something subprocess can handle.
107         final_redirects = []
108         for index,r in enumerate(redirects):
109             if r == (0,):
110                 result = input
111             elif r == (1,):
112                 if index == 0:
113                     raise InternalShellError(j,"Unsupported redirect for stdin")
114                 elif index == 1:
115                     result = subprocess.PIPE
116                 else:
117                     result = subprocess.STDOUT
118             elif r == (2,):
119                 if index != 2:
120                     raise InternalShellError(j,"Unsupported redirect on stdout")
121                 result = subprocess.PIPE
122             else:
123                 if r[2] is None:
124                     if kAvoidDevNull and r[0] == '/dev/null':
125                         r[2] = tempfile.TemporaryFile(mode=r[1])
126                     else:
127                         r[2] = open(r[0], r[1])
128                     # Workaround a Win32 and/or subprocess bug when appending.
129                     #
130                     # FIXME: Actually, this is probably an instance of PR6753.
131                     if r[1] == 'a':
132                         r[2].seek(0, 2)
133                     opened_files.append(r[2])
134                 result = r[2]
135             final_redirects.append(result)
136
137         stdin, stdout, stderr = final_redirects
138
139         # If stderr wants to come from stdout, but stdout isn't a pipe, then put
140         # stderr on a pipe and treat it as stdout.
141         if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE):
142             stderr = subprocess.PIPE
143             stderrIsStdout = True
144         else:
145             stderrIsStdout = False
146
147             # Don't allow stderr on a PIPE except for the last
148             # process, this could deadlock.
149             #
150             # FIXME: This is slow, but so is deadlock.
151             if stderr == subprocess.PIPE and j != cmd.commands[-1]:
152                 stderr = tempfile.TemporaryFile(mode='w+b')
153                 stderrTempFiles.append((i, stderr))
154
155         # Resolve the executable path ourselves.
156         args = list(j.args)
157         args[0] = Util.which(args[0], cfg.environment['PATH'])
158         if not args[0]:
159             raise InternalShellError(j, '%r: command not found' % j.args[0])
160
161         # Replace uses of /dev/null with temporary files.
162         if kAvoidDevNull:
163             for i,arg in enumerate(args):
164                 if arg == "/dev/null":
165                     f = tempfile.NamedTemporaryFile(delete=False)
166                     f.close()
167                     named_temp_files.append(f.name)
168                     args[i] = f.name
169
170         procs.append(subprocess.Popen(args, cwd=cwd,
171                                       stdin = stdin,
172                                       stdout = stdout,
173                                       stderr = stderr,
174                                       env = cfg.environment,
175                                       close_fds = kUseCloseFDs))
176
177         # Immediately close stdin for any process taking stdin from us.
178         if stdin == subprocess.PIPE:
179             procs[-1].stdin.close()
180             procs[-1].stdin = None
181
182         # Update the current stdin source.
183         if stdout == subprocess.PIPE:
184             input = procs[-1].stdout
185         elif stderrIsStdout:
186             input = procs[-1].stderr
187         else:
188             input = subprocess.PIPE
189
190     # Explicitly close any redirected files. We need to do this now because we
191     # need to release any handles we may have on the temporary files (important
192     # on Win32, for example). Since we have already spawned the subprocess, our
193     # handles have already been transferred so we do not need them anymore.
194     for f in opened_files:
195         f.close()
196
197     # FIXME: There is probably still deadlock potential here. Yawn.
198     procData = [None] * len(procs)
199     procData[-1] = procs[-1].communicate()
200
201     for i in range(len(procs) - 1):
202         if procs[i].stdout is not None:
203             out = procs[i].stdout.read()
204         else:
205             out = ''
206         if procs[i].stderr is not None:
207             err = procs[i].stderr.read()
208         else:
209             err = ''
210         procData[i] = (out,err)
211
212     # Read stderr out of the temp files.
213     for i,f in stderrTempFiles:
214         f.seek(0, 0)
215         procData[i] = (procData[i][0], f.read())
216
217     exitCode = None
218     for i,(out,err) in enumerate(procData):
219         res = procs[i].wait()
220         # Detect Ctrl-C in subprocess.
221         if res == -signal.SIGINT:
222             raise KeyboardInterrupt
223
224         results.append((cmd.commands[i], out, err, res))
225         if cmd.pipe_err:
226             # Python treats the exit code as a signed char.
227             if exitCode is None:
228                 exitCode = res
229             elif res < 0:
230                 exitCode = min(exitCode, res)
231             else:
232                 exitCode = max(exitCode, res)
233         else:
234             exitCode = res
235
236     # Remove any named temporary files we created.
237     for f in named_temp_files:
238         try:
239             os.remove(f)
240         except OSError:
241             pass
242
243     if cmd.negate:
244         exitCode = not exitCode
245
246     return exitCode
247
248 def executeScriptInternal(test, litConfig, tmpBase, commands, cwd):
249     cmds = []
250     for ln in commands:
251         try:
252             cmds.append(ShUtil.ShParser(ln, litConfig.isWindows,
253                                         test.config.pipefail).parse())
254         except:
255             return (Test.FAIL, "shell parser error on: %r" % ln)
256
257     cmd = cmds[0]
258     for c in cmds[1:]:
259         cmd = ShUtil.Seq(cmd, '&&', c)
260
261     results = []
262     try:
263         exitCode = executeShCmd(cmd, test.config, cwd, results)
264     except InternalShellError:
265         e = sys.exc_info()[1]
266         exitCode = 127
267         results.append((e.command, '', e.message, exitCode))
268
269     out = err = ''
270     for i,(cmd, cmd_out,cmd_err,res) in enumerate(results):
271         out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
272         out += 'Command %d Result: %r\n' % (i, res)
273         out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
274         out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
275
276     return out, err, exitCode
277
278 def executeScript(test, litConfig, tmpBase, commands, cwd):
279     bashPath = litConfig.getBashPath();
280     isWin32CMDEXE = (litConfig.isWindows and not bashPath)
281     script = tmpBase + '.script'
282     if isWin32CMDEXE:
283         script += '.bat'
284
285     # Write script file
286     mode = 'w'
287     if litConfig.isWindows and not isWin32CMDEXE:
288       mode += 'b'  # Avoid CRLFs when writing bash scripts.
289     f = open(script, mode)
290     if isWin32CMDEXE:
291         f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
292     else:
293         if test.config.pipefail:
294             f.write('set -o pipefail;')
295         f.write('{ ' + '; } &&\n{ '.join(commands) + '; }')
296     f.write('\n')
297     f.close()
298
299     if isWin32CMDEXE:
300         command = ['cmd','/c', script]
301     else:
302         if bashPath:
303             command = [bashPath, script]
304         else:
305             command = ['/bin/sh', script]
306         if litConfig.useValgrind:
307             # FIXME: Running valgrind on sh is overkill. We probably could just
308             # run on clang with no real loss.
309             command = litConfig.valgrindArgs + command
310
311     return executeCommand(command, cwd=cwd, env=test.config.environment)
312
313 def isExpectedFail(test, xfails):
314     # Check if any of the xfails match an available feature or the target.
315     for item in xfails:
316         # If this is the wildcard, it always fails.
317         if item == '*':
318             return True
319
320         # If this is an exact match for one of the features, it fails.
321         if item in test.config.available_features:
322             return True
323
324         # If this is a part of the target triple, it fails.
325         if item in test.suite.config.target_triple:
326             return True
327
328     return False
329
330 def parseIntegratedTestScript(test, normalize_slashes=False,
331                               extra_substitutions=[]):
332     """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
333     script and extract the lines to 'RUN' as well as 'XFAIL' and 'REQUIRES'
334     information. The RUN lines also will have variable substitution performed.
335     """
336
337     # Get the temporary location, this is always relative to the test suite
338     # root, not test source root.
339     #
340     # FIXME: This should not be here?
341     sourcepath = test.getSourcePath()
342     sourcedir = os.path.dirname(sourcepath)
343     execpath = test.getExecPath()
344     execdir,execbase = os.path.split(execpath)
345     tmpDir = os.path.join(execdir, 'Output')
346     tmpBase = os.path.join(tmpDir, execbase)
347
348     # Normalize slashes, if requested.
349     if normalize_slashes:
350         sourcepath = sourcepath.replace('\\', '/')
351         sourcedir = sourcedir.replace('\\', '/')
352         tmpDir = tmpDir.replace('\\', '/')
353         tmpBase = tmpBase.replace('\\', '/')
354
355     # We use #_MARKER_# to hide %% while we do the other substitutions.
356     substitutions = list(extra_substitutions)
357     substitutions.extend([('%%', '#_MARKER_#')])
358     substitutions.extend(test.config.substitutions)
359     substitutions.extend([('%s', sourcepath),
360                           ('%S', sourcedir),
361                           ('%p', sourcedir),
362                           ('%{pathsep}', os.pathsep),
363                           ('%t', tmpBase + '.tmp'),
364                           ('%T', tmpDir),
365                           ('#_MARKER_#', '%')])
366
367     # Collect the test lines from the script.
368     script = []
369     xfails = []
370     requires = []
371     line_number = 0
372     for ln in open(sourcepath):
373         line_number += 1
374         if 'RUN:' in ln:
375             # Isolate the command to run.
376             index = ln.index('RUN:')
377             ln = ln[index+4:]
378
379             # Trim trailing whitespace.
380             ln = ln.rstrip()
381
382             # Substitute line number expressions
383             ln = re.sub('%\(line\)', str(line_number), ln)
384             def replace_line_number(match):
385                 if match.group(1) == '+':
386                     return str(line_number + int(match.group(2)))
387                 if match.group(1) == '-':
388                     return str(line_number - int(match.group(2)))
389             ln = re.sub('%\(line *([\+-]) *(\d+)\)', replace_line_number, ln)
390
391             # Collapse lines with trailing '\\'.
392             if script and script[-1][-1] == '\\':
393                 script[-1] = script[-1][:-1] + ln
394             else:
395                 script.append(ln)
396         elif 'XFAIL:' in ln:
397             items = ln[ln.index('XFAIL:') + 6:].split(',')
398             xfails.extend([s.strip() for s in items])
399         elif 'REQUIRES:' in ln:
400             items = ln[ln.index('REQUIRES:') + 9:].split(',')
401             requires.extend([s.strip() for s in items])
402         elif 'END.' in ln:
403             # Check for END. lines.
404             if ln[ln.index('END.'):].strip() == 'END.':
405                 break
406
407     # Apply substitutions to the script.  Allow full regular
408     # expression syntax.  Replace each matching occurrence of regular
409     # expression pattern a with substitution b in line ln.
410     def processLine(ln):
411         # Apply substitutions
412         for a,b in substitutions:
413             if kIsWindows:
414                 b = b.replace("\\","\\\\")
415             ln = re.sub(a, b, ln)
416
417         # Strip the trailing newline and any extra whitespace.
418         return ln.strip()
419     script = [processLine(ln)
420               for ln in script]
421
422     # Verify the script contains a run line.
423     if not script:
424         return (Test.UNRESOLVED, "Test has no run line!")
425
426     # Check for unterminated run lines.
427     if script[-1][-1] == '\\':
428         return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
429
430     # Check that we have the required features:
431     missing_required_features = [f for f in requires
432                                  if f not in test.config.available_features]
433     if missing_required_features:
434         msg = ', '.join(missing_required_features)
435         return (Test.UNSUPPORTED,
436                 "Test requires the following features: %s" % msg)
437
438     isXFail = isExpectedFail(test, xfails)
439     return script,isXFail,tmpBase,execdir
440
441 def formatTestOutput(status, out, err, exitCode, script):
442     output = StringIO()
443     output.write(u"Script:\n")
444     output.write(u"--\n")
445     output.write(u'\n'.join(script))
446     output.write(u"\n--\n")
447     output.write(u"Exit Code: %r\n\n" % exitCode)
448     if out:
449         output.write(u"Command Output (stdout):\n")
450         output.write(u"--\n")
451         output.write(unicode(out))
452         output.write(u"--\n")
453     if err:
454         output.write(u"Command Output (stderr):\n")
455         output.write(u"--\n")
456         output.write(unicode(err))
457         output.write(u"--\n")
458     return (status, output.getvalue())
459
460 def executeShTest(test, litConfig, useExternalSh,
461                   extra_substitutions=[]):
462     if test.config.unsupported:
463         return (Test.UNSUPPORTED, 'Test is unsupported')
464
465     res = parseIntegratedTestScript(test, useExternalSh, extra_substitutions)
466     if len(res) == 2:
467         return res
468
469     script, isXFail, tmpBase, execdir = res
470
471     if litConfig.noExecute:
472         return (Test.PASS, '')
473
474     # Create the output directory if it does not already exist.
475     Util.mkdir_p(os.path.dirname(tmpBase))
476
477     if useExternalSh:
478         res = executeScript(test, litConfig, tmpBase, script, execdir)
479     else:
480         res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
481     if len(res) == 2:
482         return res
483
484     out,err,exitCode = res
485     if isXFail:
486         ok = exitCode != 0
487         if ok:
488             status = Test.XFAIL
489         else:
490             status = Test.XPASS
491     else:
492         ok = exitCode == 0
493         if ok:
494             status = Test.PASS
495         else:
496             status = Test.FAIL
497
498     if ok:
499         return (status,'')
500
501     return formatTestOutput(status, out, err, exitCode, script)