xref: /openbsd/gnu/llvm/llvm/utils/lit/lit/TestRunner.py (revision d415bd75)
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