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