1from __future__ import absolute_import 2import errno 3import io 4import itertools 5import getopt 6import os, signal, subprocess, sys 7import re 8import stat 9import pathlib 10import platform 11import shutil 12import tempfile 13import threading 14 15import io 16try: 17 from StringIO import StringIO 18except ImportError: 19 from io import StringIO 20 21from lit.ShCommands import GlobItem, Command 22import lit.ShUtil as ShUtil 23import lit.Test as Test 24import lit.util 25from lit.util import to_bytes, to_string, to_unicode 26from lit.BooleanExpression import BooleanExpression 27 28class InternalShellError(Exception): 29 def __init__(self, command, message): 30 self.command = command 31 self.message = message 32 33kIsWindows = platform.system() == 'Windows' 34 35# Don't use close_fds on Windows. 36kUseCloseFDs = not kIsWindows 37 38# Use temporary files to replace /dev/null on Windows. 39kAvoidDevNull = kIsWindows 40kDevNull = "/dev/null" 41 42# A regex that matches %dbg(ARG), which lit inserts at the beginning of each 43# run command pipeline such that ARG specifies the pipeline's source line 44# number. lit later expands each %dbg(ARG) to a command that behaves as a null 45# command in the target shell so that the line number is seen in lit's verbose 46# mode. 47# 48# This regex captures ARG. ARG must not contain a right parenthesis, which 49# terminates %dbg. ARG must not contain quotes, in which ARG might be enclosed 50# during expansion. 51# 52# COMMAND that follows %dbg(ARG) is also captured. COMMAND can be 53# empty as a result of conditinal substitution. 54kPdbgRegex = '%dbg\\(([^)\'"]*)\\)(.*)' 55 56class ShellEnvironment(object): 57 58 """Mutable shell environment containing things like CWD and env vars. 59 60 Environment variables are not implemented, but cwd tracking is. In addition, 61 we maintain a dir stack for pushd/popd. 62 """ 63 64 def __init__(self, cwd, env): 65 self.cwd = cwd 66 self.env = dict(env) 67 self.dirStack = [] 68 69 def change_dir(self, newdir): 70 if os.path.isabs(newdir): 71 self.cwd = newdir 72 else: 73 self.cwd = os.path.realpath(os.path.join(self.cwd, newdir)) 74 75class TimeoutHelper(object): 76 """ 77 Object used to helper manage enforcing a timeout in 78 _executeShCmd(). It is passed through recursive calls 79 to collect processes that have been executed so that when 80 the timeout happens they can be killed. 81 """ 82 def __init__(self, timeout): 83 self.timeout = timeout 84 self._procs = [] 85 self._timeoutReached = False 86 self._doneKillPass = False 87 # This lock will be used to protect concurrent access 88 # to _procs and _doneKillPass 89 self._lock = None 90 self._timer = None 91 92 def cancel(self): 93 if not self.active(): 94 return 95 self._timer.cancel() 96 97 def active(self): 98 return self.timeout > 0 99 100 def addProcess(self, proc): 101 if not self.active(): 102 return 103 needToRunKill = False 104 with self._lock: 105 self._procs.append(proc) 106 # Avoid re-entering the lock by finding out if kill needs to be run 107 # again here but call it if necessary once we have left the lock. 108 # We could use a reentrant lock here instead but this code seems 109 # clearer to me. 110 needToRunKill = self._doneKillPass 111 112 # The initial call to _kill() from the timer thread already happened so 113 # we need to call it again from this thread, otherwise this process 114 # will be left to run even though the timeout was already hit 115 if needToRunKill: 116 assert self.timeoutReached() 117 self._kill() 118 119 def startTimer(self): 120 if not self.active(): 121 return 122 123 # Do some late initialisation that's only needed 124 # if there is a timeout set 125 self._lock = threading.Lock() 126 self._timer = threading.Timer(self.timeout, self._handleTimeoutReached) 127 self._timer.start() 128 129 def _handleTimeoutReached(self): 130 self._timeoutReached = True 131 self._kill() 132 133 def timeoutReached(self): 134 return self._timeoutReached 135 136 def _kill(self): 137 """ 138 This method may be called multiple times as we might get unlucky 139 and be in the middle of creating a new process in _executeShCmd() 140 which won't yet be in ``self._procs``. By locking here and in 141 addProcess() we should be able to kill processes launched after 142 the initial call to _kill() 143 """ 144 with self._lock: 145 for p in self._procs: 146 lit.util.killProcessAndChildren(p.pid) 147 # Empty the list and note that we've done a pass over the list 148 self._procs = [] # Python2 doesn't have list.clear() 149 self._doneKillPass = True 150 151class ShellCommandResult(object): 152 """Captures the result of an individual command.""" 153 154 def __init__(self, command, stdout, stderr, exitCode, timeoutReached, 155 outputFiles = []): 156 self.command = command 157 self.stdout = stdout 158 self.stderr = stderr 159 self.exitCode = exitCode 160 self.timeoutReached = timeoutReached 161 self.outputFiles = list(outputFiles) 162 163def executeShCmd(cmd, shenv, results, timeout=0): 164 """ 165 Wrapper around _executeShCmd that handles 166 timeout 167 """ 168 # Use the helper even when no timeout is required to make 169 # other code simpler (i.e. avoid bunch of ``!= None`` checks) 170 timeoutHelper = TimeoutHelper(timeout) 171 if timeout > 0: 172 timeoutHelper.startTimer() 173 finalExitCode = _executeShCmd(cmd, shenv, results, timeoutHelper) 174 timeoutHelper.cancel() 175 timeoutInfo = None 176 if timeoutHelper.timeoutReached(): 177 timeoutInfo = 'Reached timeout of {} seconds'.format(timeout) 178 179 return (finalExitCode, timeoutInfo) 180 181def expand_glob(arg, cwd): 182 if isinstance(arg, GlobItem): 183 return sorted(arg.resolve(cwd)) 184 return [arg] 185 186def expand_glob_expressions(args, cwd): 187 result = [args[0]] 188 for arg in args[1:]: 189 result.extend(expand_glob(arg, cwd)) 190 return result 191 192def quote_windows_command(seq): 193 """ 194 Reimplement Python's private subprocess.list2cmdline for MSys compatibility 195 196 Based on CPython implementation here: 197 https://hg.python.org/cpython/file/849826a900d2/Lib/subprocess.py#l422 198 199 Some core util distributions (MSys) don't tokenize command line arguments 200 the same way that MSVC CRT does. Lit rolls its own quoting logic similar to 201 the stock CPython logic to paper over these quoting and tokenization rule 202 differences. 203 204 We use the same algorithm from MSDN as CPython 205 (http://msdn.microsoft.com/en-us/library/17w5ykft.aspx), but we treat more 206 characters as needing quoting, such as double quotes themselves, and square 207 brackets. 208 209 For MSys based tools, this is very brittle though, because quoting an 210 argument makes the MSys based tool unescape backslashes where it shouldn't 211 (e.g. "a\b\\c\\\\d" becomes "a\b\c\\d" where it should stay as it was, 212 according to regular win32 command line parsing rules). 213 """ 214 result = [] 215 needquote = False 216 for arg in seq: 217 bs_buf = [] 218 219 # Add a space to separate this argument from the others 220 if result: 221 result.append(' ') 222 223 # This logic differs from upstream list2cmdline. 224 needquote = (" " in arg) or ("\t" in arg) or ("\"" in arg) or ("[" in arg) or (";" in arg) or not arg 225 if needquote: 226 result.append('"') 227 228 for c in arg: 229 if c == '\\': 230 # Don't know if we need to double yet. 231 bs_buf.append(c) 232 elif c == '"': 233 # Double backslashes. 234 result.append('\\' * len(bs_buf)*2) 235 bs_buf = [] 236 result.append('\\"') 237 else: 238 # Normal char 239 if bs_buf: 240 result.extend(bs_buf) 241 bs_buf = [] 242 result.append(c) 243 244 # Add remaining backslashes, if any. 245 if bs_buf: 246 result.extend(bs_buf) 247 248 if needquote: 249 result.extend(bs_buf) 250 result.append('"') 251 252 return ''.join(result) 253 254# args are from 'export' or 'env' command. 255# Skips the command, and parses its arguments. 256# Modifies env accordingly. 257# Returns copy of args without the command or its arguments. 258def updateEnv(env, args): 259 arg_idx_next = len(args) 260 unset_next_env_var = False 261 for arg_idx, arg in enumerate(args[1:]): 262 # Support for the -u flag (unsetting) for env command 263 # e.g., env -u FOO -u BAR will remove both FOO and BAR 264 # from the environment. 265 if arg == '-u': 266 unset_next_env_var = True 267 continue 268 if unset_next_env_var: 269 unset_next_env_var = False 270 if arg in env.env: 271 del env.env[arg] 272 continue 273 274 # Partition the string into KEY=VALUE. 275 key, eq, val = arg.partition('=') 276 # Stop if there was no equals. 277 if eq == '': 278 arg_idx_next = arg_idx + 1 279 break 280 env.env[key] = val 281 return args[arg_idx_next:] 282 283def executeBuiltinCd(cmd, shenv): 284 """executeBuiltinCd - Change the current directory.""" 285 if len(cmd.args) != 2: 286 raise InternalShellError(cmd, "'cd' supports only one argument") 287 # Update the cwd in the parent environment. 288 shenv.change_dir(cmd.args[1]) 289 # The cd builtin always succeeds. If the directory does not exist, the 290 # following Popen calls will fail instead. 291 return ShellCommandResult(cmd, "", "", 0, False) 292 293def executeBuiltinPushd(cmd, shenv): 294 """executeBuiltinPushd - Change the current dir and save the old.""" 295 if len(cmd.args) != 2: 296 raise InternalShellError(cmd, "'pushd' supports only one argument") 297 shenv.dirStack.append(shenv.cwd) 298 shenv.change_dir(cmd.args[1]) 299 return ShellCommandResult(cmd, "", "", 0, False) 300 301def executeBuiltinPopd(cmd, shenv): 302 """executeBuiltinPopd - Restore a previously saved working directory.""" 303 if len(cmd.args) != 1: 304 raise InternalShellError(cmd, "'popd' does not support arguments") 305 if not shenv.dirStack: 306 raise InternalShellError(cmd, "popd: directory stack empty") 307 shenv.cwd = shenv.dirStack.pop() 308 return ShellCommandResult(cmd, "", "", 0, False) 309 310def executeBuiltinExport(cmd, shenv): 311 """executeBuiltinExport - Set an environment variable.""" 312 if len(cmd.args) != 2: 313 raise InternalShellError("'export' supports only one argument") 314 updateEnv(shenv, cmd.args) 315 return ShellCommandResult(cmd, "", "", 0, False) 316 317def executeBuiltinEcho(cmd, shenv): 318 """Interpret a redirected echo command""" 319 opened_files = [] 320 stdin, stdout, stderr = processRedirects(cmd, subprocess.PIPE, shenv, 321 opened_files) 322 if stdin != subprocess.PIPE or stderr != subprocess.PIPE: 323 raise InternalShellError( 324 cmd, "stdin and stderr redirects not supported for echo") 325 326 # Some tests have un-redirected echo commands to help debug test failures. 327 # Buffer our output and return it to the caller. 328 is_redirected = True 329 encode = lambda x : x 330 if stdout == subprocess.PIPE: 331 is_redirected = False 332 stdout = StringIO() 333 elif kIsWindows: 334 # Reopen stdout in binary mode to avoid CRLF translation. The versions 335 # of echo we are replacing on Windows all emit plain LF, and the LLVM 336 # tests now depend on this. 337 # When we open as binary, however, this also means that we have to write 338 # 'bytes' objects to stdout instead of 'str' objects. 339 encode = lit.util.to_bytes 340 stdout = open(stdout.name, stdout.mode + 'b') 341 opened_files.append((None, None, stdout, None)) 342 343 # Implement echo flags. We only support -e and -n, and not yet in 344 # combination. We have to ignore unknown flags, because `echo "-D FOO"` 345 # prints the dash. 346 args = cmd.args[1:] 347 interpret_escapes = False 348 write_newline = True 349 while len(args) >= 1 and args[0] in ('-e', '-n'): 350 flag = args[0] 351 args = args[1:] 352 if flag == '-e': 353 interpret_escapes = True 354 elif flag == '-n': 355 write_newline = False 356 357 def maybeUnescape(arg): 358 if not interpret_escapes: 359 return arg 360 361 arg = lit.util.to_bytes(arg) 362 codec = 'string_escape' if sys.version_info < (3,0) else 'unicode_escape' 363 return arg.decode(codec) 364 365 if args: 366 for arg in args[:-1]: 367 stdout.write(encode(maybeUnescape(arg))) 368 stdout.write(encode(' ')) 369 stdout.write(encode(maybeUnescape(args[-1]))) 370 if write_newline: 371 stdout.write(encode('\n')) 372 373 for (name, mode, f, path) in opened_files: 374 f.close() 375 376 output = "" if is_redirected else stdout.getvalue() 377 return ShellCommandResult(cmd, output, "", 0, False) 378 379def executeBuiltinMkdir(cmd, cmd_shenv): 380 """executeBuiltinMkdir - Create new directories.""" 381 args = expand_glob_expressions(cmd.args, cmd_shenv.cwd)[1:] 382 try: 383 opts, args = getopt.gnu_getopt(args, 'p') 384 except getopt.GetoptError as err: 385 raise InternalShellError(cmd, "Unsupported: 'mkdir': %s" % str(err)) 386 387 parent = False 388 for o, a in opts: 389 if o == "-p": 390 parent = True 391 else: 392 assert False, "unhandled option" 393 394 if len(args) == 0: 395 raise InternalShellError(cmd, "Error: 'mkdir' is missing an operand") 396 397 stderr = StringIO() 398 exitCode = 0 399 for dir in args: 400 cwd = cmd_shenv.cwd 401 dir = to_unicode(dir) if kIsWindows else to_bytes(dir) 402 cwd = to_unicode(cwd) if kIsWindows else to_bytes(cwd) 403 if not os.path.isabs(dir): 404 dir = os.path.realpath(os.path.join(cwd, dir)) 405 if parent: 406 lit.util.mkdir_p(dir) 407 else: 408 try: 409 lit.util.mkdir(dir) 410 except OSError as err: 411 stderr.write("Error: 'mkdir' command failed, %s\n" % str(err)) 412 exitCode = 1 413 return ShellCommandResult(cmd, "", stderr.getvalue(), exitCode, False) 414 415def executeBuiltinRm(cmd, cmd_shenv): 416 """executeBuiltinRm - Removes (deletes) files or directories.""" 417 args = expand_glob_expressions(cmd.args, cmd_shenv.cwd)[1:] 418 try: 419 opts, args = getopt.gnu_getopt(args, "frR", ["--recursive"]) 420 except getopt.GetoptError as err: 421 raise InternalShellError(cmd, "Unsupported: 'rm': %s" % str(err)) 422 423 force = False 424 recursive = False 425 for o, a in opts: 426 if o == "-f": 427 force = True 428 elif o in ("-r", "-R", "--recursive"): 429 recursive = True 430 else: 431 assert False, "unhandled option" 432 433 if len(args) == 0: 434 raise InternalShellError(cmd, "Error: 'rm' is missing an operand") 435 436 def on_rm_error(func, path, exc_info): 437 # path contains the path of the file that couldn't be removed 438 # let's just assume that it's read-only and remove it. 439 os.chmod(path, stat.S_IMODE( os.stat(path).st_mode) | stat.S_IWRITE) 440 os.remove(path) 441 442 stderr = StringIO() 443 exitCode = 0 444 for path in args: 445 cwd = cmd_shenv.cwd 446 path = to_unicode(path) if kIsWindows else to_bytes(path) 447 cwd = to_unicode(cwd) if kIsWindows else to_bytes(cwd) 448 if not os.path.isabs(path): 449 path = os.path.realpath(os.path.join(cwd, path)) 450 if force and not os.path.exists(path): 451 continue 452 try: 453 if os.path.isdir(path): 454 if not recursive: 455 stderr.write("Error: %s is a directory\n" % path) 456 exitCode = 1 457 if platform.system() == 'Windows': 458 # NOTE: use ctypes to access `SHFileOperationsW` on Windows to 459 # use the NT style path to get access to long file paths which 460 # cannot be removed otherwise. 461 from ctypes.wintypes import BOOL, HWND, LPCWSTR, UINT, WORD 462 from ctypes import addressof, byref, c_void_p, create_unicode_buffer 463 from ctypes import Structure 464 from ctypes import windll, WinError, POINTER 465 466 class SHFILEOPSTRUCTW(Structure): 467 _fields_ = [ 468 ('hWnd', HWND), 469 ('wFunc', UINT), 470 ('pFrom', LPCWSTR), 471 ('pTo', LPCWSTR), 472 ('fFlags', WORD), 473 ('fAnyOperationsAborted', BOOL), 474 ('hNameMappings', c_void_p), 475 ('lpszProgressTitle', LPCWSTR), 476 ] 477 478 FO_MOVE, FO_COPY, FO_DELETE, FO_RENAME = range(1, 5) 479 480 FOF_SILENT = 4 481 FOF_NOCONFIRMATION = 16 482 FOF_NOCONFIRMMKDIR = 512 483 FOF_NOERRORUI = 1024 484 485 FOF_NO_UI = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR 486 487 SHFileOperationW = windll.shell32.SHFileOperationW 488 SHFileOperationW.argtypes = [POINTER(SHFILEOPSTRUCTW)] 489 490 path = os.path.abspath(path) 491 492 pFrom = create_unicode_buffer(path, len(path) + 2) 493 pFrom[len(path)] = pFrom[len(path) + 1] = '\0' 494 operation = SHFILEOPSTRUCTW(wFunc=UINT(FO_DELETE), 495 pFrom=LPCWSTR(addressof(pFrom)), 496 fFlags=FOF_NO_UI) 497 result = SHFileOperationW(byref(operation)) 498 if result: 499 raise WinError(result) 500 else: 501 shutil.rmtree(path, onerror = on_rm_error if force else None) 502 else: 503 if force and not os.access(path, os.W_OK): 504 os.chmod(path, 505 stat.S_IMODE(os.stat(path).st_mode) | stat.S_IWRITE) 506 os.remove(path) 507 except OSError as err: 508 stderr.write("Error: 'rm' command failed, %s" % str(err)) 509 exitCode = 1 510 return ShellCommandResult(cmd, "", stderr.getvalue(), exitCode, False) 511 512def executeBuiltinColon(cmd, cmd_shenv): 513 """executeBuiltinColon - Discard arguments and exit with status 0.""" 514 return ShellCommandResult(cmd, "", "", 0, False) 515 516def processRedirects(cmd, stdin_source, cmd_shenv, opened_files): 517 """Return the standard fds for cmd after applying redirects 518 519 Returns the three standard file descriptors for the new child process. Each 520 fd may be an open, writable file object or a sentinel value from the 521 subprocess module. 522 """ 523 524 # Apply the redirections, we use (N,) as a sentinel to indicate stdin, 525 # stdout, stderr for N equal to 0, 1, or 2 respectively. Redirects to or 526 # from a file are represented with a list [file, mode, file-object] 527 # where file-object is initially None. 528 redirects = [(0,), (1,), (2,)] 529 for (op, filename) in cmd.redirects: 530 if op == ('>',2): 531 redirects[2] = [filename, 'w', None] 532 elif op == ('>>',2): 533 redirects[2] = [filename, 'a', None] 534 elif op == ('>&',2) and filename in '012': 535 redirects[2] = redirects[int(filename)] 536 elif op == ('>&',) or op == ('&>',): 537 redirects[1] = redirects[2] = [filename, 'w', None] 538 elif op == ('>',): 539 redirects[1] = [filename, 'w', None] 540 elif op == ('>>',): 541 redirects[1] = [filename, 'a', None] 542 elif op == ('<',): 543 redirects[0] = [filename, 'r', None] 544 else: 545 raise InternalShellError(cmd, "Unsupported redirect: %r" % ((op, filename),)) 546 547 # Open file descriptors in a second pass. 548 std_fds = [None, None, None] 549 for (index, r) in enumerate(redirects): 550 # Handle the sentinel values for defaults up front. 551 if isinstance(r, tuple): 552 if r == (0,): 553 fd = stdin_source 554 elif r == (1,): 555 if index == 0: 556 raise InternalShellError(cmd, "Unsupported redirect for stdin") 557 elif index == 1: 558 fd = subprocess.PIPE 559 else: 560 fd = subprocess.STDOUT 561 elif r == (2,): 562 if index != 2: 563 raise InternalShellError(cmd, "Unsupported redirect on stdout") 564 fd = subprocess.PIPE 565 else: 566 raise InternalShellError(cmd, "Bad redirect") 567 std_fds[index] = fd 568 continue 569 570 (filename, mode, fd) = r 571 572 # Check if we already have an open fd. This can happen if stdout and 573 # stderr go to the same place. 574 if fd is not None: 575 std_fds[index] = fd 576 continue 577 578 redir_filename = None 579 name = expand_glob(filename, cmd_shenv.cwd) 580 if len(name) != 1: 581 raise InternalShellError(cmd, "Unsupported: glob in " 582 "redirect expanded to multiple files") 583 name = name[0] 584 if kAvoidDevNull and name == kDevNull: 585 fd = tempfile.TemporaryFile(mode=mode) 586 elif kIsWindows and name == '/dev/tty': 587 # Simulate /dev/tty on Windows. 588 # "CON" is a special filename for the console. 589 fd = open("CON", mode) 590 else: 591 # Make sure relative paths are relative to the cwd. 592 redir_filename = os.path.join(cmd_shenv.cwd, name) 593 redir_filename = to_unicode(redir_filename) \ 594 if kIsWindows else to_bytes(redir_filename) 595 fd = open(redir_filename, mode) 596 # Workaround a Win32 and/or subprocess bug when appending. 597 # 598 # FIXME: Actually, this is probably an instance of PR6753. 599 if mode == 'a': 600 fd.seek(0, 2) 601 # Mutate the underlying redirect list so that we can redirect stdout 602 # and stderr to the same place without opening the file twice. 603 r[2] = fd 604 opened_files.append((filename, mode, fd) + (redir_filename,)) 605 std_fds[index] = fd 606 607 return std_fds 608 609def _executeShCmd(cmd, shenv, results, timeoutHelper): 610 if timeoutHelper.timeoutReached(): 611 # Prevent further recursion if the timeout has been hit 612 # as we should try avoid launching more processes. 613 return None 614 615 if isinstance(cmd, ShUtil.Seq): 616 if cmd.op == ';': 617 res = _executeShCmd(cmd.lhs, shenv, results, timeoutHelper) 618 return _executeShCmd(cmd.rhs, shenv, results, timeoutHelper) 619 620 if cmd.op == '&': 621 raise InternalShellError(cmd,"unsupported shell operator: '&'") 622 623 if cmd.op == '||': 624 res = _executeShCmd(cmd.lhs, shenv, results, timeoutHelper) 625 if res != 0: 626 res = _executeShCmd(cmd.rhs, shenv, results, timeoutHelper) 627 return res 628 629 if cmd.op == '&&': 630 res = _executeShCmd(cmd.lhs, shenv, results, timeoutHelper) 631 if res is None: 632 return res 633 634 if res == 0: 635 res = _executeShCmd(cmd.rhs, shenv, results, timeoutHelper) 636 return res 637 638 raise ValueError('Unknown shell command: %r' % cmd.op) 639 assert isinstance(cmd, ShUtil.Pipeline) 640 641 procs = [] 642 proc_not_counts = [] 643 default_stdin = subprocess.PIPE 644 stderrTempFiles = [] 645 opened_files = [] 646 named_temp_files = [] 647 builtin_commands = set(['cat', 'diff']) 648 builtin_commands_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "builtin_commands") 649 inproc_builtins = {'cd': executeBuiltinCd, 650 'export': executeBuiltinExport, 651 'echo': executeBuiltinEcho, 652 'mkdir': executeBuiltinMkdir, 653 'popd': executeBuiltinPopd, 654 'pushd': executeBuiltinPushd, 655 'rm': executeBuiltinRm, 656 ':': executeBuiltinColon} 657 # To avoid deadlock, we use a single stderr stream for piped 658 # output. This is null until we have seen some output using 659 # stderr. 660 for i,j in enumerate(cmd.commands): 661 # Reference the global environment by default. 662 cmd_shenv = shenv 663 args = list(j.args) 664 not_args = [] 665 not_count = 0 666 not_crash = False 667 while True: 668 if args[0] == 'env': 669 # Create a copy of the global environment and modify it for 670 # this one command. There might be multiple envs in a pipeline, 671 # and there might be multiple envs in a command (usually when 672 # one comes from a substitution): 673 # env FOO=1 llc < %s | env BAR=2 llvm-mc | FileCheck %s 674 # env FOO=1 %{another_env_plus_cmd} | FileCheck %s 675 if cmd_shenv is shenv: 676 cmd_shenv = ShellEnvironment(shenv.cwd, shenv.env) 677 args = updateEnv(cmd_shenv, args) 678 if not args: 679 raise InternalShellError(j, "Error: 'env' requires a" 680 " subcommand") 681 elif args[0] == 'not': 682 not_args.append(args.pop(0)) 683 not_count += 1 684 if args and args[0] == '--crash': 685 not_args.append(args.pop(0)) 686 not_crash = True 687 if not args: 688 raise InternalShellError(j, "Error: 'not' requires a" 689 " subcommand") 690 elif args[0] == '!': 691 not_args.append(args.pop(0)) 692 not_count += 1 693 if not args: 694 raise InternalShellError(j, "Error: '!' requires a" 695 " subcommand") 696 else: 697 break 698 699 # Handle in-process builtins. 700 # 701 # Handle "echo" as a builtin if it is not part of a pipeline. This 702 # greatly speeds up tests that construct input files by repeatedly 703 # echo-appending to a file. 704 # FIXME: Standardize on the builtin echo implementation. We can use a 705 # temporary file to sidestep blocking pipe write issues. 706 inproc_builtin = inproc_builtins.get(args[0], None) 707 if inproc_builtin and (args[0] != 'echo' or len(cmd.commands) == 1): 708 # env calling an in-process builtin is useless, so we take the safe 709 # approach of complaining. 710 if not cmd_shenv is shenv: 711 raise InternalShellError(j, "Error: 'env' cannot call '{}'" 712 .format(args[0])) 713 if not_crash: 714 raise InternalShellError(j, "Error: 'not --crash' cannot call" 715 " '{}'".format(args[0])) 716 if len(cmd.commands) != 1: 717 raise InternalShellError(j, "Unsupported: '{}' cannot be part" 718 " of a pipeline".format(args[0])) 719 result = inproc_builtin(Command(args, j.redirects), cmd_shenv) 720 if not_count % 2: 721 result.exitCode = int(not result.exitCode) 722 result.command.args = j.args; 723 results.append(result) 724 return result.exitCode 725 726 # Resolve any out-of-process builtin command before adding back 'not' 727 # commands. 728 if args[0] in builtin_commands: 729 args.insert(0, sys.executable) 730 cmd_shenv.env['PYTHONPATH'] = \ 731 os.path.dirname(os.path.abspath(__file__)) 732 args[1] = os.path.join(builtin_commands_dir, args[1] + ".py") 733 734 # We had to search through the 'not' commands to find all the 'env' 735 # commands and any other in-process builtin command. We don't want to 736 # reimplement 'not' and its '--crash' here, so just push all 'not' 737 # commands back to be called as external commands. Because this 738 # approach effectively moves all 'env' commands up front, it relies on 739 # the assumptions that (1) environment variables are not intended to be 740 # relevant to 'not' commands and (2) the 'env' command should always 741 # blindly pass along the status it receives from any command it calls. 742 743 # For plain negations, either 'not' without '--crash', or the shell 744 # operator '!', leave them out from the command to execute and 745 # invert the result code afterwards. 746 if not_crash: 747 args = not_args + args 748 not_count = 0 749 else: 750 not_args = [] 751 752 stdin, stdout, stderr = processRedirects(j, default_stdin, cmd_shenv, 753 opened_files) 754 755 # If stderr wants to come from stdout, but stdout isn't a pipe, then put 756 # stderr on a pipe and treat it as stdout. 757 if (stderr == subprocess.STDOUT and stdout != subprocess.PIPE): 758 stderr = subprocess.PIPE 759 stderrIsStdout = True 760 else: 761 stderrIsStdout = False 762 763 # Don't allow stderr on a PIPE except for the last 764 # process, this could deadlock. 765 # 766 # FIXME: This is slow, but so is deadlock. 767 if stderr == subprocess.PIPE and j != cmd.commands[-1]: 768 stderr = tempfile.TemporaryFile(mode='w+b') 769 stderrTempFiles.append((i, stderr)) 770 771 # Resolve the executable path ourselves. 772 executable = None 773 # For paths relative to cwd, use the cwd of the shell environment. 774 if args[0].startswith('.'): 775 exe_in_cwd = os.path.join(cmd_shenv.cwd, args[0]) 776 if os.path.isfile(exe_in_cwd): 777 executable = exe_in_cwd 778 if not executable: 779 executable = lit.util.which(args[0], cmd_shenv.env['PATH']) 780 if not executable: 781 raise InternalShellError(j, '%r: command not found' % args[0]) 782 783 # Replace uses of /dev/null with temporary files. 784 if kAvoidDevNull: 785 # In Python 2.x, basestring is the base class for all string (including unicode) 786 # In Python 3.x, basestring no longer exist and str is always unicode 787 try: 788 str_type = basestring 789 except NameError: 790 str_type = str 791 for i,arg in enumerate(args): 792 if isinstance(arg, str_type) and kDevNull in arg: 793 f = tempfile.NamedTemporaryFile(delete=False) 794 f.close() 795 named_temp_files.append(f.name) 796 args[i] = arg.replace(kDevNull, f.name) 797 798 # Expand all glob expressions 799 args = expand_glob_expressions(args, cmd_shenv.cwd) 800 801 # On Windows, do our own command line quoting for better compatibility 802 # with some core utility distributions. 803 if kIsWindows: 804 args = quote_windows_command(args) 805 806 try: 807 procs.append(subprocess.Popen(args, cwd=cmd_shenv.cwd, 808 executable = executable, 809 stdin = stdin, 810 stdout = stdout, 811 stderr = stderr, 812 env = cmd_shenv.env, 813 close_fds = kUseCloseFDs, 814 universal_newlines = True, 815 errors = 'replace')) 816 proc_not_counts.append(not_count) 817 # Let the helper know about this process 818 timeoutHelper.addProcess(procs[-1]) 819 except OSError as e: 820 raise InternalShellError(j, 'Could not create process ({}) due to {}'.format(executable, e)) 821 822 # Immediately close stdin for any process taking stdin from us. 823 if stdin == subprocess.PIPE: 824 procs[-1].stdin.close() 825 procs[-1].stdin = None 826 827 # Update the current stdin source. 828 if stdout == subprocess.PIPE: 829 default_stdin = procs[-1].stdout 830 elif stderrIsStdout: 831 default_stdin = procs[-1].stderr 832 else: 833 default_stdin = subprocess.PIPE 834 835 # Explicitly close any redirected files. We need to do this now because we 836 # need to release any handles we may have on the temporary files (important 837 # on Win32, for example). Since we have already spawned the subprocess, our 838 # handles have already been transferred so we do not need them anymore. 839 for (name, mode, f, path) in opened_files: 840 f.close() 841 842 # FIXME: There is probably still deadlock potential here. Yawn. 843 procData = [None] * len(procs) 844 procData[-1] = procs[-1].communicate() 845 846 for i in range(len(procs) - 1): 847 if procs[i].stdout is not None: 848 out = procs[i].stdout.read() 849 else: 850 out = '' 851 if procs[i].stderr is not None: 852 err = procs[i].stderr.read() 853 else: 854 err = '' 855 procData[i] = (out,err) 856 857 # Read stderr out of the temp files. 858 for i,f in stderrTempFiles: 859 f.seek(0, 0) 860 procData[i] = (procData[i][0], f.read()) 861 f.close() 862 863 exitCode = None 864 for i,(out,err) in enumerate(procData): 865 res = procs[i].wait() 866 # Detect Ctrl-C in subprocess. 867 if res == -signal.SIGINT: 868 raise KeyboardInterrupt 869 if proc_not_counts[i] % 2: 870 res = not res 871 elif proc_not_counts[i] > 1: 872 res = 1 if res != 0 else 0 873 874 # Ensure the resulting output is always of string type. 875 try: 876 if out is None: 877 out = '' 878 else: 879 out = to_string(out.decode('utf-8', errors='replace')) 880 except: 881 out = str(out) 882 try: 883 if err is None: 884 err = '' 885 else: 886 err = to_string(err.decode('utf-8', errors='replace')) 887 except: 888 err = str(err) 889 890 # Gather the redirected output files for failed commands. 891 output_files = [] 892 if res != 0: 893 for (name, mode, f, path) in sorted(opened_files): 894 if path is not None and mode in ('w', 'a'): 895 try: 896 with open(path, 'rb') as f: 897 data = f.read() 898 except: 899 data = None 900 if data is not None: 901 output_files.append((name, path, data)) 902 903 results.append(ShellCommandResult( 904 cmd.commands[i], out, err, res, timeoutHelper.timeoutReached(), 905 output_files)) 906 if cmd.pipe_err: 907 # Take the last failing exit code from the pipeline. 908 if not exitCode or res != 0: 909 exitCode = res 910 else: 911 exitCode = res 912 913 # Remove any named temporary files we created. 914 for f in named_temp_files: 915 try: 916 os.remove(f) 917 except OSError: 918 pass 919 920 if cmd.negate: 921 exitCode = not exitCode 922 923 return exitCode 924 925def executeScriptInternal(test, litConfig, tmpBase, commands, cwd): 926 cmds = [] 927 for i, ln in enumerate(commands): 928 match = re.match(kPdbgRegex, ln) 929 if match: 930 command = match.group(2) 931 ln = commands[i] = \ 932 match.expand(": '\\1'; \\2" if command else ": '\\1'") 933 try: 934 cmds.append(ShUtil.ShParser(ln, litConfig.isWindows, 935 test.config.pipefail).parse()) 936 except: 937 return lit.Test.Result(Test.FAIL, "shell parser error on: %r" % ln) 938 939 cmd = cmds[0] 940 for c in cmds[1:]: 941 cmd = ShUtil.Seq(cmd, '&&', c) 942 943 results = [] 944 timeoutInfo = None 945 try: 946 shenv = ShellEnvironment(cwd, test.config.environment) 947 exitCode, timeoutInfo = executeShCmd(cmd, shenv, results, timeout=litConfig.maxIndividualTestTime) 948 except InternalShellError: 949 e = sys.exc_info()[1] 950 exitCode = 127 951 results.append( 952 ShellCommandResult(e.command, '', e.message, exitCode, False)) 953 954 out = err = '' 955 for i,result in enumerate(results): 956 # Write the command line run. 957 out += '$ %s\n' % (' '.join('"%s"' % s 958 for s in result.command.args),) 959 960 # If nothing interesting happened, move on. 961 if litConfig.maxIndividualTestTime == 0 and \ 962 result.exitCode == 0 and \ 963 not result.stdout.strip() and not result.stderr.strip(): 964 continue 965 966 # Otherwise, something failed or was printed, show it. 967 968 # Add the command output, if redirected. 969 for (name, path, data) in result.outputFiles: 970 if data.strip(): 971 out += "# redirected output from %r:\n" % (name,) 972 data = to_string(data.decode('utf-8', errors='replace')) 973 if len(data) > 1024: 974 out += data[:1024] + "\n...\n" 975 out += "note: data was truncated\n" 976 else: 977 out += data 978 out += "\n" 979 980 if result.stdout.strip(): 981 out += '# command output:\n%s\n' % (result.stdout,) 982 if result.stderr.strip(): 983 out += '# command stderr:\n%s\n' % (result.stderr,) 984 if not result.stdout.strip() and not result.stderr.strip(): 985 out += "note: command had no output on stdout or stderr\n" 986 987 # Show the error conditions: 988 if result.exitCode != 0: 989 # On Windows, a negative exit code indicates a signal, and those are 990 # easier to recognize or look up if we print them in hex. 991 if litConfig.isWindows and (result.exitCode < 0 or result.exitCode > 255): 992 codeStr = hex(int(result.exitCode & 0xFFFFFFFF)).rstrip("L") 993 else: 994 codeStr = str(result.exitCode) 995 out += "error: command failed with exit status: %s\n" % ( 996 codeStr,) 997 if litConfig.maxIndividualTestTime > 0 and result.timeoutReached: 998 out += 'error: command reached timeout: %s\n' % ( 999 str(result.timeoutReached),) 1000 1001 return out, err, exitCode, timeoutInfo 1002 1003def executeScript(test, litConfig, tmpBase, commands, cwd): 1004 bashPath = litConfig.getBashPath() 1005 isWin32CMDEXE = (litConfig.isWindows and not bashPath) 1006 script = tmpBase + '.script' 1007 if isWin32CMDEXE: 1008 script += '.bat' 1009 1010 # Write script file 1011 mode = 'w' 1012 open_kwargs = {} 1013 if litConfig.isWindows and not isWin32CMDEXE: 1014 mode += 'b' # Avoid CRLFs when writing bash scripts. 1015 elif sys.version_info > (3,0): 1016 open_kwargs['encoding'] = 'utf-8' 1017 f = open(script, mode, **open_kwargs) 1018 if isWin32CMDEXE: 1019 for i, ln in enumerate(commands): 1020 match = re.match(kPdbgRegex, ln) 1021 if match: 1022 command = match.group(2) 1023 commands[i] = \ 1024 match.expand("echo '\\1' > nul && " if command 1025 else "echo '\\1' > nul") 1026 if litConfig.echo_all_commands: 1027 f.write('@echo on\n') 1028 else: 1029 f.write('@echo off\n') 1030 f.write('\n@if %ERRORLEVEL% NEQ 0 EXIT\n'.join(commands)) 1031 else: 1032 for i, ln in enumerate(commands): 1033 match = re.match(kPdbgRegex, ln) 1034 if match: 1035 command = match.group(2) 1036 commands[i] = match.expand(": '\\1'; \\2" if command 1037 else ": '\\1'") 1038 if test.config.pipefail: 1039 f.write(b'set -o pipefail;' if mode == 'wb' else 'set -o pipefail;') 1040 if litConfig.echo_all_commands: 1041 f.write(b'set -x;' if mode == 'wb' else 'set -x;') 1042 if sys.version_info > (3,0) and mode == 'wb': 1043 f.write(bytes('{ ' + '; } &&\n{ '.join(commands) + '; }', 'utf-8')) 1044 else: 1045 f.write('{ ' + '; } &&\n{ '.join(commands) + '; }') 1046 f.write(b'\n' if mode == 'wb' else '\n') 1047 f.close() 1048 1049 if isWin32CMDEXE: 1050 command = ['cmd','/c', script] 1051 else: 1052 if bashPath: 1053 command = [bashPath, script] 1054 else: 1055 command = ['/bin/sh', script] 1056 if litConfig.useValgrind: 1057 # FIXME: Running valgrind on sh is overkill. We probably could just 1058 # run on clang with no real loss. 1059 command = litConfig.valgrindArgs + command 1060 1061 try: 1062 out, err, exitCode = lit.util.executeCommand(command, cwd=cwd, 1063 env=test.config.environment, 1064 timeout=litConfig.maxIndividualTestTime) 1065 return (out, err, exitCode, None) 1066 except lit.util.ExecuteCommandTimeoutException as e: 1067 return (e.out, e.err, e.exitCode, e.msg) 1068 1069def parseIntegratedTestScriptCommands(source_path, keywords): 1070 """ 1071 parseIntegratedTestScriptCommands(source_path) -> commands 1072 1073 Parse the commands in an integrated test script file into a list of 1074 (line_number, command_type, line). 1075 """ 1076 1077 # This code is carefully written to be dual compatible with Python 2.5+ and 1078 # Python 3 without requiring input files to always have valid codings. The 1079 # trick we use is to open the file in binary mode and use the regular 1080 # expression library to find the commands, with it scanning strings in 1081 # Python2 and bytes in Python3. 1082 # 1083 # Once we find a match, we do require each script line to be decodable to 1084 # UTF-8, so we convert the outputs to UTF-8 before returning. This way the 1085 # remaining code can work with "strings" agnostic of the executing Python 1086 # version. 1087 1088 keywords_re = re.compile( 1089 to_bytes("(%s)(.*)\n" % ("|".join(re.escape(k) for k in keywords),))) 1090 1091 f = open(source_path, 'rb') 1092 try: 1093 # Read the entire file contents. 1094 data = f.read() 1095 1096 # Ensure the data ends with a newline. 1097 if not data.endswith(to_bytes('\n')): 1098 data = data + to_bytes('\n') 1099 1100 # Iterate over the matches. 1101 line_number = 1 1102 last_match_position = 0 1103 for match in keywords_re.finditer(data): 1104 # Compute the updated line number by counting the intervening 1105 # newlines. 1106 match_position = match.start() 1107 line_number += data.count(to_bytes('\n'), last_match_position, 1108 match_position) 1109 last_match_position = match_position 1110 1111 # Convert the keyword and line to UTF-8 strings and yield the 1112 # command. Note that we take care to return regular strings in 1113 # Python 2, to avoid other code having to differentiate between the 1114 # str and unicode types. 1115 # 1116 # Opening the file in binary mode prevented Windows \r newline 1117 # characters from being converted to Unix \n newlines, so manually 1118 # strip those from the yielded lines. 1119 keyword,ln = match.groups() 1120 yield (line_number, to_string(keyword.decode('utf-8')), 1121 to_string(ln.decode('utf-8').rstrip('\r'))) 1122 finally: 1123 f.close() 1124 1125def getTempPaths(test): 1126 """Get the temporary location, this is always relative to the test suite 1127 root, not test source root.""" 1128 execpath = test.getExecPath() 1129 execdir,execbase = os.path.split(execpath) 1130 tmpDir = os.path.join(execdir, 'Output') 1131 tmpBase = os.path.join(tmpDir, execbase) 1132 return tmpDir, tmpBase 1133 1134def colonNormalizePath(path): 1135 if kIsWindows: 1136 return re.sub(r'^(.):', r'\1', path.replace('\\', '/')) 1137 else: 1138 assert path[0] == '/' 1139 return path[1:] 1140 1141def getDefaultSubstitutions(test, tmpDir, tmpBase, normalize_slashes=False): 1142 sourcepath = test.getSourcePath() 1143 sourcedir = os.path.dirname(sourcepath) 1144 1145 # Normalize slashes, if requested. 1146 if normalize_slashes: 1147 sourcepath = sourcepath.replace('\\', '/') 1148 sourcedir = sourcedir.replace('\\', '/') 1149 tmpDir = tmpDir.replace('\\', '/') 1150 tmpBase = tmpBase.replace('\\', '/') 1151 1152 substitutions = [] 1153 substitutions.extend(test.config.substitutions) 1154 tmpName = tmpBase + '.tmp' 1155 baseName = os.path.basename(tmpBase) 1156 substitutions.extend([('%s', sourcepath), 1157 ('%S', sourcedir), 1158 ('%p', sourcedir), 1159 ('%{pathsep}', os.pathsep), 1160 ('%t', tmpName), 1161 ('%basename_t', baseName), 1162 ('%T', tmpDir)]) 1163 1164 substitutions.extend([ 1165 ('%{fs-src-root}', pathlib.Path(sourcedir).anchor), 1166 ('%{fs-tmp-root}', pathlib.Path(tmpBase).anchor), 1167 ('%{fs-sep}', os.path.sep), 1168 ]) 1169 1170 # "%/[STpst]" should be normalized. 1171 substitutions.extend([ 1172 ('%/s', sourcepath.replace('\\', '/')), 1173 ('%/S', sourcedir.replace('\\', '/')), 1174 ('%/p', sourcedir.replace('\\', '/')), 1175 ('%/t', tmpBase.replace('\\', '/') + '.tmp'), 1176 ('%/T', tmpDir.replace('\\', '/')), 1177 ('%/et',tmpName.replace('\\', '\\\\\\\\\\\\\\\\')), 1178 ]) 1179 1180 # "%{/[STpst]:regex_replacement}" should be normalized like "%/[STpst]" but we're 1181 # also in a regex replacement context of a s@@@ regex. 1182 def regex_escape(s): 1183 s = s.replace('@', r'\@') 1184 s = s.replace('&', r'\&') 1185 return s 1186 substitutions.extend([ 1187 ('%{/s:regex_replacement}', 1188 regex_escape(sourcepath.replace('\\', '/'))), 1189 ('%{/S:regex_replacement}', 1190 regex_escape(sourcedir.replace('\\', '/'))), 1191 ('%{/p:regex_replacement}', 1192 regex_escape(sourcedir.replace('\\', '/'))), 1193 ('%{/t:regex_replacement}', 1194 regex_escape(tmpBase.replace('\\', '/')) + '.tmp'), 1195 ('%{/T:regex_replacement}', 1196 regex_escape(tmpDir.replace('\\', '/'))), 1197 ]) 1198 1199 # "%:[STpst]" are normalized paths without colons and without a leading 1200 # slash. 1201 substitutions.extend([ 1202 ('%:s', colonNormalizePath(sourcepath)), 1203 ('%:S', colonNormalizePath(sourcedir)), 1204 ('%:p', colonNormalizePath(sourcedir)), 1205 ('%:t', colonNormalizePath(tmpBase + '.tmp')), 1206 ('%:T', colonNormalizePath(tmpDir)), 1207 ]) 1208 return substitutions 1209 1210def _memoize(f): 1211 cache = {} # Intentionally unbounded, see applySubstitutions() 1212 def memoized(x): 1213 if x not in cache: 1214 cache[x] = f(x) 1215 return cache[x] 1216 return memoized 1217 1218@_memoize 1219def _caching_re_compile(r): 1220 return re.compile(r) 1221 1222class ExpandableScriptDirective(object): 1223 """ 1224 Common interface for lit directives for which any lit substitutions must be 1225 expanded to produce the shell script. It includes directives (e.g., 'RUN:') 1226 specifying shell commands that might have lit substitutions to be expanded. 1227 It also includes lit directives (e.g., 'DEFINE:') that adjust substitutions. 1228 1229 start_line_number: The directive's starting line number. 1230 end_line_number: The directive's ending line number, which is 1231 start_line_number if the directive has no line continuations. 1232 keyword: The keyword that specifies the directive. For example, 'RUN:'. 1233 """ 1234 1235 def __init__(self, start_line_number, end_line_number, keyword): 1236 # Input line number where the directive starts. 1237 self.start_line_number = start_line_number 1238 # Input line number where the directive ends. 1239 self.end_line_number = end_line_number 1240 # The keyword used to indicate the directive. 1241 self.keyword = keyword 1242 1243 def add_continuation(self, line_number, keyword, line): 1244 """ 1245 Add a continuation line to this directive and return True, or do nothing 1246 and return False if the specified line is not a continuation for this 1247 directive (e.g., previous line does not end in '\', or keywords do not 1248 match). 1249 1250 line_number: The line number for the continuation line. 1251 keyword: The keyword that specifies the continuation line. For example, 1252 'RUN:'. 1253 line: The content of the continuation line after the keyword. 1254 """ 1255 assert False, "expected method to be called on derived class" 1256 1257 def needs_continuation(self): 1258 """ 1259 Does this directive require a continuation line? 1260 1261 '\' is documented as indicating a line continuation even if whitespace 1262 separates it from the newline. It looks like a line continuation, and 1263 it would be confusing if it didn't behave as one. 1264 """ 1265 assert False, "expected method to be called on derived class" 1266 1267 def get_location(self): 1268 """ 1269 Get a phrase describing the line or range of lines so far included by 1270 this directive and any line continuations. 1271 """ 1272 if self.start_line_number == self.end_line_number: 1273 return f'at line {self.start_line_number}' 1274 return f'from line {self.start_line_number} to {self.end_line_number}' 1275 1276class CommandDirective(ExpandableScriptDirective): 1277 """ 1278 A lit directive taking a shell command line. For example, 1279 'RUN: echo hello world'. 1280 1281 command: The content accumulated so far from the directive and its 1282 continuation lines. 1283 """ 1284 1285 def __init__(self, start_line_number, end_line_number, keyword, line): 1286 super().__init__(start_line_number, end_line_number, keyword) 1287 self.command = line.rstrip() 1288 1289 def add_continuation(self, line_number, keyword, line): 1290 if keyword != self.keyword or not self.needs_continuation(): 1291 return False 1292 self.command = self.command[:-1] + line.rstrip() 1293 self.end_line_number = line_number 1294 return True 1295 1296 def needs_continuation(self): 1297 # Trailing whitespace is stripped immediately when each line is added, 1298 # so '\' is never hidden here. 1299 return self.command[-1] == '\\' 1300 1301class SubstDirective(ExpandableScriptDirective): 1302 """ 1303 A lit directive taking a substitution definition or redefinition. For 1304 example, 'DEFINE: %{name} = value'. 1305 1306 new_subst: True if this directive defines a new substitution. False if it 1307 redefines an existing substitution. 1308 body: The unparsed content accumulated so far from the directive and its 1309 continuation lines. 1310 name: The substitution's name, or None if more continuation lines are still 1311 required. 1312 value: The substitution's value, or None if more continuation lines are 1313 still required. 1314 """ 1315 1316 def __init__(self, start_line_number, end_line_number, keyword, new_subst, 1317 line): 1318 super().__init__(start_line_number, end_line_number, keyword) 1319 self.new_subst = new_subst 1320 self.body = line 1321 self.name = None 1322 self.value = None 1323 self._parse_body() 1324 1325 def add_continuation(self, line_number, keyword, line): 1326 if keyword != self.keyword or not self.needs_continuation(): 1327 return False 1328 if not line.strip(): 1329 raise ValueError("Substitution's continuation is empty") 1330 # Append line. Replace the '\' and any adjacent whitespace with a 1331 # single space. 1332 self.body = self.body.rstrip()[:-1].rstrip() + ' ' + line.lstrip() 1333 self.end_line_number = line_number 1334 self._parse_body() 1335 return True 1336 1337 def needs_continuation(self): 1338 return self.body.rstrip()[-1:] == '\\' 1339 1340 def _parse_body(self): 1341 """ 1342 If no more line continuations are required, parse all the directive's 1343 accumulated lines in order to identify the substitution's name and full 1344 value, and raise an exception if invalid. 1345 """ 1346 if self.needs_continuation(): 1347 return 1348 1349 # Extract the left-hand side and value, and discard any whitespace 1350 # enclosing each. 1351 parts = self.body.split('=', 1) 1352 if len(parts) == 1: 1353 raise ValueError("Substitution's definition does not contain '='") 1354 self.name = parts[0].strip() 1355 self.value = parts[1].strip() 1356 1357 # Check the substitution's name. 1358 # 1359 # Do not extend this to permit '.' or any sequence that's special in a 1360 # python pattern. We could escape that automatically for 1361 # DEFINE/REDEFINE directives in test files. However, lit configuration 1362 # file authors would still have to remember to escape them manually in 1363 # substitution names but not in values. Moreover, the manually chosen 1364 # and automatically chosen escape sequences would have to be consistent 1365 # (e.g., '\.' vs. '[.]') in order for REDEFINE to successfully redefine 1366 # a substitution previously defined by a lit configuration file. All 1367 # this seems too error prone and confusing to be worthwhile. If you 1368 # want your name to express structure, use ':' instead of '.'. 1369 # 1370 # Actually, '{' and '}' are special if they contain only digits possibly 1371 # separated by a comma. Requiring a leading letter avoids that. 1372 if not re.fullmatch(r'%{[_a-zA-Z][-_:0-9a-zA-Z]*}', self.name): 1373 raise ValueError( 1374 f"Substitution name '{self.name}' is malformed as it must " 1375 f"start with '%{{', it must end with '}}', and the rest must " 1376 f"start with a letter or underscore and contain only " 1377 f"alphanumeric characters, hyphens, underscores, and colons") 1378 1379 def adjust_substitutions(self, substitutions): 1380 """ 1381 Modify the specified substitution list as specified by this directive. 1382 """ 1383 assert not self.needs_continuation(), \ 1384 "expected directive continuations to be parsed before applying" 1385 value_repl = self.value.replace('\\', '\\\\') 1386 existing = [i for i, subst in enumerate(substitutions) 1387 if self.name in subst[0]] 1388 existing_res = ''.join("\nExisting pattern: " + substitutions[i][0] 1389 for i in existing) 1390 if self.new_subst: 1391 if existing: 1392 raise ValueError( 1393 f"Substitution whose pattern contains '{self.name}' is " 1394 f"already defined before '{self.keyword}' directive " 1395 f"{self.get_location()}" 1396 f"{existing_res}") 1397 substitutions.insert(0, (self.name, value_repl)) 1398 return 1399 if len(existing) > 1: 1400 raise ValueError( 1401 f"Multiple substitutions whose patterns contain '{self.name}' " 1402 f"are defined before '{self.keyword}' directive " 1403 f"{self.get_location()}" 1404 f"{existing_res}") 1405 if not existing: 1406 raise ValueError( 1407 f"No substitution for '{self.name}' is defined before " 1408 f"'{self.keyword}' directive {self.get_location()}") 1409 if substitutions[existing[0]][0] != self.name: 1410 raise ValueError( 1411 f"Existing substitution whose pattern contains '{self.name}' " 1412 f"does not have the pattern specified by '{self.keyword}' " 1413 f"directive {self.get_location()}\n" 1414 f"Expected pattern: {self.name}" 1415 f"{existing_res}") 1416 substitutions[existing[0]] = (self.name, value_repl) 1417 1418 1419def applySubstitutions(script, substitutions, conditions={}, 1420 recursion_limit=None): 1421 """ 1422 Apply substitutions to the script. Allow full regular expression syntax. 1423 Replace each matching occurrence of regular expression pattern a with 1424 substitution b in line ln. 1425 1426 If a substitution expands into another substitution, it is expanded 1427 recursively until the line has no more expandable substitutions. If 1428 the line can still can be substituted after being substituted 1429 `recursion_limit` times, it is an error. If the `recursion_limit` is 1430 `None` (the default), no recursive substitution is performed at all. 1431 """ 1432 1433 # We use #_MARKER_# to hide %% while we do the other substitutions. 1434 def escapePercents(ln): 1435 return _caching_re_compile('%%').sub('#_MARKER_#', ln) 1436 1437 def unescapePercents(ln): 1438 return _caching_re_compile('#_MARKER_#').sub('%', ln) 1439 1440 def substituteIfElse(ln): 1441 # early exit to avoid wasting time on lines without 1442 # conditional substitutions 1443 if ln.find('%if ') == -1: 1444 return ln 1445 1446 def tryParseIfCond(ln): 1447 # space is important to not conflict with other (possible) 1448 # substitutions 1449 if not ln.startswith('%if '): 1450 return None, ln 1451 ln = ln[4:] 1452 1453 # stop at '%{' 1454 match = _caching_re_compile('%{').search(ln) 1455 if not match: 1456 raise ValueError("'%{' is missing for %if substitution") 1457 cond = ln[:match.start()] 1458 1459 # eat '%{' as well 1460 ln = ln[match.end():] 1461 return cond, ln 1462 1463 def tryParseElse(ln): 1464 match = _caching_re_compile('^\s*%else\s*(%{)?').search(ln) 1465 if not match: 1466 return False, ln 1467 if not match.group(1): 1468 raise ValueError("'%{' is missing for %else substitution") 1469 return True, ln[match.end():] 1470 1471 def tryParseEnd(ln): 1472 if ln.startswith('%}'): 1473 return True, ln[2:] 1474 return False, ln 1475 1476 def parseText(ln, isNested): 1477 # parse everything until %if, or %} if we're parsing a 1478 # nested expression. 1479 match = _caching_re_compile( 1480 '(.*?)(?:%if|%})' if isNested else '(.*?)(?:%if)').search(ln) 1481 if not match: 1482 # there is no terminating pattern, so treat the whole 1483 # line as text 1484 return ln, '' 1485 text_end = match.end(1) 1486 return ln[:text_end], ln[text_end:] 1487 1488 def parseRecursive(ln, isNested): 1489 result = '' 1490 while len(ln): 1491 if isNested: 1492 found_end, _ = tryParseEnd(ln) 1493 if found_end: 1494 break 1495 1496 # %if cond %{ branch_if %} %else %{ branch_else %} 1497 cond, ln = tryParseIfCond(ln) 1498 if cond: 1499 branch_if, ln = parseRecursive(ln, isNested=True) 1500 found_end, ln = tryParseEnd(ln) 1501 if not found_end: 1502 raise ValueError("'%}' is missing for %if substitution") 1503 1504 branch_else = '' 1505 found_else, ln = tryParseElse(ln) 1506 if found_else: 1507 branch_else, ln = parseRecursive(ln, isNested=True) 1508 found_end, ln = tryParseEnd(ln) 1509 if not found_end: 1510 raise ValueError("'%}' is missing for %else substitution") 1511 1512 if BooleanExpression.evaluate(cond, conditions): 1513 result += branch_if 1514 else: 1515 result += branch_else 1516 continue 1517 1518 # The rest is handled as plain text. 1519 text, ln = parseText(ln, isNested) 1520 result += text 1521 1522 return result, ln 1523 1524 result, ln = parseRecursive(ln, isNested=False) 1525 assert len(ln) == 0 1526 return result 1527 1528 def processLine(ln): 1529 # Apply substitutions 1530 ln = substituteIfElse(escapePercents(ln)) 1531 for a,b in substitutions: 1532 if kIsWindows: 1533 b = b.replace("\\","\\\\") 1534 # re.compile() has a built-in LRU cache with 512 entries. In some 1535 # test suites lit ends up thrashing that cache, which made e.g. 1536 # check-llvm run 50% slower. Use an explicit, unbounded cache 1537 # to prevent that from happening. Since lit is fairly 1538 # short-lived, since the set of substitutions is fairly small, and 1539 # since thrashing has such bad consequences, not bounding the cache 1540 # seems reasonable. 1541 ln = _caching_re_compile(a).sub(str(b), escapePercents(ln)) 1542 1543 # Strip the trailing newline and any extra whitespace. 1544 return ln.strip() 1545 1546 def processLineToFixedPoint(ln): 1547 assert isinstance(recursion_limit, int) and recursion_limit >= 0 1548 origLine = ln 1549 steps = 0 1550 processed = processLine(ln) 1551 while processed != ln and steps < recursion_limit: 1552 ln = processed 1553 processed = processLine(ln) 1554 steps += 1 1555 1556 if processed != ln: 1557 raise ValueError("Recursive substitution of '%s' did not complete " 1558 "in the provided recursion limit (%s)" % \ 1559 (origLine, recursion_limit)) 1560 1561 return processed 1562 1563 process = processLine if recursion_limit is None else processLineToFixedPoint 1564 output = [] 1565 for directive in script: 1566 if isinstance(directive, SubstDirective): 1567 directive.adjust_substitutions(substitutions) 1568 else: 1569 if isinstance(directive, CommandDirective): 1570 line = directive.command 1571 else: 1572 # Can come from preamble_commands. 1573 assert isinstance(directive, str) 1574 line = directive 1575 output.append(unescapePercents(process(line))) 1576 1577 return output 1578 1579 1580class ParserKind(object): 1581 """ 1582 An enumeration representing the style of an integrated test keyword or 1583 command. 1584 1585 TAG: A keyword taking no value. Ex 'END.' 1586 COMMAND: A keyword taking a list of shell commands. Ex 'RUN:' 1587 LIST: A keyword taking a comma-separated list of values. 1588 BOOLEAN_EXPR: A keyword taking a comma-separated list of 1589 boolean expressions. Ex 'XFAIL:' 1590 INTEGER: A keyword taking a single integer. Ex 'ALLOW_RETRIES:' 1591 CUSTOM: A keyword with custom parsing semantics. 1592 DEFINE: A keyword taking a new lit substitution definition. Ex 1593 'DEFINE: %{name}=value' 1594 REDEFINE: A keyword taking a lit substitution redefinition. Ex 1595 'REDEFINE: %{name}=value' 1596 """ 1597 TAG = 0 1598 COMMAND = 1 1599 LIST = 2 1600 BOOLEAN_EXPR = 3 1601 INTEGER = 4 1602 CUSTOM = 5 1603 DEFINE = 6 1604 REDEFINE = 7 1605 1606 @staticmethod 1607 def allowedKeywordSuffixes(value): 1608 return { ParserKind.TAG: ['.'], 1609 ParserKind.COMMAND: [':'], 1610 ParserKind.LIST: [':'], 1611 ParserKind.BOOLEAN_EXPR: [':'], 1612 ParserKind.INTEGER: [':'], 1613 ParserKind.CUSTOM: [':', '.'], 1614 ParserKind.DEFINE: [':'], 1615 ParserKind.REDEFINE: [':'] 1616 } [value] 1617 1618 @staticmethod 1619 def str(value): 1620 return { ParserKind.TAG: 'TAG', 1621 ParserKind.COMMAND: 'COMMAND', 1622 ParserKind.LIST: 'LIST', 1623 ParserKind.BOOLEAN_EXPR: 'BOOLEAN_EXPR', 1624 ParserKind.INTEGER: 'INTEGER', 1625 ParserKind.CUSTOM: 'CUSTOM', 1626 ParserKind.DEFINE: 'DEFINE', 1627 ParserKind.REDEFINE: 'REDEFINE' 1628 } [value] 1629 1630 1631class IntegratedTestKeywordParser(object): 1632 """A parser for LLVM/Clang style integrated test scripts. 1633 1634 keyword: The keyword to parse for. It must end in either '.' or ':'. 1635 kind: An value of ParserKind. 1636 parser: A custom parser. This value may only be specified with 1637 ParserKind.CUSTOM. 1638 """ 1639 def __init__(self, keyword, kind, parser=None, initial_value=None): 1640 allowedSuffixes = ParserKind.allowedKeywordSuffixes(kind) 1641 if len(keyword) == 0 or keyword[-1] not in allowedSuffixes: 1642 if len(allowedSuffixes) == 1: 1643 raise ValueError("Keyword '%s' of kind '%s' must end in '%s'" 1644 % (keyword, ParserKind.str(kind), 1645 allowedSuffixes[0])) 1646 else: 1647 raise ValueError("Keyword '%s' of kind '%s' must end in " 1648 " one of '%s'" 1649 % (keyword, ParserKind.str(kind), 1650 ' '.join(allowedSuffixes))) 1651 1652 if parser is not None and kind != ParserKind.CUSTOM: 1653 raise ValueError("custom parsers can only be specified with " 1654 "ParserKind.CUSTOM") 1655 self.keyword = keyword 1656 self.kind = kind 1657 self.parsed_lines = [] 1658 self.value = initial_value 1659 self.parser = parser 1660 1661 if kind == ParserKind.COMMAND: 1662 self.parser = lambda line_number, line, output: \ 1663 self._handleCommand(line_number, line, output, 1664 self.keyword) 1665 elif kind == ParserKind.LIST: 1666 self.parser = self._handleList 1667 elif kind == ParserKind.BOOLEAN_EXPR: 1668 self.parser = self._handleBooleanExpr 1669 elif kind == ParserKind.INTEGER: 1670 self.parser = self._handleSingleInteger 1671 elif kind == ParserKind.TAG: 1672 self.parser = self._handleTag 1673 elif kind == ParserKind.CUSTOM: 1674 if parser is None: 1675 raise ValueError("ParserKind.CUSTOM requires a custom parser") 1676 self.parser = parser 1677 elif kind == ParserKind.DEFINE: 1678 self.parser = lambda line_number, line, output: \ 1679 self._handleSubst(line_number, line, output, 1680 self.keyword, new_subst=True) 1681 elif kind == ParserKind.REDEFINE: 1682 self.parser = lambda line_number, line, output: \ 1683 self._handleSubst(line_number, line, output, 1684 self.keyword, 1685 new_subst=False) 1686 else: 1687 raise ValueError("Unknown kind '%s'" % kind) 1688 1689 def parseLine(self, line_number, line): 1690 try: 1691 self.parsed_lines += [(line_number, line)] 1692 self.value = self.parser(line_number, line, self.value) 1693 except ValueError as e: 1694 raise ValueError(str(e) + ("\nin %s directive on test line %d" % 1695 (self.keyword, line_number))) 1696 1697 def getValue(self): 1698 return self.value 1699 1700 @staticmethod 1701 def _handleTag(line_number, line, output): 1702 """A helper for parsing TAG type keywords""" 1703 return (not line.strip() or output) 1704 1705 @staticmethod 1706 def _substituteLineNumbers(line_number, line): 1707 line = re.sub(r'%\(line\)', str(line_number), line) 1708 def replace_line_number(match): 1709 if match.group(1) == '+': 1710 return str(line_number + int(match.group(2))) 1711 if match.group(1) == '-': 1712 return str(line_number - int(match.group(2))) 1713 return re.sub(r'%\(line *([\+-]) *(\d+)\)', replace_line_number, line) 1714 1715 @classmethod 1716 def _handleCommand(cls, line_number, line, output, keyword): 1717 """A helper for parsing COMMAND type keywords""" 1718 # Substitute line number expressions. 1719 line = cls._substituteLineNumbers(line_number, line) 1720 1721 # Collapse lines with trailing '\\', or add line with line number to 1722 # start a new pipeline. 1723 if not output or not output[-1].add_continuation(line_number, keyword, 1724 line): 1725 if output is None: 1726 output = [] 1727 pdbg = "%dbg({keyword} at line {line_number})".format( 1728 keyword=keyword, 1729 line_number=line_number) 1730 assert re.match(kPdbgRegex + "$", pdbg), \ 1731 "kPdbgRegex expected to match actual %dbg usage" 1732 line = "{pdbg} {real_command}".format( 1733 pdbg=pdbg, 1734 real_command=line) 1735 output.append(CommandDirective(line_number, line_number, keyword, 1736 line)) 1737 return output 1738 1739 @staticmethod 1740 def _handleList(line_number, line, output): 1741 """A parser for LIST type keywords""" 1742 if output is None: 1743 output = [] 1744 output.extend([s.strip() for s in line.split(',')]) 1745 return output 1746 1747 @staticmethod 1748 def _handleSingleInteger(line_number, line, output): 1749 """A parser for INTEGER type keywords""" 1750 if output is None: 1751 output = [] 1752 try: 1753 n = int(line) 1754 except ValueError: 1755 raise ValueError("INTEGER parser requires the input to be an integer (got {})".format(line)) 1756 output.append(n) 1757 return output 1758 1759 @staticmethod 1760 def _handleBooleanExpr(line_number, line, output): 1761 """A parser for BOOLEAN_EXPR type keywords""" 1762 parts = [s.strip() for s in line.split(',') if s.strip() != ''] 1763 if output and output[-1][-1] == '\\': 1764 output[-1] = output[-1][:-1] + parts[0] 1765 del parts[0] 1766 if output is None: 1767 output = [] 1768 output.extend(parts) 1769 # Evaluate each expression to verify syntax. 1770 # We don't want any results, just the raised ValueError. 1771 for s in output: 1772 if s != '*' and not s.endswith('\\'): 1773 BooleanExpression.evaluate(s, []) 1774 return output 1775 1776 @classmethod 1777 def _handleSubst(cls, line_number, line, output, keyword, new_subst): 1778 """A parser for DEFINE and REDEFINE type keywords""" 1779 line = cls._substituteLineNumbers(line_number, line) 1780 if output and output[-1].add_continuation(line_number, keyword, line): 1781 return output 1782 if output is None: 1783 output = [] 1784 output.append(SubstDirective(line_number, line_number, keyword, 1785 new_subst, line)) 1786 return output 1787 1788 1789def _parseKeywords(sourcepath, additional_parsers=[], 1790 require_script=True): 1791 """_parseKeywords 1792 1793 Scan an LLVM/Clang style integrated test script and extract all the lines 1794 pertaining to a special parser. This includes 'RUN', 'XFAIL', 'REQUIRES', 1795 'UNSUPPORTED', 'ALLOW_RETRIES', 'END', 'DEFINE', 'REDEFINE', as well as 1796 other specified custom parsers. 1797 1798 Returns a dictionary mapping each custom parser to its value after 1799 parsing the test. 1800 """ 1801 # Install the built-in keyword parsers. 1802 script = [] 1803 builtin_parsers = [ 1804 IntegratedTestKeywordParser('RUN:', ParserKind.COMMAND, initial_value=script), 1805 IntegratedTestKeywordParser('XFAIL:', ParserKind.BOOLEAN_EXPR), 1806 IntegratedTestKeywordParser('REQUIRES:', ParserKind.BOOLEAN_EXPR), 1807 IntegratedTestKeywordParser('UNSUPPORTED:', ParserKind.BOOLEAN_EXPR), 1808 IntegratedTestKeywordParser('ALLOW_RETRIES:', ParserKind.INTEGER), 1809 IntegratedTestKeywordParser('END.', ParserKind.TAG), 1810 IntegratedTestKeywordParser('DEFINE:', ParserKind.DEFINE, 1811 initial_value=script), 1812 IntegratedTestKeywordParser('REDEFINE:', ParserKind.REDEFINE, 1813 initial_value=script) 1814 ] 1815 keyword_parsers = {p.keyword: p for p in builtin_parsers} 1816 1817 # Install user-defined additional parsers. 1818 for parser in additional_parsers: 1819 if not isinstance(parser, IntegratedTestKeywordParser): 1820 raise ValueError('Additional parser must be an instance of ' 1821 'IntegratedTestKeywordParser') 1822 if parser.keyword in keyword_parsers: 1823 raise ValueError("Parser for keyword '%s' already exists" 1824 % parser.keyword) 1825 keyword_parsers[parser.keyword] = parser 1826 1827 # Collect the test lines from the script. 1828 for line_number, command_type, ln in \ 1829 parseIntegratedTestScriptCommands(sourcepath, 1830 keyword_parsers.keys()): 1831 parser = keyword_parsers[command_type] 1832 parser.parseLine(line_number, ln) 1833 if command_type == 'END.' and parser.getValue() is True: 1834 break 1835 1836 # Verify the script contains a run line. 1837 if require_script and not any(isinstance(directive, CommandDirective) 1838 for directive in script): 1839 raise ValueError("Test has no 'RUN:' line") 1840 1841 # Check for unterminated run or subst lines. 1842 # 1843 # If, after a line continuation for one kind of directive (e.g., 'RUN:', 1844 # 'DEFINE:', 'REDEFINE:') in script, the next directive in script is a 1845 # different kind, then the '\\' remains on the former, and we report it 1846 # here. 1847 for directive in script: 1848 if directive.needs_continuation(): 1849 raise ValueError(f"Test has unterminated '{directive.keyword}' " 1850 f"directive (with '\\') " 1851 f"{directive.get_location()}") 1852 1853 # Check boolean expressions for unterminated lines. 1854 for key in keyword_parsers: 1855 kp = keyword_parsers[key] 1856 if kp.kind != ParserKind.BOOLEAN_EXPR: 1857 continue 1858 value = kp.getValue() 1859 if value and value[-1][-1] == '\\': 1860 raise ValueError("Test has unterminated '{key}' lines (with '\\')" 1861 .format(key=key)) 1862 1863 # Make sure there's at most one ALLOW_RETRIES: line 1864 allowed_retries = keyword_parsers['ALLOW_RETRIES:'].getValue() 1865 if allowed_retries and len(allowed_retries) > 1: 1866 raise ValueError("Test has more than one ALLOW_RETRIES lines") 1867 1868 return {p.keyword: p.getValue() for p in keyword_parsers.values()} 1869 1870 1871def parseIntegratedTestScript(test, additional_parsers=[], 1872 require_script=True): 1873 """parseIntegratedTestScript - Scan an LLVM/Clang style integrated test 1874 script and extract the lines to 'RUN' as well as 'XFAIL', 'REQUIRES', 1875 'UNSUPPORTED' and 'ALLOW_RETRIES' information into the given test. 1876 1877 If additional parsers are specified then the test is also scanned for the 1878 keywords they specify and all matches are passed to the custom parser. 1879 1880 If 'require_script' is False an empty script 1881 may be returned. This can be used for test formats where the actual script 1882 is optional or ignored. 1883 """ 1884 # Parse the test sources and extract test properties 1885 try: 1886 parsed = _parseKeywords(test.getSourcePath(), additional_parsers, 1887 require_script) 1888 except ValueError as e: 1889 return lit.Test.Result(Test.UNRESOLVED, str(e)) 1890 script = parsed['RUN:'] or [] 1891 assert parsed['DEFINE:'] == script 1892 assert parsed['REDEFINE:'] == script 1893 test.xfails += parsed['XFAIL:'] or [] 1894 test.requires += parsed['REQUIRES:'] or [] 1895 test.unsupported += parsed['UNSUPPORTED:'] or [] 1896 if parsed['ALLOW_RETRIES:']: 1897 test.allowed_retries = parsed['ALLOW_RETRIES:'][0] 1898 1899 # Enforce REQUIRES: 1900 missing_required_features = test.getMissingRequiredFeatures() 1901 if missing_required_features: 1902 msg = ', '.join(missing_required_features) 1903 return lit.Test.Result(Test.UNSUPPORTED, 1904 "Test requires the following unavailable " 1905 "features: %s" % msg) 1906 1907 # Enforce UNSUPPORTED: 1908 unsupported_features = test.getUnsupportedFeatures() 1909 if unsupported_features: 1910 msg = ', '.join(unsupported_features) 1911 return lit.Test.Result( 1912 Test.UNSUPPORTED, 1913 "Test does not support the following features " 1914 "and/or targets: %s" % msg) 1915 1916 # Enforce limit_to_features. 1917 if not test.isWithinFeatureLimits(): 1918 msg = ', '.join(test.config.limit_to_features) 1919 return lit.Test.Result(Test.UNSUPPORTED, 1920 "Test does not require any of the features " 1921 "specified in limit_to_features: %s" % msg) 1922 1923 return script 1924 1925 1926def _runShTest(test, litConfig, useExternalSh, script, tmpBase): 1927 def runOnce(execdir): 1928 if useExternalSh: 1929 res = executeScript(test, litConfig, tmpBase, script, execdir) 1930 else: 1931 res = executeScriptInternal(test, litConfig, tmpBase, script, execdir) 1932 if isinstance(res, lit.Test.Result): 1933 return res 1934 1935 out,err,exitCode,timeoutInfo = res 1936 if exitCode == 0: 1937 status = Test.PASS 1938 else: 1939 if timeoutInfo is None: 1940 status = Test.FAIL 1941 else: 1942 status = Test.TIMEOUT 1943 return out,err,exitCode,timeoutInfo,status 1944 1945 # Create the output directory if it does not already exist. 1946 lit.util.mkdir_p(os.path.dirname(tmpBase)) 1947 1948 # Re-run failed tests up to test.allowed_retries times. 1949 execdir = os.path.dirname(test.getExecPath()) 1950 attempts = test.allowed_retries + 1 1951 for i in range(attempts): 1952 res = runOnce(execdir) 1953 if isinstance(res, lit.Test.Result): 1954 return res 1955 1956 out,err,exitCode,timeoutInfo,status = res 1957 if status != Test.FAIL: 1958 break 1959 1960 # If we had to run the test more than once, count it as a flaky pass. These 1961 # will be printed separately in the test summary. 1962 if i > 0 and status == Test.PASS: 1963 status = Test.FLAKYPASS 1964 1965 # Form the output log. 1966 output = """Script:\n--\n%s\n--\nExit Code: %d\n""" % ( 1967 '\n'.join(script), exitCode) 1968 1969 if timeoutInfo is not None: 1970 output += """Timeout: %s\n""" % (timeoutInfo,) 1971 output += "\n" 1972 1973 # Append the outputs, if present. 1974 if out: 1975 output += """Command Output (stdout):\n--\n%s\n--\n""" % (out,) 1976 if err: 1977 output += """Command Output (stderr):\n--\n%s\n--\n""" % (err,) 1978 1979 return lit.Test.Result(status, output) 1980 1981 1982def executeShTest(test, litConfig, useExternalSh, 1983 extra_substitutions=[], 1984 preamble_commands=[]): 1985 if test.config.unsupported: 1986 return lit.Test.Result(Test.UNSUPPORTED, 'Test is unsupported') 1987 1988 script = list(preamble_commands) 1989 parsed = parseIntegratedTestScript(test, require_script=not script) 1990 if isinstance(parsed, lit.Test.Result): 1991 return parsed 1992 script += parsed 1993 1994 if litConfig.noExecute: 1995 return lit.Test.Result(Test.PASS) 1996 1997 tmpDir, tmpBase = getTempPaths(test) 1998 substitutions = list(extra_substitutions) 1999 substitutions += getDefaultSubstitutions(test, tmpDir, tmpBase, 2000 normalize_slashes=useExternalSh) 2001 conditions = { feature: True for feature in test.config.available_features } 2002 script = applySubstitutions(script, substitutions, conditions, 2003 recursion_limit=test.config.recursiveExpansionLimit) 2004 2005 return _runShTest(test, litConfig, useExternalSh, script, tmpBase) 2006