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 26# Negate if win32file is not found. 27kHaveWin32File = kIsWindows 28 29def RemoveForce(f): 30 try: 31 os.remove(f) 32 except OSError: 33 pass 34 35def 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 76def 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 91def 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 293def 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 317def 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 393def 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 423def 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 440def 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 547def 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 570def 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 617def 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) 662