]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - utils/libcxx/test/executor.py
Vendor import of libc++ 4.0.0 release r297347:
[FreeBSD/FreeBSD.git] / utils / libcxx / test / executor.py
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         env_cmd = []
42         if env:
43             env_cmd += ['env']
44             env_cmd += ['%s=%s' % (k, v) for k, v in env.items()]
45         if work_dir == '.':
46             work_dir = os.getcwd()
47         if not self.is_windows:
48             out, err, rc = executeCommand(env_cmd + cmd, cwd=work_dir)
49         else:
50             out, err, rc = executeCommand(cmd, cwd=work_dir,
51                                           env=self._build_windows_env(env))
52         return (env_cmd + cmd, out, err, rc)
53
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:
62             return None
63         new_env = dict(os.environ)
64         for key, value in exec_env.items():
65             if key == 'PATH':
66                 assert value.strip() != '' and "expected non-empty path"
67                 new_env['PATH'] = "%s;%s" % (value, os.environ['PATH'])
68             else:
69                 new_env[key] = value
70         return new_env
71
72 class PrefixExecutor(Executor):
73     """Prefix an executor with some other command wrapper.
74
75     Most useful for setting ulimits on commands, or running an emulator like
76     qemu and valgrind.
77     """
78     def __init__(self, commandPrefix, chain):
79         super(PrefixExecutor, self).__init__()
80
81         self.commandPrefix = commandPrefix
82         self.chain = chain
83
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,
87                               file_deps, env=env)
88
89
90 class PostfixExecutor(Executor):
91     """Postfix an executor with some args."""
92     def __init__(self, commandPostfix, chain):
93         super(PostfixExecutor, self).__init__()
94
95         self.commandPostfix = commandPostfix
96         self.chain = chain
97
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,
101                               env=env)
102
103
104
105 class TimeoutExecutor(PrefixExecutor):
106     """Execute another action under a timeout.
107
108     Deprecated. http://reviews.llvm.org/D6584 adds timeouts to LIT.
109     """
110     def __init__(self, duration, chain):
111         super(TimeoutExecutor, self).__init__(
112             ['timeout', duration], chain)
113
114
115 class RemoteExecutor(Executor):
116     def __init__(self):
117         self.local_run = executeCommand
118
119     def remote_temp_dir(self):
120         return self._remote_temp(True)
121
122     def remote_temp_file(self):
123         return self._remote_temp(False)
124
125     def _remote_temp(self, is_dir):
126         raise NotImplementedError()
127
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)
133
134     def _copy_in_file(self, src, dst):
135         raise NotImplementedError()
136
137     def delete_remote(self, remote):
138         try:
139             self._execute_command_remote(['rm', '-rf', remote])
140         except OSError:
141             # TODO: Log failure to delete?
142             pass
143
144     def run(self, exe_path, cmd=None, work_dir='.', file_deps=None, env=None):
145         target_exe_path = None
146         target_cwd = None
147         try:
148             target_cwd = self.remote_temp_dir()
149             target_exe_path = os.path.join(target_cwd, 'libcxx_test.exe')
150             if cmd:
151                 # Replace exe_path with target_exe_path.
152                 cmd = [c if c != exe_path else target_exe_path for c in cmd]
153             else:
154                 cmd = [target_exe_path]
155
156             srcs = [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))
160                              for f in file_deps]
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
166             # returned here:
167             return self._execute_command_remote(cmd, target_cwd, env)
168         finally:
169             if target_cwd:
170                 self.delete_remote(target_cwd)
171
172     def _execute_command_remote(self, cmd, remote_work_dir='.', env=None):
173         raise NotImplementedError()
174
175
176 class SSHExecutor(RemoteExecutor):
177     def __init__(self, host, username=None):
178         super(SSHExecutor, self).__init__()
179
180         self.user_prefix = username + '@' if username else ''
181         self.host = host
182         self.scp_command = 'scp'
183         self.ssh_command = 'ssh'
184
185         # TODO(jroelofs): switch this on some -super-verbose-debug config flag
186         if False:
187             self.local_run = tracing.trace_function(
188                 self.local_run, log_calls=True, log_results=True,
189                 label='ssh_local')
190
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)
195
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()
201         if exitCode != 0:
202             raise RuntimeError(err)
203         return temp_path
204
205     def _copy_in_file(self, src, dst):
206         scp = self.scp_command
207         remote = self.host
208         remote = self.user_prefix + remote
209         cmd = [scp, '-p', src, remote + ':' + dst]
210         self.local_run(cmd)
211
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]
215         if env:
216             env_cmd = ['env'] + ['%s=%s' % (k, v) for k, v in env.items()]
217         else:
218             env_cmd = []
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)