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