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