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 cmd, 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 out, err, rc = executeCommand(env_cmd + cmd, cwd=work_dir) 38 return (env_cmd + cmd, out, err, rc) 39 40 41class PrefixExecutor(Executor): 42 """Prefix an executor with some other command wrapper. 43 44 Most useful for setting ulimits on commands, or running an emulator like 45 qemu and valgrind. 46 """ 47 def __init__(self, commandPrefix, chain): 48 super(PrefixExecutor, self).__init__() 49 50 self.commandPrefix = commandPrefix 51 self.chain = chain 52 53 def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None): 54 cmd = cmd or [exe_path] 55 return self.chain.run(exe_path, self.commandPrefix + cmd, work_dir, 56 file_deps, env=env) 57 58 59class PostfixExecutor(Executor): 60 """Postfix an executor with some args.""" 61 def __init__(self, commandPostfix, chain): 62 super(PostfixExecutor, self).__init__() 63 64 self.commandPostfix = commandPostfix 65 self.chain = chain 66 67 def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None): 68 cmd = cmd or [exe_path] 69 return self.chain.run(cmd + self.commandPostfix, work_dir, file_deps, 70 env=env) 71 72 73 74class TimeoutExecutor(PrefixExecutor): 75 """Execute another action under a timeout. 76 77 Deprecated. http://reviews.llvm.org/D6584 adds timeouts to LIT. 78 """ 79 def __init__(self, duration, chain): 80 super(TimeoutExecutor, self).__init__( 81 ['timeout', duration], chain) 82 83 84class RemoteExecutor(Executor): 85 def __init__(self): 86 self.local_run = executeCommand 87 88 def remote_temp_dir(self): 89 return self._remote_temp(True) 90 91 def remote_temp_file(self): 92 return self._remote_temp(False) 93 94 def _remote_temp(self, is_dir): 95 raise NotImplementedError() 96 97 def copy_in(self, local_srcs, remote_dsts): 98 # This could be wrapped up in a tar->scp->untar for performance 99 # if there are lots of files to be copied/moved 100 for src, dst in zip(local_srcs, remote_dsts): 101 self._copy_in_file(src, dst) 102 103 def _copy_in_file(self, src, dst): 104 raise NotImplementedError() 105 106 def delete_remote(self, remote): 107 try: 108 self._execute_command_remote(['rm', '-rf', remote]) 109 except OSError: 110 # TODO: Log failure to delete? 111 pass 112 113 def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None): 114 target_exe_path = None 115 target_cwd = None 116 try: 117 target_cwd = self.remote_temp_dir() 118 target_exe_path = os.path.join(target_cwd, 'libcxx_test.exe') 119 if cmd: 120 # Replace exe_path with target_exe_path. 121 cmd = [c if c != exe_path else target_exe_path for c in cmd] 122 else: 123 cmd = [target_exe_path] 124 125 srcs = [exe_path] 126 dsts = [target_exe_path] 127 if file_deps is not None: 128 dev_paths = [os.path.join(target_cwd, os.path.basename(f)) 129 for f in file_deps] 130 srcs.extend(file_deps) 131 dsts.extend(dev_paths) 132 self.copy_in(srcs, dsts) 133 # TODO(jroelofs): capture the copy_in and delete_remote commands, 134 # and conjugate them with '&&'s around the first tuple element 135 # returned here: 136 return self._execute_command_remote(cmd, target_cwd, env) 137 finally: 138 if target_cwd: 139 self.delete_remote(target_cwd) 140 141 def _execute_command_remote(self, cmd, remote_work_dir='.', env=None): 142 raise NotImplementedError() 143 144 145class SSHExecutor(RemoteExecutor): 146 def __init__(self, host, username=None): 147 super(SSHExecutor, self).__init__() 148 149 self.user_prefix = username + '@' if username else '' 150 self.host = host 151 self.scp_command = 'scp' 152 self.ssh_command = 'ssh' 153 154 # TODO(jroelofs): switch this on some -super-verbose-debug config flag 155 if False: 156 self.local_run = tracing.trace_function( 157 self.local_run, log_calls=True, log_results=True, 158 label='ssh_local') 159 160 def _remote_temp(self, is_dir): 161 # TODO: detect what the target system is, and use the correct 162 # mktemp command for it. (linux and darwin differ here, and I'm 163 # sure windows has another way to do it) 164 165 # Not sure how to do suffix on osx yet 166 dir_arg = '-d' if is_dir else '' 167 cmd = 'mktemp -q {} /tmp/libcxx.XXXXXXXXXX'.format(dir_arg) 168 temp_path, err, exitCode = self._execute_command_remote([cmd]) 169 temp_path = temp_path.strip() 170 if exitCode != 0: 171 raise RuntimeError(err) 172 return temp_path 173 174 def _copy_in_file(self, src, dst): 175 scp = self.scp_command 176 remote = self.host 177 remote = self.user_prefix + remote 178 cmd = [scp, '-p', src, remote + ':' + dst] 179 self.local_run(cmd) 180 181 def _execute_command_remote(self, cmd, remote_work_dir='.', env=None): 182 remote = self.user_prefix + self.host 183 ssh_cmd = [self.ssh_command, '-oBatchMode=yes', remote] 184 if env: 185 env_cmd = ['env'] + ['%s=%s' % (k, v) for k, v in env.items()] 186 else: 187 env_cmd = [] 188 remote_cmd = ' '.join(env_cmd + cmd) 189 if remote_work_dir != '.': 190 remote_cmd = 'cd ' + remote_work_dir + ' && ' + remote_cmd 191 return self.local_run(ssh_cmd + [remote_cmd]) 192