Add remote testing support to the lit config.
Executors can be specified at configure time by using the -DLIBCXX_EXECUTOR=""
option. Examples include:
$ cmake <other_flags> -DLIBCXX_EXECUTOR="TimeoutExecutor(30,LocalExecutor())"
This runs individual tests with a maximum duration
$ cmake <other_flags> -DLIBCXX_EXECUTOR="SSHExecutor('hostname','username')"
This runs tests on a remote target, using scp to shuttle binaries to the
target, and ssh to invoke commands there.
$ cmake <other_flags> -DLIBCXX_EXECUTOR="PrefixExecutor('/path/to/run/script',LocalExecutor())"
This assumes the script knows how to copy run the executables passed to it,
and allows for the ultimate control. This is useful for running things
inside emulators like Valgrind & QEMU.
TODO: This doesn't claim to support ShTest tests yet, that will take a bit more
thought & finagling (I'm still not sure how to orchestrate copy-in for those cases.
I've also punted on what to do about tests that read data files. The testsuite
has several tests that need to read *.dat files placed next to them, and
currently those aren't copied over when using, say, an SSHExecutor. The
affected tests are:
libc++ :: std/input.output/file.streams/fstreams/filebuf.virtuals/pbackfail.pass.cpp
libc++ :: std/input.output/file.streams/fstreams/filebuf.virtuals/underflow.pass.cpp
libc++ :: std/input.output/file.streams/fstreams/ifstream.assign/member_swap.pass.cpp
libc++ :: std/input.output/file.streams/fstreams/ifstream.assign/move_assign.pass.cpp
libc++ :: std/input.output/file.streams/fstreams/ifstream.assign/nonmember_swap.pass.cpp
libc++ :: std/input.output/file.streams/fstreams/ifstream.cons/move.pass.cpp
libc++ :: std/input.output/file.streams/fstreams/ifstream.cons/pointer.pass.cpp
libc++ :: std/input.output/file.streams/fstreams/ifstream.cons/string.pass.cpp
libc++ :: std/input.output/file.streams/fstreams/ifstream.members/close.pass.cpp
libc++ :: std/input.output/file.streams/fstreams/ifstream.members/open_pointer.pass.cpp
libc++ :: std/input.output/file.streams/fstreams/ifstream.members/open_string.pass.cpp
libc++ :: std/input.output/file.streams/fstreams/ifstream.members/rdbuf.pass.cpp
libc++ :: std/localization/locales/locale.convenience/conversions/conversions.buffer/pbackfail.pass.cpp
libc++ :: std/localization/locales/locale.convenience/conversions/conversions.buffer/underflow.pass.cpp
Note: One thing to watch out for when using the SSHExecutor for cross-testing is
that you'll also want to specify a TargetInfo object (so that the host's
features aren't used for available-features checks and flags setup).
http://reviews.llvm.org/D7380
git-svn-id: https://llvm.org/svn/llvm-project/libcxx/trunk@230592 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
166
test/libcxx/test/executor.py
Normal file
166
test/libcxx/test/executor.py
Normal file
@@ -0,0 +1,166 @@
|
||||
import os
|
||||
|
||||
import tracing
|
||||
|
||||
from lit.util import executeCommand # pylint: disable=import-error
|
||||
|
||||
|
||||
class Executor(object):
|
||||
def run(self, exe_path, cmd, local_cwd, env=None):
|
||||
"""Execute a command.
|
||||
Be very careful not to change shared state in this function.
|
||||
Executor objects are shared between python processes in `lit -jN`.
|
||||
Args:
|
||||
exe_path: str: Local path to the executable to be run
|
||||
cmd: [str]: subprocess.call style command
|
||||
local_cwd: str: Local path to the working directory
|
||||
env: {str: str}: Environment variables to execute under
|
||||
Returns:
|
||||
out, err, exitCode
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class LocalExecutor(Executor):
|
||||
def __init__(self):
|
||||
super(LocalExecutor, self).__init__()
|
||||
|
||||
def run(self, exe_path, cmd=None, work_dir='.', env=None):
|
||||
cmd = cmd or [exe_path]
|
||||
env_cmd = []
|
||||
if env:
|
||||
env_cmd += ['env']
|
||||
env_cmd += ['%s=%s' % (k, v) for k, v in env.items()]
|
||||
if work_dir == '.':
|
||||
work_dir = os.getcwd()
|
||||
return executeCommand(env_cmd + cmd, cwd=work_dir)
|
||||
|
||||
|
||||
class PrefixExecutor(Executor):
|
||||
"""Prefix an executor with some other command wrapper.
|
||||
|
||||
Most useful for setting ulimits on commands, or running an emulator like
|
||||
qemu and valgrind.
|
||||
"""
|
||||
def __init__(self, commandPrefix, chain):
|
||||
super(PrefixExecutor, self).__init__()
|
||||
|
||||
self.commandPrefix = commandPrefix
|
||||
self.chain = chain
|
||||
|
||||
def run(self, exe_path, cmd=None, work_dir='.', env=None):
|
||||
cmd = cmd or [exe_path]
|
||||
return self.chain.run(self.commandPrefix + cmd, work_dir, env=env)
|
||||
|
||||
|
||||
class PostfixExecutor(Executor):
|
||||
"""Postfix an executor with some args."""
|
||||
def __init__(self, commandPostfix, chain):
|
||||
super(PostfixExecutor, self).__init__()
|
||||
|
||||
self.commandPostfix = commandPostfix
|
||||
self.chain = chain
|
||||
|
||||
def run(self, exe_path, cmd=None, work_dir='.', env=None):
|
||||
cmd = cmd or [exe_path]
|
||||
return self.chain.run(cmd + self.commandPostfix, work_dir, env=env)
|
||||
|
||||
|
||||
|
||||
class TimeoutExecutor(PrefixExecutor):
|
||||
"""Execute another action under a timeout.
|
||||
|
||||
Deprecated. http://reviews.llvm.org/D6584 adds timeouts to LIT.
|
||||
"""
|
||||
def __init__(self, duration, chain):
|
||||
super(TimeoutExecutor, self).__init__(
|
||||
['timeout', duration], chain)
|
||||
|
||||
|
||||
class SSHExecutor(Executor):
|
||||
def __init__(self, host, username=None):
|
||||
super(SSHExecutor, self).__init__()
|
||||
|
||||
self.user_prefix = username + '@' if username else ''
|
||||
self.host = host
|
||||
self.scp_command = 'scp'
|
||||
self.ssh_command = 'ssh'
|
||||
|
||||
self.local_run = executeCommand
|
||||
# TODO(jroelofs): switch this on some -super-verbose-debug config flag
|
||||
if False:
|
||||
self.local_run = tracing.trace_function(
|
||||
self.local_run, log_calls=True, log_results=True,
|
||||
label='ssh_local')
|
||||
|
||||
def remote_temp_dir(self):
|
||||
return self._remote_temp(True)
|
||||
|
||||
def remote_temp_file(self):
|
||||
return self._remote_temp(False)
|
||||
|
||||
def _remote_temp(self, is_dir):
|
||||
# TODO: detect what the target system is, and use the correct
|
||||
# mktemp command for it. (linux and darwin differ here, and I'm
|
||||
# sure windows has another way to do it)
|
||||
|
||||
# Not sure how to do suffix on osx yet
|
||||
dir_arg = '-d' if is_dir else ''
|
||||
cmd = 'mktemp -q {} /tmp/libcxx.XXXXXXXXXX'.format(dir_arg)
|
||||
temp_path, err, exitCode = self.__execute_command_remote([cmd])
|
||||
temp_path = temp_path.strip()
|
||||
if exitCode != 0:
|
||||
raise RuntimeError(err)
|
||||
return temp_path
|
||||
|
||||
def copy_in(self, local_srcs, remote_dsts):
|
||||
scp = self.scp_command
|
||||
remote = self.host
|
||||
remote = self.user_prefix + remote
|
||||
|
||||
# This could be wrapped up in a tar->scp->untar for performance
|
||||
# if there are lots of files to be copied/moved
|
||||
for src, dst in zip(local_srcs, remote_dsts):
|
||||
cmd = [scp, '-p', src, remote + ':' + dst]
|
||||
self.local_run(cmd)
|
||||
|
||||
def delete_remote(self, remote):
|
||||
try:
|
||||
self.__execute_command_remote(['rm', '-rf', remote])
|
||||
except OSError:
|
||||
# TODO: Log failure to delete?
|
||||
pass
|
||||
|
||||
def run(self, exe_path, cmd=None, work_dir='.', env=None):
|
||||
target_exe_path = None
|
||||
target_cwd = None
|
||||
try:
|
||||
target_exe_path = self.remote_temp_file()
|
||||
target_cwd = self.remote_temp_dir()
|
||||
if cmd:
|
||||
# Replace exe_path with target_exe_path.
|
||||
cmd = [c if c != exe_path else target_exe_path for c in cmd]
|
||||
else:
|
||||
cmd = [target_exe_path]
|
||||
self.copy_in([exe_path], [target_exe_path])
|
||||
return self.__execute_command_remote(cmd, target_cwd, env)
|
||||
except:
|
||||
raise
|
||||
finally:
|
||||
if target_exe_path:
|
||||
self.delete_remote(target_exe_path)
|
||||
if target_cwd:
|
||||
self.delete_remote(target_cwd)
|
||||
|
||||
def __execute_command_remote(self, cmd, remote_work_dir='.', env=None):
|
||||
remote = self.user_prefix + self.host
|
||||
ssh_cmd = [self.ssh_command, '-oBatchMode=yes', remote]
|
||||
if env:
|
||||
env_cmd = ['env'] + ['%s=%s' % (k, v) for k, v in env.items()]
|
||||
else:
|
||||
env_cmd = []
|
||||
remote_cmd = ' '.join(env_cmd + cmd)
|
||||
if remote_work_dir != '.':
|
||||
remote_cmd = 'cd ' + remote_work_dir + ' && ' + remote_cmd
|
||||
return self.local_run(ssh_cmd + [remote_cmd])
|
||||
|
||||
Reference in New Issue
Block a user