You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
198 lines
6.9 KiB
Python
198 lines
6.9 KiB
Python
#===----------------------------------------------------------------------===##
|
|
#
|
|
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
# See https://llvm.org/LICENSE.txt for license information.
|
|
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
#
|
|
#===----------------------------------------------------------------------===##
|
|
|
|
import platform
|
|
import os
|
|
|
|
from libcxx.test import tracing
|
|
from libcxx.util import executeCommand
|
|
|
|
|
|
class Executor(object):
|
|
def run(self, exe_path, cmd, local_cwd, file_deps=None, 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
|
|
file_deps: [str]: Files required by the test
|
|
env: {str: str}: Environment variables to execute under
|
|
Returns:
|
|
cmd, out, err, exitCode
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
class LocalExecutor(Executor):
|
|
def __init__(self):
|
|
super(LocalExecutor, self).__init__()
|
|
self.is_windows = platform.system() == 'Windows'
|
|
|
|
def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None):
|
|
cmd = cmd or [exe_path]
|
|
if work_dir == '.':
|
|
work_dir = os.getcwd()
|
|
out, err, rc = executeCommand(cmd, cwd=work_dir, env=env)
|
|
return (cmd, out, err, rc)
|
|
|
|
|
|
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='.', file_deps=None, env=None):
|
|
cmd = cmd or [exe_path]
|
|
return self.chain.run(exe_path, self.commandPrefix + cmd, work_dir,
|
|
file_deps, 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='.', file_deps=None, env=None):
|
|
cmd = cmd or [exe_path]
|
|
return self.chain.run(cmd + self.commandPostfix, work_dir, file_deps,
|
|
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 RemoteExecutor(Executor):
|
|
def __init__(self):
|
|
self.local_run = executeCommand
|
|
|
|
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):
|
|
raise NotImplementedError()
|
|
|
|
def copy_in(self, local_srcs, remote_dsts):
|
|
# 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):
|
|
self._copy_in_file(src, dst)
|
|
|
|
def _copy_in_file(self, src, dst):
|
|
raise NotImplementedError()
|
|
|
|
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='.', file_deps=None, env=None):
|
|
target_exe_path = None
|
|
target_cwd = None
|
|
try:
|
|
target_cwd = self.remote_temp_dir()
|
|
target_exe_path = os.path.join(target_cwd, 'libcxx_test.exe')
|
|
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]
|
|
|
|
srcs = [exe_path]
|
|
dsts = [target_exe_path]
|
|
if file_deps is not None:
|
|
dev_paths = [os.path.join(target_cwd, os.path.basename(f))
|
|
for f in file_deps]
|
|
srcs.extend(file_deps)
|
|
dsts.extend(dev_paths)
|
|
self.copy_in(srcs, dsts)
|
|
# TODO(jroelofs): capture the copy_in and delete_remote commands,
|
|
# and conjugate them with '&&'s around the first tuple element
|
|
# returned here:
|
|
return self._execute_command_remote(cmd, target_cwd, env)
|
|
finally:
|
|
if target_cwd:
|
|
self.delete_remote(target_cwd)
|
|
|
|
def _execute_command_remote(self, cmd, remote_work_dir='.', env=None):
|
|
raise NotImplementedError()
|
|
|
|
|
|
class SSHExecutor(RemoteExecutor):
|
|
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'
|
|
|
|
# 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(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_file(self, src, dst):
|
|
scp = self.scp_command
|
|
remote = self.host
|
|
remote = self.user_prefix + remote
|
|
cmd = [scp, '-p', src, remote + ':' + dst]
|
|
self.local_run(cmd)
|
|
|
|
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
|
|
out, err, rc = self.local_run(ssh_cmd + [remote_cmd])
|
|
return (remote_cmd, out, err, rc)
|