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