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