Lit: rewind WinWaitReleased() stuff in TestRunner.
[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     p = subprocess.Popen(command, cwd=cwd,
28                          stdin=subprocess.PIPE,
29                          stdout=subprocess.PIPE,
30                          stderr=subprocess.PIPE,
31                          env=env)
32     out,err = p.communicate()
33     exitCode = p.wait()
34
35     # Detect Ctrl-C in subprocess.
36     if exitCode == -signal.SIGINT:
37         raise KeyboardInterrupt
38
39     return out, err, exitCode
40
41 def executeShCmd(cmd, cfg, cwd, results):
42     if isinstance(cmd, ShUtil.Seq):
43         if cmd.op == ';':
44             res = executeShCmd(cmd.lhs, cfg, cwd, results)
45             return executeShCmd(cmd.rhs, cfg, cwd, results)
46
47         if cmd.op == '&':
48             raise NotImplementedError,"unsupported test command: '&'"
49
50         if cmd.op == '||':
51             res = executeShCmd(cmd.lhs, cfg, cwd, results)
52             if res != 0:
53                 res = executeShCmd(cmd.rhs, cfg, cwd, results)
54             return res
55         if cmd.op == '&&':
56             res = executeShCmd(cmd.lhs, cfg, cwd, results)
57             if res is None:
58                 return res
59
60             if res == 0:
61                 res = executeShCmd(cmd.rhs, cfg, cwd, results)
62             return res
63
64         raise ValueError,'Unknown shell command: %r' % cmd.op
65
66     assert isinstance(cmd, ShUtil.Pipeline)
67     procs = []
68     input = subprocess.PIPE
69     stderrTempFiles = []
70     opened_files = []
71     named_temp_files = []
72     # To avoid deadlock, we use a single stderr stream for piped
73     # output. This is null until we have seen some output using
74     # stderr.
75     for i,j in enumerate(cmd.commands):
76         # Apply the redirections, we use (N,) as a sentinal to indicate stdin,
77         # stdout, stderr for N equal to 0, 1, or 2 respectively. Redirects to or
78         # from a file are represented with a list [file, mode, file-object]
79         # where file-object is initially None.
80         redirects = [(0,), (1,), (2,)]
81         for r in j.redirects:
82             if r[0] == ('>',2):
83                 redirects[2] = [r[1], 'w', None]
84             elif r[0] == ('>>',2):
85                 redirects[2] = [r[1], 'a', None]
86             elif r[0] == ('>&',2) and r[1] in '012':
87                 redirects[2] = redirects[int(r[1])]
88             elif r[0] == ('>&',) or r[0] == ('&>',):
89                 redirects[1] = redirects[2] = [r[1], 'w', None]
90             elif r[0] == ('>',):
91                 redirects[1] = [r[1], 'w', None]
92             elif r[0] == ('>>',):
93                 redirects[1] = [r[1], 'a', None]
94             elif r[0] == ('<',):
95                 redirects[0] = [r[1], 'r', None]
96             else:
97                 raise NotImplementedError,"Unsupported redirect: %r" % (r,)
98
99         # Map from the final redirections to something subprocess can handle.
100         final_redirects = []
101         for index,r in enumerate(redirects):
102             if r == (0,):
103                 result = input
104             elif r == (1,):
105                 if index == 0:
106                     raise NotImplementedError,"Unsupported redirect for stdin"
107                 elif index == 1:
108                     result = subprocess.PIPE
109                 else:
110                     result = subprocess.STDOUT
111             elif r == (2,):
112                 if index != 2:
113                     raise NotImplementedError,"Unsupported redirect on stdout"
114                 result = subprocess.PIPE
115             else:
116                 if r[2] is None:
117                     if kAvoidDevNull and r[0] == '/dev/null':
118                         r[2] = tempfile.TemporaryFile(mode=r[1])
119                     else:
120                         r[2] = open(r[0], r[1])
121                     # Workaround a Win32 and/or subprocess bug when appending.
122                     #
123                     # FIXME: Actually, this is probably an instance of PR6753.
124                     if r[1] == 'a':
125                         r[2].seek(0, 2)
126                     opened_files.append(r[2])
127                 result = r[2]
128             final_redirects.append(result)
129
130         stdin, stdout, stderr = final_redirects
131
132         # If stderr wants to come from stdout, but stdout isn't a pipe, then put
133         # stderr on a pipe and treat it as stdout.
134         if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE):
135             stderr = subprocess.PIPE
136             stderrIsStdout = True
137         else:
138             stderrIsStdout = False
139
140             # Don't allow stderr on a PIPE except for the last
141             # process, this could deadlock.
142             #
143             # FIXME: This is slow, but so is deadlock.
144             if stderr == subprocess.PIPE and j != cmd.commands[-1]:
145                 stderr = tempfile.TemporaryFile(mode='w+b')
146                 stderrTempFiles.append((i, stderr))
147
148         # Resolve the executable path ourselves.
149         args = list(j.args)
150         args[0] = Util.which(args[0], cfg.environment['PATH'])
151         if not args[0]:
152             raise InternalShellError(j, '%r: command not found' % j.args[0])
153
154         # Replace uses of /dev/null with temporary files.
155         if kAvoidDevNull:
156             for i,arg in enumerate(args):
157                 if arg == "/dev/null":
158                     f = tempfile.NamedTemporaryFile(delete=False)
159                     f.close()
160                     named_temp_files.append(f.name)
161                     args[i] = f.name
162
163         procs.append(subprocess.Popen(args, cwd=cwd,
164                                       stdin = stdin,
165                                       stdout = stdout,
166                                       stderr = stderr,
167                                       env = cfg.environment,
168                                       close_fds = kUseCloseFDs))
169
170         # Immediately close stdin for any process taking stdin from us.
171         if stdin == subprocess.PIPE:
172             procs[-1].stdin.close()
173             procs[-1].stdin = None
174
175         # Update the current stdin source.
176         if stdout == subprocess.PIPE:
177             input = procs[-1].stdout
178         elif stderrIsStdout:
179             input = procs[-1].stderr
180         else:
181             input = subprocess.PIPE
182
183     # Explicitly close any redirected files. We need to do this now because we
184     # need to release any handles we may have on the temporary files (important
185     # on Win32, for example). Since we have already spawned the subprocess, our
186     # handles have already been transferred so we do not need them anymore.
187     for f in opened_files:
188         f.close()
189
190     # FIXME: There is probably still deadlock potential here. Yawn.
191     procData = [None] * len(procs)
192     procData[-1] = procs[-1].communicate()
193
194     for i in range(len(procs) - 1):
195         if procs[i].stdout is not None:
196             out = procs[i].stdout.read()
197         else:
198             out = ''
199         if procs[i].stderr is not None:
200             err = procs[i].stderr.read()
201         else:
202             err = ''
203         procData[i] = (out,err)
204
205     # Read stderr out of the temp files.
206     for i,f in stderrTempFiles:
207         f.seek(0, 0)
208         procData[i] = (procData[i][0], f.read())
209
210     exitCode = None
211     for i,(out,err) in enumerate(procData):
212         res = procs[i].wait()
213         # Detect Ctrl-C in subprocess.
214         if res == -signal.SIGINT:
215             raise KeyboardInterrupt
216
217         results.append((cmd.commands[i], out, err, res))
218         if cmd.pipe_err:
219             # Python treats the exit code as a signed char.
220             if res < 0:
221                 exitCode = min(exitCode, res)
222             else:
223                 exitCode = max(exitCode, res)
224         else:
225             exitCode = res
226
227     # Remove any named temporary files we created.
228     for f in named_temp_files:
229         try:
230             os.remove(f)
231         except OSError:
232             pass
233
234     if cmd.negate:
235         exitCode = not exitCode
236
237     return exitCode
238
239 def executeScriptInternal(test, litConfig, tmpBase, commands, cwd):
240     ln = ' &&\n'.join(commands)
241     try:
242         cmd = ShUtil.ShParser(ln, litConfig.isWindows).parse()
243     except:
244         return (Test.FAIL, "shell parser error on: %r" % ln)
245
246     results = []
247     try:
248         exitCode = executeShCmd(cmd, test.config, cwd, results)
249     except InternalShellError,e:
250         out = ''
251         err = e.message
252         exitCode = 255
253
254     out = err = ''
255     for i,(cmd, cmd_out,cmd_err,res) in enumerate(results):
256         out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
257         out += 'Command %d Result: %r\n' % (i, res)
258         out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
259         out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
260
261     return out, err, exitCode
262
263 def executeTclScriptInternal(test, litConfig, tmpBase, commands, cwd):
264     import TclUtil
265     cmds = []
266     for ln in commands:
267         # Given the unfortunate way LLVM's test are written, the line gets
268         # backslash substitution done twice.
269         ln = TclUtil.TclLexer(ln).lex_unquoted(process_all = True)
270
271         try:
272             tokens = list(TclUtil.TclLexer(ln).lex())
273         except:
274             return (Test.FAIL, "Tcl lexer error on: %r" % ln)
275
276         # Validate there are no control tokens.
277         for t in tokens:
278             if not isinstance(t, str):
279                 return (Test.FAIL,
280                         "Invalid test line: %r containing %r" % (ln, t))
281
282         try:
283             cmds.append(TclUtil.TclExecCommand(tokens).parse_pipeline())
284         except:
285             return (Test.FAIL, "Tcl 'exec' parse error on: %r" % ln)
286
287     if litConfig.useValgrind:
288         for pipeline in cmds:
289             if pipeline.commands:
290                 # Only valgrind the first command in each pipeline, to avoid
291                 # valgrinding things like grep, not, and FileCheck.
292                 cmd = pipeline.commands[0]
293                 cmd.args = litConfig.valgrindArgs + cmd.args
294
295     cmd = cmds[0]
296     for c in cmds[1:]:
297         cmd = ShUtil.Seq(cmd, '&&', c)
298
299     # FIXME: This is lame, we shouldn't need bash. See PR5240.
300     bashPath = litConfig.getBashPath()
301     if litConfig.useTclAsSh and bashPath:
302         script = tmpBase + '.script'
303
304         # Write script file
305         f = open(script,'w')
306         print >>f, 'set -o pipefail'
307         cmd.toShell(f, pipefail = True)
308         f.close()
309
310         if 0:
311             print >>sys.stdout, cmd
312             print >>sys.stdout, open(script).read()
313             print >>sys.stdout
314             return '', '', 0
315
316         command = [litConfig.getBashPath(), script]
317         out,err,exitCode = executeCommand(command, cwd=cwd,
318                                           env=test.config.environment)
319
320         return out,err,exitCode
321     else:
322         results = []
323         try:
324             exitCode = executeShCmd(cmd, test.config, cwd, results)
325         except InternalShellError,e:
326             results.append((e.command, '', e.message + '\n', 255))
327             exitCode = 255
328
329     out = err = ''
330
331     for i,(cmd, cmd_out, cmd_err, res) in enumerate(results):
332         out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
333         out += 'Command %d Result: %r\n' % (i, res)
334         out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
335         out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
336
337     return out, err, exitCode
338
339 def executeScript(test, litConfig, tmpBase, commands, cwd):
340     bashPath = litConfig.getBashPath();
341     isWin32CMDEXE = (litConfig.isWindows and not bashPath)
342     script = tmpBase + '.script'
343     if isWin32CMDEXE:
344         script += '.bat'
345
346     # Write script file
347     f = open(script,'w')
348     if isWin32CMDEXE:
349         f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
350     else:
351         f.write(' &&\n'.join(commands))
352     f.write('\n')
353     f.close()
354
355     if isWin32CMDEXE:
356         command = ['cmd','/c', script]
357     else:
358         if bashPath:
359             command = [bashPath, script]
360         else:
361             command = ['/bin/sh', script]
362         if litConfig.useValgrind:
363             # FIXME: Running valgrind on sh is overkill. We probably could just
364             # run on clang with no real loss.
365             command = litConfig.valgrindArgs + command
366
367     return executeCommand(command, cwd=cwd, env=test.config.environment)
368
369 def isExpectedFail(xfails, xtargets, target_triple):
370     # Check if any xfail matches this target.
371     for item in xfails:
372         if item == '*' or item in target_triple:
373             break
374     else:
375         return False
376
377     # If so, see if it is expected to pass on this target.
378     #
379     # FIXME: Rename XTARGET to something that makes sense, like XPASS.
380     for item in xtargets:
381         if item == '*' or item in target_triple:
382             return False
383
384     return True
385
386 def parseIntegratedTestScript(test, normalize_slashes=False,
387                               extra_substitutions=[]):
388     """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
389     script and extract the lines to 'RUN' as well as 'XFAIL' and 'XTARGET'
390     information. The RUN lines also will have variable substitution performed.
391     """
392
393     # Get the temporary location, this is always relative to the test suite
394     # root, not test source root.
395     #
396     # FIXME: This should not be here?
397     sourcepath = test.getSourcePath()
398     sourcedir = os.path.dirname(sourcepath)
399     execpath = test.getExecPath()
400     execdir,execbase = os.path.split(execpath)
401     tmpDir = os.path.join(execdir, 'Output')
402     tmpBase = os.path.join(tmpDir, execbase)
403     if test.index is not None:
404         tmpBase += '_%d' % test.index
405
406     # Normalize slashes, if requested.
407     if normalize_slashes:
408         sourcepath = sourcepath.replace('\\', '/')
409         sourcedir = sourcedir.replace('\\', '/')
410         tmpDir = tmpDir.replace('\\', '/')
411         tmpBase = tmpBase.replace('\\', '/')
412
413     # We use #_MARKER_# to hide %% while we do the other substitutions.
414     substitutions = list(extra_substitutions)
415     substitutions.extend([('%%', '#_MARKER_#')])
416     substitutions.extend(test.config.substitutions)
417     substitutions.extend([('%s', sourcepath),
418                           ('%S', sourcedir),
419                           ('%p', sourcedir),
420                           ('%{pathsep}', os.pathsep),
421                           ('%t', tmpBase + '.tmp'),
422                           ('%T', tmpDir),
423                           # FIXME: Remove this once we kill DejaGNU.
424                           ('%abs_tmp', tmpBase + '.tmp'),
425                           ('#_MARKER_#', '%')])
426
427     # Collect the test lines from the script.
428     script = []
429     xfails = []
430     xtargets = []
431     requires = []
432     for ln in open(sourcepath):
433         if 'RUN:' in ln:
434             # Isolate the command to run.
435             index = ln.index('RUN:')
436             ln = ln[index+4:]
437
438             # Trim trailing whitespace.
439             ln = ln.rstrip()
440
441             # Collapse lines with trailing '\\'.
442             if script and script[-1][-1] == '\\':
443                 script[-1] = script[-1][:-1] + ln
444             else:
445                 script.append(ln)
446         elif 'XFAIL:' in ln:
447             items = ln[ln.index('XFAIL:') + 6:].split(',')
448             xfails.extend([s.strip() for s in items])
449         elif 'XTARGET:' in ln:
450             items = ln[ln.index('XTARGET:') + 8:].split(',')
451             xtargets.extend([s.strip() for s in items])
452         elif 'REQUIRES:' in ln:
453             items = ln[ln.index('REQUIRES:') + 9:].split(',')
454             requires.extend([s.strip() for s in items])
455         elif 'END.' in ln:
456             # Check for END. lines.
457             if ln[ln.index('END.'):].strip() == 'END.':
458                 break
459
460     # Apply substitutions to the script.  Allow full regular
461     # expression syntax.  Replace each matching occurrence of regular
462     # expression pattern a with substitution b in line ln.
463     def processLine(ln):
464         # Apply substitutions
465         for a,b in substitutions:
466             if kIsWindows:
467                 b = b.replace("\\","\\\\")
468             ln = re.sub(a, b, ln)
469
470         # Strip the trailing newline and any extra whitespace.
471         return ln.strip()
472     script = map(processLine, script)
473
474     # Verify the script contains a run line.
475     if not script:
476         return (Test.UNRESOLVED, "Test has no run line!")
477
478     # Check for unterminated run lines.
479     if script[-1][-1] == '\\':
480         return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
481
482     # Check that we have the required features:
483     missing_required_features = [f for f in requires
484                                  if f not in test.config.available_features]
485     if missing_required_features:
486         msg = ', '.join(missing_required_features)
487         return (Test.UNSUPPORTED,
488                 "Test requires the following features: %s" % msg)
489
490     isXFail = isExpectedFail(xfails, xtargets, test.suite.config.target_triple)
491     return script,isXFail,tmpBase,execdir
492
493 def formatTestOutput(status, out, err, exitCode, failDueToStderr, script):
494     output = StringIO.StringIO()
495     print >>output, "Script:"
496     print >>output, "--"
497     print >>output, '\n'.join(script)
498     print >>output, "--"
499     print >>output, "Exit Code: %r" % exitCode,
500     if failDueToStderr:
501         print >>output, "(but there was output on stderr)"
502     else:
503         print >>output
504     if out:
505         print >>output, "Command Output (stdout):"
506         print >>output, "--"
507         output.write(out)
508         print >>output, "--"
509     if err:
510         print >>output, "Command Output (stderr):"
511         print >>output, "--"
512         output.write(err)
513         print >>output, "--"
514     return (status, output.getvalue())
515
516 def executeTclTest(test, litConfig):
517     if test.config.unsupported:
518         return (Test.UNSUPPORTED, 'Test is unsupported')
519
520     # Parse the test script, normalizing slashes in substitutions on Windows
521     # (since otherwise Tcl style lexing will treat them as escapes).
522     res = parseIntegratedTestScript(test, normalize_slashes=kIsWindows)
523     if len(res) == 2:
524         return res
525
526     script, isXFail, tmpBase, execdir = res
527
528     if litConfig.noExecute:
529         return (Test.PASS, '')
530
531     # Create the output directory if it does not already exist.
532     Util.mkdir_p(os.path.dirname(tmpBase))
533
534     res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
535     if len(res) == 2:
536         return res
537
538     # Test for failure. In addition to the exit code, Tcl commands are
539     # considered to fail if there is any standard error output.
540     out,err,exitCode = res
541     if isXFail:
542         ok = exitCode != 0 or err and not litConfig.ignoreStdErr
543         if ok:
544             status = Test.XFAIL
545         else:
546             status = Test.XPASS
547     else:
548         ok = exitCode == 0 and (not err or litConfig.ignoreStdErr)
549         if ok:
550             status = Test.PASS
551         else:
552             status = Test.FAIL
553
554     if ok:
555         return (status,'')
556
557     # Set a flag for formatTestOutput so it can explain why the test was
558     # considered to have failed, despite having an exit code of 0.
559     failDueToStderr = exitCode == 0 and err and not litConfig.ignoreStdErr
560
561     return formatTestOutput(status, out, err, exitCode, failDueToStderr, script)
562
563 def executeShTest(test, litConfig, useExternalSh,
564                   extra_substitutions=[]):
565     if test.config.unsupported:
566         return (Test.UNSUPPORTED, 'Test is unsupported')
567
568     res = parseIntegratedTestScript(test, useExternalSh, extra_substitutions)
569     if len(res) == 2:
570         return res
571
572     script, isXFail, tmpBase, execdir = res
573
574     if litConfig.noExecute:
575         return (Test.PASS, '')
576
577     # Create the output directory if it does not already exist.
578     Util.mkdir_p(os.path.dirname(tmpBase))
579
580     if useExternalSh:
581         res = executeScript(test, litConfig, tmpBase, script, execdir)
582     else:
583         res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
584     if len(res) == 2:
585         return res
586
587     out,err,exitCode = res
588     if isXFail:
589         ok = exitCode != 0
590         if ok:
591             status = Test.XFAIL
592         else:
593             status = Test.XPASS
594     else:
595         ok = exitCode == 0
596         if ok:
597             status = Test.PASS
598         else:
599             status = Test.FAIL
600
601     if ok:
602         return (status,'')
603
604     # Sh tests are not considered to fail just from stderr output.
605     failDueToStderr = False
606
607     return formatTestOutput(status, out, err, exitCode, failDueToStderr, script)