1import os 2 3from libcxx.test import tracing 4 5from lit.util import executeCommand # pylint: disable=import-error 6 7 8class Executor(object): 9 def run(self, exe_path, cmd, local_cwd, file_deps=None, env=None): 10 """Execute a command. 11 Be very careful not to change shared state in this function. 12 Executor objects are shared between python processes in `lit -jN`. 13 Args: 14 exe_path: str: Local path to the executable to be run 15 cmd: [str]: subprocess.call style command 16 local_cwd: str: Local path to the working directory 17 file_deps: [str]: Files required by the test 18 env: {str: str}: Environment variables to execute under 19 Returns: 20 out, err, exitCode 21 """ 22 raise NotImplementedError 23 24 25class LocalExecutor(Executor): 26 def __init__(self): 27 super(LocalExecutor, self).__init__() 28 29 def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None): 30 cmd = cmd or [exe_path] 31 env_cmd = [] 32 if env: 33 env_cmd += ['env'] 34 env_cmd += ['%s=%s' % (k, v) for k, v in env.items()] 35 if work_dir == '.': 36 work_dir = os.getcwd() 37 return executeCommand(env_cmd + cmd, cwd=work_dir) 38 39 40class PrefixExecutor(Executor): 41 """Prefix an executor with some other command wrapper. 42 43 Most useful for setting ulimits on commands, or running an emulator like 44 qemu and valgrind. 45 """ 46 def __init__(self, commandPrefix, chain): 47 super(PrefixExecutor, self).__init__() 48 49 self.commandPrefix = commandPrefix 50 self.chain = chain 51 52 def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None): 53 cmd = cmd or [exe_path] 54 return self.chain.run(exe_path, self.commandPrefix + cmd, work_dir, 55 file_deps, env=env) 56 57 58class PostfixExecutor(Executor): 59 """Postfix an executor with some args.""" 60 def __init__(self, commandPostfix, chain): 61 super(PostfixExecutor, self).__init__() 62 63 self.commandPostfix = commandPostfix 64 self.chain = chain 65 66 def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None): 67 cmd = cmd or [exe_path] 68 return self.chain.run(cmd + self.commandPostfix, work_dir, file_deps, 69 env=env) 70 71 72 73class TimeoutExecutor(PrefixExecutor): 74 """Execute another action under a timeout. 75 76 Deprecated. http://reviews.llvm.org/D6584 adds timeouts to LIT. 77 """ 78 def __init__(self, duration, chain): 79 super(TimeoutExecutor, self).__init__( 80 ['timeout', duration], chain) 81 82 83class RemoteExecutor(Executor): 84 def __init__(self): 85 self.local_run = executeCommand 86 87 def remote_temp_dir(self): 88 return self._remote_temp(True) 89 90 def remote_temp_file(self): 91 return self._remote_temp(False) 92 93 def _remote_temp(self, is_dir): 94 raise NotImplementedError() 95 96 def copy_in(self, local_srcs, remote_dsts): 97 # This could be wrapped up in a tar->scp->untar for performance 98 # if there are lots of files to be copied/moved 99 for src, dst in zip(local_srcs, remote_dsts): 100 self._copy_in_file(src, dst) 101 102 def _copy_in_file(self, src, dst): 103 raise NotImplementedError() 104 105 def delete_remote(self, remote): 106 try: 107 self._execute_command_remote(['rm', '-rf', remote]) 108 except OSError: 109 # TODO: Log failure to delete? 110 pass 111 112 def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None): 113 target_exe_path = None 114 target_cwd = None 115 try: 116 target_cwd = self.remote_temp_dir() 117 target_exe_path = os.path.join(target_cwd, 'libcxx_test.exe') 118 if cmd: 119 # Replace exe_path with target_exe_path. 120 cmd = [c if c != exe_path else target_exe_path for c in cmd] 121 else: 122 cmd = [target_exe_path] 123 124 srcs = [exe_path] 125 dsts = [target_exe_path] 126 if file_deps is not None: 127 dev_paths = [os.path.join(target_cwd, os.path.basename(f)) 128 for f in file_deps] 129 srcs.extend(file_deps) 130 dsts.extend(dev_paths) 131 self.copy_in(srcs, dsts) 132 return self._execute_command_remote(cmd, target_cwd, env) 133 finally: 134 if target_cwd: 135 self.delete_remote(target_cwd) 136 137 def _execute_command_remote(self, cmd, remote_work_dir='.', env=None): 138 raise NotImplementedError() 139 140 141class SSHExecutor(RemoteExecutor): 142 def __init__(self, host, username=None): 143 super(SSHExecutor, self).__init__() 144 145 self.user_prefix = username + '@' if username else '' 146 self.host = host 147 self.scp_command = 'scp' 148 self.ssh_command = 'ssh' 149 150 # TODO(jroelofs): switch this on some -super-verbose-debug config flag 151 if False: 152 self.local_run = tracing.trace_function( 153 self.local_run, log_calls=True, log_results=True, 154 label='ssh_local') 155 156 def _remote_temp(self, is_dir): 157 # TODO: detect what the target system is, and use the correct 158 # mktemp command for it. (linux and darwin differ here, and I'm 159 # sure windows has another way to do it) 160 161 # Not sure how to do suffix on osx yet 162 dir_arg = '-d' if is_dir else '' 163 cmd = 'mktemp -q {} /tmp/libcxx.XXXXXXXXXX'.format(dir_arg) 164 temp_path, err, exitCode = self._execute_command_remote([cmd]) 165 temp_path = temp_path.strip() 166 if exitCode != 0: 167 raise RuntimeError(err) 168 return temp_path 169 170 def _copy_in_file(self, src, dst): 171 scp = self.scp_command 172 remote = self.host 173 remote = self.user_prefix + remote 174 cmd = [scp, '-p', src, remote + ':' + dst] 175 self.local_run(cmd) 176 177 def _execute_command_remote(self, cmd, remote_work_dir='.', env=None): 178 remote = self.user_prefix + self.host 179 ssh_cmd = [self.ssh_command, '-oBatchMode=yes', remote] 180 if env: 181 env_cmd = ['env'] + ['%s=%s' % (k, v) for k, v in env.items()] 182 else: 183 env_cmd = [] 184 remote_cmd = ' '.join(env_cmd + cmd) 185 if remote_work_dir != '.': 186 remote_cmd = 'cd ' + remote_work_dir + ' && ' + remote_cmd 187 return self.local_run(ssh_cmd + [remote_cmd]) 188