[lit] Make string encoding issues explicit.
[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     if test.index is not None:
348         tmpBase += '_%d' % test.index
349
350     # Normalize slashes, if requested.
351     if normalize_slashes:
352         sourcepath = sourcepath.replace('\\', '/')
353         sourcedir = sourcedir.replace('\\', '/')
354         tmpDir = tmpDir.replace('\\', '/')
355         tmpBase = tmpBase.replace('\\', '/')
356
357     # We use #_MARKER_# to hide %% while we do the other substitutions.
358     substitutions = list(extra_substitutions)
359     substitutions.extend([('%%', '#_MARKER_#')])
360     substitutions.extend(test.config.substitutions)
361     substitutions.extend([('%s', sourcepath),
362                           ('%S', sourcedir),
363                           ('%p', sourcedir),
364                           ('%{pathsep}', os.pathsep),
365                           ('%t', tmpBase + '.tmp'),
366                           ('%T', tmpDir),
367                           ('#_MARKER_#', '%')])
368
369     # Collect the test lines from the script.
370     script = []
371     xfails = []
372     requires = []
373     line_number = 0
374     for ln in open(sourcepath):
375         line_number += 1
376         if 'RUN:' in ln:
377             # Isolate the command to run.
378             index = ln.index('RUN:')
379             ln = ln[index+4:]
380
381             # Trim trailing whitespace.
382             ln = ln.rstrip()
383
384             # Substitute line number expressions
385             ln = re.sub('%\(line\)', str(line_number), ln)
386             def replace_line_number(match):
387                 if match.group(1) == '+':
388                     return str(line_number + int(match.group(2)))
389                 if match.group(1) == '-':
390                     return str(line_number - int(match.group(2)))
391             ln = re.sub('%\(line *([\+-]) *(\d+)\)', replace_line_number, ln)
392
393             # Collapse lines with trailing '\\'.
394             if script and script[-1][-1] == '\\':
395                 script[-1] = script[-1][:-1] + ln
396             else:
397                 script.append(ln)
398         elif 'XFAIL:' in ln:
399             items = ln[ln.index('XFAIL:') + 6:].split(',')
400             xfails.extend([s.strip() for s in items])
401         elif 'REQUIRES:' in ln:
402             items = ln[ln.index('REQUIRES:') + 9:].split(',')
403             requires.extend([s.strip() for s in items])
404         elif 'END.' in ln:
405             # Check for END. lines.
406             if ln[ln.index('END.'):].strip() == 'END.':
407                 break
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 = StringIO()
445     output.write(u"Script:\n")
446     output.write(u"--\n")
447     output.write(u'\n'.join(script))
448     output.write(u"\n--\n")
449     output.write(u"Exit Code: %r\n\n" % exitCode)
450     if out:
451         output.write(u"Command Output (stdout):\n")
452         output.write(u"--\n")
453         output.write(unicode(out))
454         output.write(u"--\n")
455     if err:
456         output.write(u"Command Output (stderr):\n")
457         output.write(u"--\n")
458         output.write(unicode(err))
459         output.write(u"--\n")
460     return (status, output.getvalue())
461
462 def executeShTest(test, litConfig, useExternalSh,
463                   extra_substitutions=[]):
464     if test.config.unsupported:
465         return (Test.UNSUPPORTED, 'Test is unsupported')
466
467     res = parseIntegratedTestScript(test, useExternalSh, extra_substitutions)
468     if len(res) == 2:
469         return res
470
471     script, isXFail, tmpBase, execdir = res
472
473     if litConfig.noExecute:
474         return (Test.PASS, '')
475
476     # Create the output directory if it does not already exist.
477     Util.mkdir_p(os.path.dirname(tmpBase))
478
479     if useExternalSh:
480         res = executeScript(test, litConfig, tmpBase, script, execdir)
481     else:
482         res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
483     if len(res) == 2:
484         return res
485
486     out,err,exitCode = res
487     if isXFail:
488         ok = exitCode != 0
489         if ok:
490             status = Test.XFAIL
491         else:
492             status = Test.XPASS
493     else:
494         ok = exitCode == 0
495         if ok:
496             status = Test.PASS
497         else:
498             status = Test.FAIL
499
500     if ok:
501         return (status,'')
502
503     return formatTestOutput(status, out, err, exitCode, script)