summaryrefslogtreecommitdiffstats
path: root/gnu/llvm/libcxx/utils/libcxx/test/executor.py
diff options
context:
space:
mode:
Diffstat (limited to 'gnu/llvm/libcxx/utils/libcxx/test/executor.py')
-rw-r--r--gnu/llvm/libcxx/utils/libcxx/test/executor.py251
1 files changed, 251 insertions, 0 deletions
diff --git a/gnu/llvm/libcxx/utils/libcxx/test/executor.py b/gnu/llvm/libcxx/utils/libcxx/test/executor.py
new file mode 100644
index 00000000000..4ba6164e125
--- /dev/null
+++ b/gnu/llvm/libcxx/utils/libcxx/test/executor.py
@@ -0,0 +1,251 @@
+#===----------------------------------------------------------------------===##
+#
+# 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
+import posixpath
+import ntpath
+
+from libcxx.test import tracing
+from libcxx.util import executeCommand
+
+class Executor(object):
+ def __init__(self):
+ self.target_info = None
+
+ 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
+
+ def merge_environments(self, current_env, updated_env):
+ """Merges two execution environments.
+
+ If both environments contain the PATH variables, they are also merged
+ using the proper separator.
+ """
+ result_env = dict(current_env)
+ for k, v in updated_env.items():
+ if k == 'PATH' and self.target_info:
+ self.target_info.add_path(result_env, v)
+ else:
+ result_env[k] = v
+ return result_env
+
+
+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()
+
+ if env:
+ env = self.merge_environments(os.environ, env)
+
+ 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):
+ super(RemoteExecutor, self).__init__()
+ 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()
+ executable_name = 'libcxx_test.exe'
+ if self.target_info.is_windows():
+ target_exe_path = ntpath.join(target_cwd, executable_name)
+ else:
+ target_exe_path = posixpath.join(target_cwd, executable_name)
+
+ 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)
+
+ # When testing executables that were cross-compiled on Windows for
+ # Linux, we may need to explicitly set the execution permission to
+ # avoid the 'Permission denied' error:
+ chmod_cmd = ['chmod', '+x', target_exe_path]
+
+ # 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(chmod_cmd + ['&&'] + 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 _export_command(self, env):
+ if not env:
+ return []
+
+ export_cmd = ['export']
+
+ for k, v in env.items():
+ v = v.replace('\\', '\\\\')
+ if k == 'PATH':
+ # Pick up the existing paths, so we don't lose any commands
+ if self.target_info and self.target_info.is_windows():
+ export_cmd.append('PATH="%s;%PATH%"' % v)
+ else:
+ export_cmd.append('PATH="%s:$PATH"' % v)
+ else:
+ export_cmd.append('"%s"="%s"' % (k, v))
+
+ return export_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]
+ export_cmd = self._export_command(env)
+ remote_cmd = ' '.join(cmd)
+ if export_cmd:
+ remote_cmd = ' '.join(export_cmd) + ' && ' + remote_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)