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