1 #===----------------------------------------------------------------------===##
3 # The LLVM Compiler Infrastructure
5 # This file is dual licensed under the MIT and the University of Illinois Open
6 # Source Licenses. See LICENSE.TXT for details.
8 #===----------------------------------------------------------------------===##
13 from libcxx.test import tracing
14 from libcxx.util import executeCommand
17 class Executor(object):
18 def run(self, exe_path, cmd, local_cwd, file_deps=None, env=None):
20 Be very careful not to change shared state in this function.
21 Executor objects are shared between python processes in `lit -jN`.
23 exe_path: str: Local path to the executable to be run
24 cmd: [str]: subprocess.call style command
25 local_cwd: str: Local path to the working directory
26 file_deps: [str]: Files required by the test
27 env: {str: str}: Environment variables to execute under
29 cmd, out, err, exitCode
31 raise NotImplementedError
34 class LocalExecutor(Executor):
36 super(LocalExecutor, self).__init__()
37 self.is_windows = platform.system() == 'Windows'
39 def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None):
40 cmd = cmd or [exe_path]
44 env_cmd += ['%s=%s' % (k, v) for k, v in env.items()]
46 work_dir = os.getcwd()
47 if not self.is_windows:
48 out, err, rc = executeCommand(env_cmd + cmd, cwd=work_dir)
50 out, err, rc = executeCommand(cmd, cwd=work_dir,
51 env=self._build_windows_env(env))
52 return (env_cmd + cmd, out, err, rc)
54 def _build_windows_env(self, exec_env):
55 # FIXME: Finding Windows DLL's at runtime requires modifying the
56 # PATH environment variables. However we don't want to print out
57 # the entire PATH as part of the diagnostic for every failing test.
58 # Therefore this hack builds a new executable environment that
59 # merges the current environment and the supplied environment while
60 # still only printing the supplied environment in diagnostics.
61 if not self.is_windows or exec_env is None:
63 new_env = dict(os.environ)
64 for key, value in exec_env.items():
66 assert value.strip() != '' and "expected non-empty path"
67 new_env['PATH'] = "%s;%s" % (value, os.environ['PATH'])
72 class PrefixExecutor(Executor):
73 """Prefix an executor with some other command wrapper.
75 Most useful for setting ulimits on commands, or running an emulator like
78 def __init__(self, commandPrefix, chain):
79 super(PrefixExecutor, self).__init__()
81 self.commandPrefix = commandPrefix
84 def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None):
85 cmd = cmd or [exe_path]
86 return self.chain.run(exe_path, self.commandPrefix + cmd, work_dir,
90 class PostfixExecutor(Executor):
91 """Postfix an executor with some args."""
92 def __init__(self, commandPostfix, chain):
93 super(PostfixExecutor, self).__init__()
95 self.commandPostfix = commandPostfix
98 def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None):
99 cmd = cmd or [exe_path]
100 return self.chain.run(cmd + self.commandPostfix, work_dir, file_deps,
105 class TimeoutExecutor(PrefixExecutor):
106 """Execute another action under a timeout.
108 Deprecated. http://reviews.llvm.org/D6584 adds timeouts to LIT.
110 def __init__(self, duration, chain):
111 super(TimeoutExecutor, self).__init__(
112 ['timeout', duration], chain)
115 class RemoteExecutor(Executor):
117 self.local_run = executeCommand
119 def remote_temp_dir(self):
120 return self._remote_temp(True)
122 def remote_temp_file(self):
123 return self._remote_temp(False)
125 def _remote_temp(self, is_dir):
126 raise NotImplementedError()
128 def copy_in(self, local_srcs, remote_dsts):
129 # This could be wrapped up in a tar->scp->untar for performance
130 # if there are lots of files to be copied/moved
131 for src, dst in zip(local_srcs, remote_dsts):
132 self._copy_in_file(src, dst)
134 def _copy_in_file(self, src, dst):
135 raise NotImplementedError()
137 def delete_remote(self, remote):
139 self._execute_command_remote(['rm', '-rf', remote])
141 # TODO: Log failure to delete?
144 def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None):
145 target_exe_path = None
148 target_cwd = self.remote_temp_dir()
149 target_exe_path = os.path.join(target_cwd, 'libcxx_test.exe')
151 # Replace exe_path with target_exe_path.
152 cmd = [c if c != exe_path else target_exe_path for c in cmd]
154 cmd = [target_exe_path]
157 dsts = [target_exe_path]
158 if file_deps is not None:
159 dev_paths = [os.path.join(target_cwd, os.path.basename(f))
161 srcs.extend(file_deps)
162 dsts.extend(dev_paths)
163 self.copy_in(srcs, dsts)
164 # TODO(jroelofs): capture the copy_in and delete_remote commands,
165 # and conjugate them with '&&'s around the first tuple element
167 return self._execute_command_remote(cmd, target_cwd, env)
170 self.delete_remote(target_cwd)
172 def _execute_command_remote(self, cmd, remote_work_dir='.', env=None):
173 raise NotImplementedError()
176 class SSHExecutor(RemoteExecutor):
177 def __init__(self, host, username=None):
178 super(SSHExecutor, self).__init__()
180 self.user_prefix = username + '@' if username else ''
182 self.scp_command = 'scp'
183 self.ssh_command = 'ssh'
185 # TODO(jroelofs): switch this on some -super-verbose-debug config flag
187 self.local_run = tracing.trace_function(
188 self.local_run, log_calls=True, log_results=True,
191 def _remote_temp(self, is_dir):
192 # TODO: detect what the target system is, and use the correct
193 # mktemp command for it. (linux and darwin differ here, and I'm
194 # sure windows has another way to do it)
196 # Not sure how to do suffix on osx yet
197 dir_arg = '-d' if is_dir else ''
198 cmd = 'mktemp -q {} /tmp/libcxx.XXXXXXXXXX'.format(dir_arg)
199 _, temp_path, err, exitCode = self._execute_command_remote([cmd])
200 temp_path = temp_path.strip()
202 raise RuntimeError(err)
205 def _copy_in_file(self, src, dst):
206 scp = self.scp_command
208 remote = self.user_prefix + remote
209 cmd = [scp, '-p', src, remote + ':' + dst]
212 def _execute_command_remote(self, cmd, remote_work_dir='.', env=None):
213 remote = self.user_prefix + self.host
214 ssh_cmd = [self.ssh_command, '-oBatchMode=yes', remote]
216 env_cmd = ['env'] + ['%s=%s' % (k, v) for k, v in env.items()]
219 remote_cmd = ' '.join(env_cmd + cmd)
220 if remote_work_dir != '.':
221 remote_cmd = 'cd ' + remote_work_dir + ' && ' + remote_cmd
222 out, err, rc = self.local_run(ssh_cmd + [remote_cmd])
223 return (remote_cmd, out, err, rc)