mirror of
				https://github.com/c64scene-ar/llvm-6502.git
				synced 2025-10-31 08:16:47 +00:00 
			
		
		
		
	commands except the last one, instead redirect the stderr to a temporary file. This sidesteps a potential deadlocking issue. git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@82538 91177308-0d34-0410-b5e6-96231b3b80d8
		
			
				
	
	
		
			506 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			506 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import os, signal, subprocess, sys
 | |
| import StringIO
 | |
| 
 | |
| import ShUtil
 | |
| import Test
 | |
| import Util
 | |
| 
 | |
| import platform
 | |
| import tempfile
 | |
| 
 | |
| class InternalShellError(Exception):
 | |
|     def __init__(self, command, message):
 | |
|         self.command = command
 | |
|         self.message = message
 | |
| 
 | |
| # Don't use close_fds on Windows.
 | |
| kUseCloseFDs = platform.system() != 'Windows'
 | |
| def executeCommand(command, cwd=None, env=None):
 | |
|     p = subprocess.Popen(command, cwd=cwd,
 | |
|                          stdin=subprocess.PIPE,
 | |
|                          stdout=subprocess.PIPE,
 | |
|                          stderr=subprocess.PIPE,
 | |
|                          env=env)
 | |
|     out,err = p.communicate()
 | |
|     exitCode = p.wait()
 | |
| 
 | |
|     # Detect Ctrl-C in subprocess.
 | |
|     if exitCode == -signal.SIGINT:
 | |
|         raise KeyboardInterrupt
 | |
| 
 | |
|     return out, err, exitCode
 | |
| 
 | |
| def executeShCmd(cmd, cfg, cwd, results):
 | |
|     if isinstance(cmd, ShUtil.Seq):
 | |
|         if cmd.op == ';':
 | |
|             res = executeShCmd(cmd.lhs, cfg, cwd, results)
 | |
|             return executeShCmd(cmd.rhs, cfg, cwd, results)
 | |
| 
 | |
|         if cmd.op == '&':
 | |
|             raise NotImplementedError,"unsupported test command: '&'"
 | |
| 
 | |
|         if cmd.op == '||':
 | |
|             res = executeShCmd(cmd.lhs, cfg, cwd, results)
 | |
|             if res != 0:
 | |
|                 res = executeShCmd(cmd.rhs, cfg, cwd, results)
 | |
|             return res
 | |
|         if cmd.op == '&&':
 | |
|             res = executeShCmd(cmd.lhs, cfg, cwd, results)
 | |
|             if res is None:
 | |
|                 return res
 | |
| 
 | |
|             if res == 0:
 | |
|                 res = executeShCmd(cmd.rhs, cfg, cwd, results)
 | |
|             return res
 | |
| 
 | |
|         raise ValueError,'Unknown shell command: %r' % cmd.op
 | |
| 
 | |
|     assert isinstance(cmd, ShUtil.Pipeline)
 | |
|     procs = []
 | |
|     input = subprocess.PIPE
 | |
|     stderrTempFiles = []
 | |
|     # To avoid deadlock, we use a single stderr stream for piped
 | |
|     # output. This is null until we have seen some output using
 | |
|     # stderr.
 | |
|     for i,j in enumerate(cmd.commands):
 | |
|         redirects = [(0,), (1,), (2,)]
 | |
|         for r in j.redirects:
 | |
|             if r[0] == ('>',2):
 | |
|                 redirects[2] = [r[1], 'w', None]
 | |
|             elif r[0] == ('>&',2) and r[1] in '012':
 | |
|                 redirects[2] = redirects[int(r[1])]
 | |
|             elif r[0] == ('>&',) or r[0] == ('&>',):
 | |
|                 redirects[1] = redirects[2] = [r[1], 'w', None]
 | |
|             elif r[0] == ('>',):
 | |
|                 redirects[1] = [r[1], 'w', None]
 | |
|             elif r[0] == ('<',):
 | |
|                 redirects[0] = [r[1], 'r', None]
 | |
|             else:
 | |
|                 raise NotImplementedError,"Unsupported redirect: %r" % (r,)
 | |
| 
 | |
|         final_redirects = []
 | |
|         for index,r in enumerate(redirects):
 | |
|             if r == (0,):
 | |
|                 result = input
 | |
|             elif r == (1,):
 | |
|                 if index == 0:
 | |
|                     raise NotImplementedError,"Unsupported redirect for stdin"
 | |
|                 elif index == 1:
 | |
|                     result = subprocess.PIPE
 | |
|                 else:
 | |
|                     result = subprocess.STDOUT
 | |
|             elif r == (2,):
 | |
|                 if index != 2:
 | |
|                     raise NotImplementedError,"Unsupported redirect on stdout"
 | |
|                 result = subprocess.PIPE
 | |
|             else:
 | |
|                 if r[2] is None:
 | |
|                     r[2] = open(r[0], r[1])
 | |
|                 result = r[2]
 | |
|             final_redirects.append(result)
 | |
| 
 | |
|         stdin, stdout, stderr = final_redirects
 | |
| 
 | |
|         # If stderr wants to come from stdout, but stdout isn't a pipe, then put
 | |
|         # stderr on a pipe and treat it as stdout.
 | |
|         if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE):
 | |
|             stderr = subprocess.PIPE
 | |
|             stderrIsStdout = True
 | |
|         else:
 | |
|             stderrIsStdout = False
 | |
| 
 | |
|             # Don't allow stderr on a PIPE except for the last
 | |
|             # process, this could deadlock.
 | |
|             #
 | |
|             # FIXME: This is slow, but so is deadlock.
 | |
|             if stderr == subprocess.PIPE and j != cmd.commands[-1]:
 | |
|                 stderr = tempfile.TemporaryFile(mode='w+b')
 | |
|                 stderrTempFiles.append((i, stderr))
 | |
| 
 | |
|         # Resolve the executable path ourselves.
 | |
|         args = list(j.args)
 | |
|         args[0] = Util.which(args[0], cfg.environment['PATH'])
 | |
|         if not args[0]:
 | |
|             raise InternalShellError(j, '%r: command not found' % j.args[0])
 | |
| 
 | |
|         procs.append(subprocess.Popen(args, cwd=cwd,
 | |
|                                       stdin = stdin,
 | |
|                                       stdout = stdout,
 | |
|                                       stderr = stderr,
 | |
|                                       env = cfg.environment,
 | |
|                                       close_fds = kUseCloseFDs))
 | |
| 
 | |
|         # Immediately close stdin for any process taking stdin from us.
 | |
|         if stdin == subprocess.PIPE:
 | |
|             procs[-1].stdin.close()
 | |
|             procs[-1].stdin = None
 | |
| 
 | |
|         # Update the current stdin source.
 | |
|         if stdout == subprocess.PIPE:
 | |
|             input = procs[-1].stdout
 | |
|         elif stderrIsStdout:
 | |
|             input = procs[-1].stderr
 | |
|         else:
 | |
|             input = subprocess.PIPE
 | |
| 
 | |
|     # FIXME: There is probably still deadlock potential here. Yawn.
 | |
|     procData = [None] * len(procs)
 | |
|     procData[-1] = procs[-1].communicate()
 | |
| 
 | |
|     for i in range(len(procs) - 1):
 | |
|         if procs[i].stdout is not None:
 | |
|             out = procs[i].stdout.read()
 | |
|         else:
 | |
|             out = ''
 | |
|         if procs[i].stderr is not None:
 | |
|             err = procs[i].stderr.read()
 | |
|         else:
 | |
|             err = ''
 | |
|         procData[i] = (out,err)
 | |
|         
 | |
|     # Read stderr out of the temp files.
 | |
|     for i,f in stderrTempFiles:
 | |
|         f.seek(0, 0)
 | |
|         procData[i] = (procData[i][0], f.read())
 | |
| 
 | |
|     exitCode = None
 | |
|     for i,(out,err) in enumerate(procData):
 | |
|         res = procs[i].wait()
 | |
|         # Detect Ctrl-C in subprocess.
 | |
|         if res == -signal.SIGINT:
 | |
|             raise KeyboardInterrupt
 | |
| 
 | |
|         results.append((cmd.commands[i], out, err, res))
 | |
|         if cmd.pipe_err:
 | |
|             # Python treats the exit code as a signed char.
 | |
|             if res < 0:
 | |
|                 exitCode = min(exitCode, res)
 | |
|             else:
 | |
|                 exitCode = max(exitCode, res)
 | |
|         else:
 | |
|             exitCode = res
 | |
| 
 | |
|     if cmd.negate:
 | |
|         exitCode = not exitCode
 | |
| 
 | |
|     return exitCode
 | |
| 
 | |
| def executeScriptInternal(test, litConfig, tmpBase, commands, cwd):
 | |
|     ln = ' &&\n'.join(commands)
 | |
|     try:
 | |
|         cmd = ShUtil.ShParser(ln, litConfig.isWindows).parse()
 | |
|     except:
 | |
|         return (Test.FAIL, "shell parser error on: %r" % ln)
 | |
| 
 | |
|     results = []
 | |
|     try:
 | |
|         exitCode = executeShCmd(cmd, test.config, cwd, results)
 | |
|     except InternalShellError,e:
 | |
|         out = ''
 | |
|         err = e.message
 | |
|         exitCode = 255
 | |
| 
 | |
|     out = err = ''
 | |
|     for i,(cmd, cmd_out,cmd_err,res) in enumerate(results):
 | |
|         out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
 | |
|         out += 'Command %d Result: %r\n' % (i, res)
 | |
|         out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
 | |
|         out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
 | |
| 
 | |
|     return out, err, exitCode
 | |
| 
 | |
| def executeTclScriptInternal(test, litConfig, tmpBase, commands, cwd):
 | |
|     import TclUtil
 | |
|     cmds = []
 | |
|     for ln in commands:
 | |
|         # Given the unfortunate way LLVM's test are written, the line gets
 | |
|         # backslash substitution done twice.
 | |
|         ln = TclUtil.TclLexer(ln).lex_unquoted(process_all = True)
 | |
| 
 | |
|         try:
 | |
|             tokens = list(TclUtil.TclLexer(ln).lex())
 | |
|         except:
 | |
|             return (Test.FAIL, "Tcl lexer error on: %r" % ln)
 | |
| 
 | |
|         # Validate there are no control tokens.
 | |
|         for t in tokens:
 | |
|             if not isinstance(t, str):
 | |
|                 return (Test.FAIL,
 | |
|                         "Invalid test line: %r containing %r" % (ln, t))
 | |
| 
 | |
|         try:
 | |
|             cmds.append(TclUtil.TclExecCommand(tokens).parse_pipeline())
 | |
|         except:
 | |
|             return (Test.FAIL, "Tcl 'exec' parse error on: %r" % ln)
 | |
| 
 | |
|     cmd = cmds[0]
 | |
|     for c in cmds[1:]:
 | |
|         cmd = ShUtil.Seq(cmd, '&&', c)
 | |
| 
 | |
|     if litConfig.useTclAsSh:
 | |
|         script = tmpBase + '.script'
 | |
| 
 | |
|         # Write script file
 | |
|         f = open(script,'w')
 | |
|         print >>f, 'set -o pipefail'
 | |
|         cmd.toShell(f, pipefail = True)
 | |
|         f.close()
 | |
| 
 | |
|         if 0:
 | |
|             print >>sys.stdout, cmd
 | |
|             print >>sys.stdout, open(script).read()
 | |
|             print >>sys.stdout
 | |
|             return '', '', 0
 | |
| 
 | |
|         command = ['/bin/bash', script]
 | |
|         out,err,exitCode = executeCommand(command, cwd=cwd,
 | |
|                                           env=test.config.environment)
 | |
| 
 | |
|         # Tcl commands fail on standard error output.
 | |
|         if err:
 | |
|             exitCode = 1
 | |
|             out = 'Command has output on stderr!\n\n' + out
 | |
| 
 | |
|         return out,err,exitCode
 | |
|     else:
 | |
|         results = []
 | |
|         try:
 | |
|             exitCode = executeShCmd(cmd, test.config, cwd, results)
 | |
|         except InternalShellError,e:
 | |
|             results.append((e.command, '', e.message + '\n', 255))
 | |
|             exitCode = 255
 | |
| 
 | |
|     out = err = ''
 | |
| 
 | |
|     # Tcl commands fail on standard error output.
 | |
|     if [True for _,_,err,res in results if err]:
 | |
|         exitCode = 1
 | |
|         out += 'Command has output on stderr!\n\n'
 | |
| 
 | |
|     for i,(cmd, cmd_out, cmd_err, res) in enumerate(results):
 | |
|         out += 'Command %d: %s\n' % (i, ' '.join('"%s"' % s for s in cmd.args))
 | |
|         out += 'Command %d Result: %r\n' % (i, res)
 | |
|         out += 'Command %d Output:\n%s\n\n' % (i, cmd_out)
 | |
|         out += 'Command %d Stderr:\n%s\n\n' % (i, cmd_err)
 | |
| 
 | |
|     return out, err, exitCode
 | |
| 
 | |
| def executeScript(test, litConfig, tmpBase, commands, cwd):
 | |
|     script = tmpBase + '.script'
 | |
|     if litConfig.isWindows:
 | |
|         script += '.bat'
 | |
| 
 | |
|     # Write script file
 | |
|     f = open(script,'w')
 | |
|     if litConfig.isWindows:
 | |
|         f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands))
 | |
|     else:
 | |
|         f.write(' &&\n'.join(commands))
 | |
|     f.write('\n')
 | |
|     f.close()
 | |
| 
 | |
|     if litConfig.isWindows:
 | |
|         command = ['cmd','/c', script]
 | |
|     else:
 | |
|         command = ['/bin/sh', script]
 | |
|         if litConfig.useValgrind:
 | |
|             # FIXME: Running valgrind on sh is overkill. We probably could just
 | |
|             # run on clang with no real loss.
 | |
|             valgrindArgs = ['valgrind', '-q',
 | |
|                             '--tool=memcheck', '--trace-children=yes',
 | |
|                             '--error-exitcode=123']
 | |
|             valgrindArgs.extend(litConfig.valgrindArgs)
 | |
| 
 | |
|             command = valgrindArgs + command
 | |
| 
 | |
|     return executeCommand(command, cwd=cwd, env=test.config.environment)
 | |
| 
 | |
| def parseIntegratedTestScript(test, xfailHasColon, requireAndAnd):
 | |
|     """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
 | |
|     script and extract the lines to 'RUN' as well as 'XFAIL' and 'XTARGET'
 | |
|     information. The RUN lines also will have variable substitution performed.
 | |
|     """
 | |
| 
 | |
|     # Get the temporary location, this is always relative to the test suite
 | |
|     # root, not test source root.
 | |
|     #
 | |
|     # FIXME: This should not be here?
 | |
|     sourcepath = test.getSourcePath()
 | |
|     execpath = test.getExecPath()
 | |
|     execdir,execbase = os.path.split(execpath)
 | |
|     tmpBase = os.path.join(execdir, 'Output', execbase)
 | |
| 
 | |
|     # We use #_MARKER_# to hide %% while we do the other substitutions.
 | |
|     substitutions = [('%%', '#_MARKER_#')]
 | |
|     substitutions.extend(test.config.substitutions)
 | |
|     substitutions.extend([('%s', sourcepath),
 | |
|                           ('%S', os.path.dirname(sourcepath)),
 | |
|                           ('%p', os.path.dirname(sourcepath)),
 | |
|                           ('%t', tmpBase + '.tmp'),
 | |
|                           # FIXME: Remove this once we kill DejaGNU.
 | |
|                           ('%abs_tmp', tmpBase + '.tmp'),
 | |
|                           ('#_MARKER_#', '%')])
 | |
| 
 | |
|     # Collect the test lines from the script.
 | |
|     script = []
 | |
|     xfails = []
 | |
|     xtargets = []
 | |
|     for ln in open(sourcepath):
 | |
|         if 'RUN:' in ln:
 | |
|             # Isolate the command to run.
 | |
|             index = ln.index('RUN:')
 | |
|             ln = ln[index+4:]
 | |
| 
 | |
|             # Trim trailing whitespace.
 | |
|             ln = ln.rstrip()
 | |
| 
 | |
|             # Collapse lines with trailing '\\'.
 | |
|             if script and script[-1][-1] == '\\':
 | |
|                 script[-1] = script[-1][:-1] + ln
 | |
|             else:
 | |
|                 script.append(ln)
 | |
|         elif xfailHasColon and 'XFAIL:' in ln:
 | |
|             items = ln[ln.index('XFAIL:') + 6:].split(',')
 | |
|             xfails.extend([s.strip() for s in items])
 | |
|         elif not xfailHasColon and 'XFAIL' in ln:
 | |
|             items = ln[ln.index('XFAIL') + 5:].split(',')
 | |
|             xfails.extend([s.strip() for s in items])
 | |
|         elif 'XTARGET:' in ln:
 | |
|             items = ln[ln.index('XTARGET:') + 8:].split(',')
 | |
|             xtargets.extend([s.strip() for s in items])
 | |
|         elif 'END.' in ln:
 | |
|             # Check for END. lines.
 | |
|             if ln[ln.index('END.'):].strip() == 'END.':
 | |
|                 break
 | |
| 
 | |
|     # Apply substitutions to the script.
 | |
|     def processLine(ln):
 | |
|         # Apply substitutions
 | |
|         for a,b in substitutions:
 | |
|             ln = ln.replace(a,b)
 | |
| 
 | |
|         # Strip the trailing newline and any extra whitespace.
 | |
|         return ln.strip()
 | |
|     script = map(processLine, script)
 | |
| 
 | |
|     # Verify the script contains a run line.
 | |
|     if not script:
 | |
|         return (Test.UNRESOLVED, "Test has no run line!")
 | |
| 
 | |
|     if script[-1][-1] == '\\':
 | |
|         return (Test.UNRESOLVED, "Test has unterminated run lines (with '\\')")
 | |
| 
 | |
|     # Validate interior lines for '&&', a lovely historical artifact.
 | |
|     if requireAndAnd:
 | |
|         for i in range(len(script) - 1):
 | |
|             ln = script[i]
 | |
| 
 | |
|             if not ln.endswith('&&'):
 | |
|                 return (Test.FAIL,
 | |
|                         ("MISSING \'&&\': %s\n"  +
 | |
|                          "FOLLOWED BY   : %s\n") % (ln, script[i + 1]))
 | |
| 
 | |
|             # Strip off '&&'
 | |
|             script[i] = ln[:-2]
 | |
| 
 | |
|     return script,xfails,xtargets,tmpBase,execdir
 | |
| 
 | |
| def formatTestOutput(status, out, err, exitCode, script):
 | |
|     output = StringIO.StringIO()
 | |
|     print >>output, "Script:"
 | |
|     print >>output, "--"
 | |
|     print >>output, '\n'.join(script)
 | |
|     print >>output, "--"
 | |
|     print >>output, "Exit Code: %r" % exitCode
 | |
|     print >>output, "Command Output (stdout):"
 | |
|     print >>output, "--"
 | |
|     output.write(out)
 | |
|     print >>output, "--"
 | |
|     print >>output, "Command Output (stderr):"
 | |
|     print >>output, "--"
 | |
|     output.write(err)
 | |
|     print >>output, "--"
 | |
|     return (status, output.getvalue())
 | |
| 
 | |
| def executeTclTest(test, litConfig):
 | |
|     if test.config.unsupported:
 | |
|         return (Test.UNSUPPORTED, 'Test is unsupported')
 | |
| 
 | |
|     res = parseIntegratedTestScript(test, True, False)
 | |
|     if len(res) == 2:
 | |
|         return res
 | |
| 
 | |
|     script, xfails, xtargets, tmpBase, execdir = res
 | |
| 
 | |
|     if litConfig.noExecute:
 | |
|         return (Test.PASS, '')
 | |
| 
 | |
|     # Create the output directory if it does not already exist.
 | |
|     Util.mkdir_p(os.path.dirname(tmpBase))
 | |
| 
 | |
|     res = executeTclScriptInternal(test, litConfig, tmpBase, script, execdir)
 | |
|     if len(res) == 2:
 | |
|         return res
 | |
| 
 | |
|     isXFail = False
 | |
|     for item in xfails:
 | |
|         if item == '*' or item in test.suite.config.target_triple:
 | |
|             isXFail = True
 | |
|             break
 | |
| 
 | |
|     # If this is XFAIL, see if it is expected to pass on this target.
 | |
|     if isXFail:
 | |
|         for item in xtargets:
 | |
|             if item == '*' or item in test.suite.config.target_triple:
 | |
|                 isXFail = False
 | |
|                 break
 | |
| 
 | |
|     out,err,exitCode = res
 | |
|     if isXFail:
 | |
|         ok = exitCode != 0
 | |
|         status = (Test.XPASS, Test.XFAIL)[ok]
 | |
|     else:
 | |
|         ok = exitCode == 0
 | |
|         status = (Test.FAIL, Test.PASS)[ok]
 | |
| 
 | |
|     if ok:
 | |
|         return (status,'')
 | |
| 
 | |
|     return formatTestOutput(status, out, err, exitCode, script)
 | |
| 
 | |
| def executeShTest(test, litConfig, useExternalSh, requireAndAnd):
 | |
|     if test.config.unsupported:
 | |
|         return (Test.UNSUPPORTED, 'Test is unsupported')
 | |
| 
 | |
|     res = parseIntegratedTestScript(test, False, requireAndAnd)
 | |
|     if len(res) == 2:
 | |
|         return res
 | |
| 
 | |
|     script, xfails, xtargets, tmpBase, execdir = res
 | |
| 
 | |
|     if litConfig.noExecute:
 | |
|         return (Test.PASS, '')
 | |
| 
 | |
|     # Create the output directory if it does not already exist.
 | |
|     Util.mkdir_p(os.path.dirname(tmpBase))
 | |
| 
 | |
|     if useExternalSh:
 | |
|         res = executeScript(test, litConfig, tmpBase, script, execdir)
 | |
|     else:
 | |
|         res = executeScriptInternal(test, litConfig, tmpBase, script, execdir)
 | |
|     if len(res) == 2:
 | |
|         return res
 | |
| 
 | |
|     out,err,exitCode = res
 | |
|     if xfails:
 | |
|         ok = exitCode != 0
 | |
|         status = (Test.XPASS, Test.XFAIL)[ok]
 | |
|     else:
 | |
|         ok = exitCode == 0
 | |
|         status = (Test.FAIL, Test.PASS)[ok]
 | |
| 
 | |
|     if ok:
 | |
|         return (status,'')
 | |
| 
 | |
|     return formatTestOutput(status, out, err, exitCode, script)
 |