import lit.ShUtil as ShUtil
import lit.Test as Test
import lit.util
+from lit.util import to_bytes, to_string
class InternalShellError(Exception):
def __init__(self, command, message):
# Use temporary files to replace /dev/null on Windows.
kAvoidDevNull = kIsWindows
-def executeShCmd(cmd, cfg, cwd, results):
+class ShellEnvironment(object):
+
+ """Mutable shell environment containing things like CWD and env vars.
+
+ Environment variables are not implemented, but cwd tracking is.
+ """
+
+ def __init__(self, cwd, env):
+ self.cwd = cwd
+ self.env = env
+
+def executeShCmd(cmd, shenv, results):
if isinstance(cmd, ShUtil.Seq):
if cmd.op == ';':
- res = executeShCmd(cmd.lhs, cfg, cwd, results)
- return executeShCmd(cmd.rhs, cfg, cwd, results)
+ res = executeShCmd(cmd.lhs, shenv, results)
+ return executeShCmd(cmd.rhs, shenv, results)
if cmd.op == '&':
raise InternalShellError(cmd,"unsupported shell operator: '&'")
if cmd.op == '||':
- res = executeShCmd(cmd.lhs, cfg, cwd, results)
+ res = executeShCmd(cmd.lhs, shenv, results)
if res != 0:
- res = executeShCmd(cmd.rhs, cfg, cwd, results)
+ res = executeShCmd(cmd.rhs, shenv, results)
return res
if cmd.op == '&&':
- res = executeShCmd(cmd.lhs, cfg, cwd, results)
+ res = executeShCmd(cmd.lhs, shenv, results)
if res is None:
return res
if res == 0:
- res = executeShCmd(cmd.rhs, cfg, cwd, results)
+ res = executeShCmd(cmd.rhs, shenv, results)
return res
raise ValueError('Unknown shell command: %r' % cmd.op)
-
assert isinstance(cmd, ShUtil.Pipeline)
+
+ # Handle shell builtins first.
+ if cmd.commands[0].args[0] == 'cd':
+ # Update the cwd in the environment.
+ if len(cmd.commands[0].args) != 2:
+ raise ValueError('cd supports only one argument')
+ newdir = cmd.commands[0].args[1]
+ if os.path.isabs(newdir):
+ shenv.cwd = newdir
+ else:
+ shenv.cwd = os.path.join(shenv.cwd, newdir)
+ return 0
+
procs = []
input = subprocess.PIPE
stderrTempFiles = []
if kAvoidDevNull and r[0] == '/dev/null':
r[2] = tempfile.TemporaryFile(mode=r[1])
else:
- r[2] = open(r[0], r[1])
+ # Make sure relative paths are relative to the cwd.
+ redir_filename = os.path.join(shenv.cwd, r[0])
+ 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.
# Resolve the executable path ourselves.
args = list(j.args)
- executable = lit.util.which(args[0], cfg.environment['PATH'])
+ executable = lit.util.which(args[0], shenv.env['PATH'])
if not executable:
raise InternalShellError(j, '%r: command not found' % j.args[0])
args[i] = f.name
try:
- procs.append(subprocess.Popen(args, cwd=cwd,
+ procs.append(subprocess.Popen(args, cwd=shenv.cwd,
executable = executable,
stdin = stdin,
stdout = stdout,
stderr = stderr,
- env = cfg.environment,
+ env = shenv.env,
close_fds = kUseCloseFDs))
except OSError as e:
raise InternalShellError(j, 'Could not create process due to {}'.format(e))
results = []
try:
- exitCode = executeShCmd(cmd, test.config, cwd, results)
+ shenv = ShellEnvironment(cwd, test.config.environment)
+ exitCode = executeShCmd(cmd, shenv, results)
except InternalShellError:
e = sys.exc_info()[1]
exitCode = 127
# UTF-8, so we convert the outputs to UTF-8 before returning. This way the
# remaining code can work with "strings" agnostic of the executing Python
# version.
-
- def to_bytes(str):
- # Encode to UTF-8 to get binary data.
- return str.encode('utf-8')
- def to_string(bytes):
- if isinstance(bytes, str):
- return bytes
- return to_bytes(bytes)
-
- keywords = ('RUN:', 'XFAIL:', 'REQUIRES:', 'END.')
+
+ keywords = ['RUN:', 'XFAIL:', 'REQUIRES:', 'UNSUPPORTED:', 'END.']
keywords_re = re.compile(
to_bytes("(%s)(.*)\n" % ("|".join(k for k in keywords),)))
finally:
f.close()
+
def parseIntegratedTestScript(test, normalize_slashes=False,
- extra_substitutions=[]):
+ extra_substitutions=[], require_script=True):
"""parseIntegratedTestScript - Scan an LLVM/Clang style integrated test
script and extract the lines to 'RUN' as well as 'XFAIL' and 'REQUIRES'
- information. The RUN lines also will have variable substitution performed.
+ and 'UNSUPPORTED' information. The RUN lines also will have variable
+ substitution performed. If 'require_script' is False an empty script may be
+ returned. This can be used for test formats where the actual script is
+ optional or ignored.
"""
# Get the temporary location, this is always relative to the test suite
# Collect the test lines from the script.
script = []
requires = []
+ unsupported = []
for line_number, command_type, ln in \
parseIntegratedTestScriptCommands(sourcepath):
if command_type == 'RUN':
test.xfails.extend([s.strip() for s in ln.split(',')])
elif command_type == 'REQUIRES':
requires.extend([s.strip() for s in ln.split(',')])
+ elif command_type == 'UNSUPPORTED':
+ unsupported.extend([s.strip() for s in ln.split(',')])
elif command_type == 'END':
# END commands are only honored if the rest of the line is empty.
if not ln.strip():
for ln in script]
# Verify the script contains a run line.
- if not script:
+ if require_script and not script:
return lit.Test.Result(Test.UNRESOLVED, "Test has no run line!")
# Check for unterminated run lines.
- if script[-1][-1] == '\\':
+ if script and script[-1][-1] == '\\':
return lit.Test.Result(Test.UNRESOLVED,
"Test has unterminated run lines (with '\\')")
msg = ', '.join(missing_required_features)
return lit.Test.Result(Test.UNSUPPORTED,
"Test requires the following features: %s" % msg)
+ unsupported_features = [f for f in unsupported
+ if f in test.config.available_features]
+ if unsupported_features:
+ msg = ', '.join(unsupported_features)
+ return lit.Test.Result(Test.UNSUPPORTED,
+ "Test is unsupported with the following features: %s" % msg)
return script,tmpBase,execdir
-def executeShTest(test, litConfig, useExternalSh,
- extra_substitutions=[]):
- if test.config.unsupported:
- return (Test.UNSUPPORTED, 'Test is unsupported')
-
- res = parseIntegratedTestScript(test, useExternalSh, extra_substitutions)
- if isinstance(res, lit.Test.Result):
- return res
- if litConfig.noExecute:
- return lit.Test.Result(Test.PASS)
-
- script, tmpBase, execdir = res
-
+def _runShTest(test, litConfig, useExternalSh,
+ script, tmpBase, execdir):
# Create the output directory if it does not already exist.
lit.util.mkdir_p(os.path.dirname(tmpBase))
output += """Command Output (stderr):\n--\n%s\n--\n""" % (err,)
return lit.Test.Result(status, output)
+
+
+def executeShTest(test, litConfig, useExternalSh,
+ extra_substitutions=[]):
+ if test.config.unsupported:
+ return (Test.UNSUPPORTED, 'Test is unsupported')
+
+ res = parseIntegratedTestScript(test, useExternalSh, extra_substitutions)
+ if isinstance(res, lit.Test.Result):
+ return res
+ if litConfig.noExecute:
+ return lit.Test.Result(Test.PASS)
+
+ script, tmpBase, execdir = res
+ return _runShTest(test, litConfig, useExternalSh, script, tmpBase, execdir)
+