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