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