summaryrefslogtreecommitdiff
path: root/utils/lit
diff options
context:
space:
mode:
Diffstat (limited to 'utils/lit')
-rw-r--r--utils/lit/lit/TestRunner.py241
-rw-r--r--utils/lit/lit/formats/googletest.py5
-rwxr-xr-xutils/lit/lit/main.py3
-rw-r--r--utils/lit/tests/selecting.py5
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
#