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