diff options
Diffstat (limited to 'utils/lit')
-rw-r--r-- | utils/lit/lit/TestRunner.py | 241 | ||||
-rw-r--r-- | utils/lit/lit/formats/googletest.py | 5 | ||||
-rwxr-xr-x | utils/lit/lit/main.py | 3 | ||||
-rw-r--r-- | utils/lit/tests/selecting.py | 5 |
4 files changed, 182 insertions, 72 deletions
diff --git a/utils/lit/lit/TestRunner.py b/utils/lit/lit/TestRunner.py index 37b03cc19f85..8260d3813345 100644 --- a/utils/lit/lit/TestRunner.py +++ b/utils/lit/lit/TestRunner.py @@ -5,6 +5,11 @@ import platform import tempfile import threading +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + from lit.ShCommands import GlobItem import lit.ShUtil as ShUtil import lit.Test as Test @@ -221,6 +226,155 @@ def updateEnv(env, cmd): env.env[key] = val cmd.args = cmd.args[arg_idx+1:] +def executeBuiltinEcho(cmd, shenv): + """Interpret a redirected echo command""" + opened_files = [] + stdin, stdout, stderr = processRedirects(cmd, subprocess.PIPE, shenv, + opened_files) + if stdin != subprocess.PIPE or stderr != subprocess.PIPE: + raise InternalShellError( + cmd, "stdin and stderr redirects not supported for echo") + + # Some tests have un-redirected echo commands to help debug test failures. + # Buffer our output and return it to the caller. + is_redirected = True + if stdout == subprocess.PIPE: + is_redirected = False + stdout = StringIO() + elif kIsWindows: + # Reopen stdout in binary mode to avoid CRLF translation. The versions + # of echo we are replacing on Windows all emit plain LF, and the LLVM + # tests now depend on this. + stdout = open(stdout.name, stdout.mode + 'b') + opened_files.append((None, None, stdout, None)) + + # Implement echo flags. We only support -e and -n, and not yet in + # combination. We have to ignore unknown flags, because `echo "-D FOO"` + # prints the dash. + args = cmd.args[1:] + interpret_escapes = False + write_newline = True + while len(args) >= 1 and args[0] in ('-e', '-n'): + flag = args[0] + args = args[1:] + if flag == '-e': + interpret_escapes = True + elif flag == '-n': + write_newline = False + + def maybeUnescape(arg): + if not interpret_escapes: + return arg + # Python string escapes and "echo" escapes are obviously different, but + # this should be enough for the LLVM test suite. + return arg.decode('string_escape') + + if args: + for arg in args[:-1]: + stdout.write(maybeUnescape(arg)) + stdout.write(' ') + stdout.write(maybeUnescape(args[-1])) + if write_newline: + stdout.write('\n') + + for (name, mode, f, path) in opened_files: + f.close() + + if not is_redirected: + return stdout.getvalue() + return "" + +def processRedirects(cmd, stdin_source, cmd_shenv, opened_files): + """Return the standard fds for cmd after applying redirects + + Returns the three standard file descriptors for the new child process. Each + fd may be an open, writable file object or a sentinel value from the + subprocess module. + """ + + # Apply the redirections, we use (N,) as a sentinel to indicate stdin, + # stdout, stderr for N equal to 0, 1, or 2 respectively. Redirects to or + # from a file are represented with a list [file, mode, file-object] + # where file-object is initially None. + redirects = [(0,), (1,), (2,)] + for (op, filename) in cmd.redirects: + if op == ('>',2): + redirects[2] = [filename, 'w', None] + elif op == ('>>',2): + redirects[2] = [filename, 'a', None] + elif op == ('>&',2) and filename in '012': + redirects[2] = redirects[int(filename)] + elif op == ('>&',) or op == ('&>',): + redirects[1] = redirects[2] = [filename, 'w', None] + elif op == ('>',): + redirects[1] = [filename, 'w', None] + elif op == ('>>',): + redirects[1] = [filename, 'a', None] + elif op == ('<',): + redirects[0] = [filename, 'r', None] + else: + raise InternalShellError(cmd, "Unsupported redirect: %r" % (r,)) + + # Open file descriptors in a second pass. + std_fds = [None, None, None] + for (index, r) in enumerate(redirects): + # Handle the sentinel values for defaults up front. + if isinstance(r, tuple): + if r == (0,): + fd = stdin_source + elif r == (1,): + if index == 0: + raise InternalShellError(cmd, "Unsupported redirect for stdin") + elif index == 1: + fd = subprocess.PIPE + else: + fd = subprocess.STDOUT + elif r == (2,): + if index != 2: + raise InternalShellError(cmd, "Unsupported redirect on stdout") + fd = subprocess.PIPE + else: + raise InternalShellError(cmd, "Bad redirect") + std_fds[index] = fd + continue + + (filename, mode, fd) = r + + # Check if we already have an open fd. This can happen if stdout and + # stderr go to the same place. + if fd is not None: + std_fds[index] = fd + continue + + redir_filename = None + name = expand_glob(filename, cmd_shenv.cwd) + if len(name) != 1: + raise InternalShellError(cmd, "Unsupported: glob in " + "redirect expanded to multiple files") + name = name[0] + if kAvoidDevNull and name == '/dev/null': + fd = tempfile.TemporaryFile(mode=mode) + elif kIsWindows and name == '/dev/tty': + # Simulate /dev/tty on Windows. + # "CON" is a special filename for the console. + fd = open("CON", mode) + else: + # Make sure relative paths are relative to the cwd. + redir_filename = os.path.join(cmd_shenv.cwd, name) + fd = open(redir_filename, mode) + # Workaround a Win32 and/or subprocess bug when appending. + # + # FIXME: Actually, this is probably an instance of PR6753. + if mode == 'a': + fd.seek(0, 2) + # Mutate the underlying redirect list so that we can redirect stdout + # and stderr to the same place without opening the file twice. + r[2] = fd + opened_files.append((filename, mode, fd) + (redir_filename,)) + std_fds[index] = fd + + return std_fds + def _executeShCmd(cmd, shenv, results, timeoutHelper): if timeoutHelper.timeoutReached(): # Prevent further recursion if the timeout has been hit @@ -269,6 +423,17 @@ def _executeShCmd(cmd, shenv, results, timeoutHelper): # following Popen calls will fail instead. return 0 + # Handle "echo" as a builtin if it is not part of a pipeline. This greatly + # speeds up tests that construct input files by repeatedly echo-appending to + # a file. + # FIXME: Standardize on the builtin echo implementation. We can use a + # temporary file to sidestep blocking pipe write issues. + if cmd.commands[0].args[0] == 'echo' and len(cmd.commands) == 1: + output = executeBuiltinEcho(cmd.commands[0], shenv) + results.append(ShellCommandResult(cmd.commands[0], output, "", 0, + False)) + return 0 + if cmd.commands[0].args[0] == 'export': if len(cmd.commands) != 1: raise ValueError("'export' cannot be part of a pipeline") @@ -278,7 +443,7 @@ def _executeShCmd(cmd, shenv, results, timeoutHelper): return 0 procs = [] - input = subprocess.PIPE + default_stdin = subprocess.PIPE stderrTempFiles = [] opened_files = [] named_temp_files = [] @@ -295,72 +460,8 @@ def _executeShCmd(cmd, shenv, results, timeoutHelper): cmd_shenv = ShellEnvironment(shenv.cwd, shenv.env) updateEnv(cmd_shenv, j) - # Apply the redirections, we use (N,) as a sentinel to indicate stdin, - # stdout, stderr for N equal to 0, 1, or 2 respectively. Redirects to or - # from a file are represented with a list [file, mode, file-object] - # where file-object is initially None. - redirects = [(0,), (1,), (2,)] - for r in j.redirects: - if r[0] == ('>',2): - redirects[2] = [r[1], 'w', None] - elif r[0] == ('>>',2): - redirects[2] = [r[1], 'a', None] - elif r[0] == ('>&',2) and r[1] in '012': - redirects[2] = redirects[int(r[1])] - elif r[0] == ('>&',) or r[0] == ('&>',): - redirects[1] = redirects[2] = [r[1], 'w', None] - elif r[0] == ('>',): - redirects[1] = [r[1], 'w', None] - elif r[0] == ('>>',): - redirects[1] = [r[1], 'a', None] - elif r[0] == ('<',): - redirects[0] = [r[1], 'r', None] - else: - raise InternalShellError(j,"Unsupported redirect: %r" % (r,)) - - # Map from the final redirections to something subprocess can handle. - final_redirects = [] - for index,r in enumerate(redirects): - if r == (0,): - result = input - elif r == (1,): - if index == 0: - raise InternalShellError(j,"Unsupported redirect for stdin") - elif index == 1: - result = subprocess.PIPE - else: - result = subprocess.STDOUT - elif r == (2,): - if index != 2: - raise InternalShellError(j,"Unsupported redirect on stdout") - result = subprocess.PIPE - else: - if r[2] is None: - redir_filename = None - name = expand_glob(r[0], cmd_shenv.cwd) - if len(name) != 1: - raise InternalShellError(j,"Unsupported: glob in redirect expanded to multiple files") - name = name[0] - if kAvoidDevNull and name == '/dev/null': - r[2] = tempfile.TemporaryFile(mode=r[1]) - elif kIsWindows and name == '/dev/tty': - # Simulate /dev/tty on Windows. - # "CON" is a special filename for the console. - r[2] = open("CON", r[1]) - else: - # Make sure relative paths are relative to the cwd. - redir_filename = os.path.join(cmd_shenv.cwd, name) - r[2] = open(redir_filename, r[1]) - # Workaround a Win32 and/or subprocess bug when appending. - # - # FIXME: Actually, this is probably an instance of PR6753. - if r[1] == 'a': - r[2].seek(0, 2) - opened_files.append(tuple(r) + (redir_filename,)) - result = r[2] - final_redirects.append(result) - - stdin, stdout, stderr = final_redirects + stdin, stdout, stderr = processRedirects(j, default_stdin, cmd_shenv, + opened_files) # If stderr wants to come from stdout, but stdout isn't a pipe, then put # stderr on a pipe and treat it as stdout. @@ -428,11 +529,11 @@ def _executeShCmd(cmd, shenv, results, timeoutHelper): # Update the current stdin source. if stdout == subprocess.PIPE: - input = procs[-1].stdout + default_stdin = procs[-1].stdout elif stderrIsStdout: - input = procs[-1].stderr + default_stdin = procs[-1].stderr else: - input = subprocess.PIPE + default_stdin = subprocess.PIPE # Explicitly close any redirected files. We need to do this now because we # need to release any handles we may have on the temporary files (important diff --git a/utils/lit/lit/formats/googletest.py b/utils/lit/lit/formats/googletest.py index b683f7c7db8e..9c55e71d2330 100644 --- a/utils/lit/lit/formats/googletest.py +++ b/utils/lit/lit/formats/googletest.py @@ -78,7 +78,10 @@ class GoogleTest(TestFormat): litConfig, localConfig): source_path = testSuite.getSourcePath(path_in_suite) for subdir in self.test_sub_dirs: - for fn in lit.util.listdir_files(os.path.join(source_path, subdir), + dir_path = os.path.join(source_path, subdir) + if not os.path.isdir(dir_path): + continue + for fn in lit.util.listdir_files(dir_path, suffixes={self.test_suffix}): # Discover the tests in this executable. execpath = os.path.join(source_path, subdir, fn) diff --git a/utils/lit/lit/main.py b/utils/lit/lit/main.py index a7f407fc210c..530f962d336d 100755 --- a/utils/lit/lit/main.py +++ b/utils/lit/lit/main.py @@ -262,7 +262,8 @@ def main_with_tmp(builtinParameters): selection_group.add_argument("--filter", metavar="REGEX", help=("Only run tests with paths matching the given " "regular expression"), - action="store", default=None) + action="store", + default=os.environ.get("LIT_FILTER")) selection_group.add_argument("--num-shards", dest="numShards", metavar="M", help="Split testsuite into M pieces and only run one", action="store", type=int, diff --git a/utils/lit/tests/selecting.py b/utils/lit/tests/selecting.py index 72d6fbabdc93..19ba240f9b0f 100644 --- a/utils/lit/tests/selecting.py +++ b/utils/lit/tests/selecting.py @@ -7,6 +7,11 @@ # RUN: %{lit} --filter 'o[a-z]e' %{inputs}/discovery | FileCheck --check-prefix=CHECK-FILTER %s # CHECK-FILTER: Testing: 2 of 5 tests +# Check that regex-filtering based on environment variables work. +# +# RUN: LIT_FILTER='o[a-z]e' %{lit} %{inputs}/discovery | FileCheck --check-prefix=CHECK-FILTER-ENV %s +# CHECK-FILTER-ENV: Testing: 2 of 5 tests + # Check that maximum counts work # |