blob: 8361fc9e9408c7dde0e5b66be5dd79ff8abbbbba [file] [log] [blame]
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -07001#!/usr/bin/env python3.4
2#
3# Copyright (C) 2016 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Module containing common logic from python testing tools."""
18
19import abc
20import os
21import shlex
22
23from subprocess import check_call
24from subprocess import PIPE
25from subprocess import Popen
26from subprocess import TimeoutExpired
27
28from tempfile import mkdtemp
29from tempfile import NamedTemporaryFile
30
31# Temporary directory path on device.
32DEVICE_TMP_PATH = '/data/local/tmp'
33
34# Architectures supported in dalvik cache.
35DALVIK_CACHE_ARCHS = ['arm', 'arm64', 'x86', 'x86_64']
36
37
38def GetEnvVariableOrError(variable_name):
39 """Gets value of an environmental variable.
40
41 If the variable is not set raises FatalError.
42
43 Args:
44 variable_name: string, name of variable to get.
45
46 Returns:
47 string, value of requested variable.
48
49 Raises:
50 FatalError: Requested variable is not set.
51 """
52 top = os.environ.get(variable_name)
53 if top is None:
54 raise FatalError('{0} environmental variable not set.'.format(
55 variable_name))
56 return top
57
58
59def _DexArchCachePaths(android_data_path):
60 """Returns paths to architecture specific caches.
61
62 Args:
63 android_data_path: string, path dalvik-cache resides in.
64
65 Returns:
66 Iterable paths to architecture specific caches.
67 """
68 return ('{0}/dalvik-cache/{1}'.format(android_data_path, arch)
69 for arch in DALVIK_CACHE_ARCHS)
70
71
72def _RunCommandForOutputAndLog(cmd, env, logfile, timeout=60):
73 """Runs command and logs its output. Returns the output.
74
75 Args:
76 cmd: list of strings, command to run.
77 env: shell environment to run the command with.
78 logfile: file handle to logfile.
79 timeout: int, timeout in seconds
80
81 Returns:
82 tuple (string, string, int) stdout output, stderr output, return code.
83 """
84 proc = Popen(cmd, stderr=PIPE, stdout=PIPE, env=env, universal_newlines=True)
85 timeouted = False
86 try:
87 (output, err_output) = proc.communicate(timeout=timeout)
88 except TimeoutExpired:
89 timeouted = True
90 proc.kill()
91 (output, err_output) = proc.communicate()
92 logfile.write('Command:\n{0}\n{1}{2}\nReturn code: {3}\n'.format(
93 _CommandListToCommandString(cmd), err_output, output,
94 'TIMEOUT' if timeouted else proc.returncode))
95 ret_code = 1 if timeouted else proc.returncode
96 return (output, err_output, ret_code)
97
98
99def _CommandListToCommandString(cmd):
100 """Converts shell command represented as list of strings to a single string.
101
102 Each element of the list is wrapped in double quotes.
103
104 Args:
105 cmd: list of strings, shell command.
106
107 Returns:
108 string, shell command.
109 """
110 return ' '.join(['"{0}"'.format(segment) for segment in cmd])
111
112
113class FatalError(Exception):
114 """Fatal error in script."""
115
116
117class ITestEnv(object):
118 """Test environment abstraction.
119
120 Provides unified interface for interacting with host and device test
121 environments. Creates a test directory and expose methods to modify test files
122 and run commands.
123 """
124 __meta_class__ = abc.ABCMeta
125
126 @abc.abstractmethod
127 def CreateFile(self, name=None):
128 """Creates a file in test directory.
129
130 Returned path to file can be used in commands run in the environment.
131
132 Args:
133 name: string, file name. If None file is named arbitrarily.
134
135 Returns:
136 string, environment specific path to file.
137 """
138
139 @abc.abstractmethod
140 def WriteLines(self, file_path, lines):
141 """Writes lines to a file in test directory.
142
143 If file exists it gets overwritten. If file doest not exist it is created.
144
145 Args:
146 file_path: string, environment specific path to file.
147 lines: list of strings to write.
148 """
149
150 @abc.abstractmethod
151 def RunCommand(self, cmd):
152 """Runs command in environment.
153
154 Args:
155 cmd: string, command to run.
156
157 Returns:
158 tuple (string, string, int) stdout output, stderr output, return code.
159 """
160
161 @abc.abstractproperty
162 def classpath(self):
163 """Gets environment specific classpath with test class."""
164
165 @abc.abstractproperty
166 def logfile(self):
167 """Gets file handle to logfile residing on host."""
168
169
170class HostTestEnv(ITestEnv):
171 """Host test environment. Concrete implementation of ITestEnv.
172
173 Maintains a test directory in /tmp/. Runs commands on the host in modified
174 shell environment. Mimics art script behavior.
175
176 For methods documentation see base class.
177 """
178
179 def __init__(self, classpath, x64):
180 """Constructor.
181
182 Args:
183 classpath: string, classpath with test class.
184 x64: boolean, whether to setup in x64 mode.
185 """
186 self._classpath = classpath
187 self._env_path = mkdtemp(dir='/tmp/', prefix='bisection_search_')
188 self._logfile = open('{0}/log'.format(self._env_path), 'w+')
189 os.mkdir('{0}/dalvik-cache'.format(self._env_path))
190 for arch_cache_path in _DexArchCachePaths(self._env_path):
191 os.mkdir(arch_cache_path)
192 lib = 'lib64' if x64 else 'lib'
193 android_root = GetEnvVariableOrError('ANDROID_HOST_OUT')
194 library_path = android_root + '/' + lib
195 path = android_root + '/bin'
196 self._shell_env = os.environ.copy()
197 self._shell_env['ANDROID_DATA'] = self._env_path
198 self._shell_env['ANDROID_ROOT'] = android_root
199 self._shell_env['LD_LIBRARY_PATH'] = library_path
200 self._shell_env['PATH'] = (path + ':' + self._shell_env['PATH'])
201 # Using dlopen requires load bias on the host.
202 self._shell_env['LD_USE_LOAD_BIAS'] = '1'
203
204 def CreateFile(self, name=None):
205 if name is None:
206 f = NamedTemporaryFile(dir=self._env_path, delete=False)
207 else:
208 f = open('{0}/{1}'.format(self._env_path, name), 'w+')
209 return f.name
210
211 def WriteLines(self, file_path, lines):
212 with open(file_path, 'w') as f:
213 f.writelines('{0}\n'.format(line) for line in lines)
214 return
215
216 def RunCommand(self, cmd):
217 self._EmptyDexCache()
218 return _RunCommandForOutputAndLog(cmd, self._shell_env, self._logfile)
219
220 @property
221 def classpath(self):
222 return self._classpath
223
224 @property
225 def logfile(self):
226 return self._logfile
227
228 def _EmptyDexCache(self):
229 """Empties dex cache.
230
231 Iterate over files in architecture specific cache directories and remove
232 them.
233 """
234 for arch_cache_path in _DexArchCachePaths(self._env_path):
235 for file_path in os.listdir(arch_cache_path):
236 file_path = '{0}/{1}'.format(arch_cache_path, file_path)
237 if os.path.isfile(file_path):
238 os.unlink(file_path)
239
240
241class DeviceTestEnv(ITestEnv):
242 """Device test environment. Concrete implementation of ITestEnv.
243
244 Makes use of HostTestEnv to maintain a test directory on host. Creates an
245 on device test directory which is kept in sync with the host one.
246
247 For methods documentation see base class.
248 """
249
250 def __init__(self, classpath):
251 """Constructor.
252
253 Args:
254 classpath: string, classpath with test class.
255 """
256 self._host_env_path = mkdtemp(dir='/tmp/', prefix='bisection_search_')
257 self._logfile = open('{0}/log'.format(self._host_env_path), 'w+')
258 self._device_env_path = '{0}/{1}'.format(
259 DEVICE_TMP_PATH, os.path.basename(self._host_env_path))
260 self._classpath = os.path.join(
261 self._device_env_path, os.path.basename(classpath))
262 self._shell_env = os.environ
263
264 self._AdbMkdir('{0}/dalvik-cache'.format(self._device_env_path))
265 for arch_cache_path in _DexArchCachePaths(self._device_env_path):
266 self._AdbMkdir(arch_cache_path)
267
268 paths = classpath.split(':')
269 device_paths = []
270 for path in paths:
271 device_paths.append('{0}/{1}'.format(
272 self._device_env_path, os.path.basename(path)))
273 self._AdbPush(path, self._device_env_path)
274 self._classpath = ':'.join(device_paths)
275
276 def CreateFile(self, name=None):
277 with NamedTemporaryFile(mode='w') as temp_file:
278 self._AdbPush(temp_file.name, self._device_env_path)
279 if name is None:
280 name = os.path.basename(temp_file.name)
281 return '{0}/{1}'.format(self._device_env_path, name)
282
283 def WriteLines(self, file_path, lines):
284 with NamedTemporaryFile(mode='w') as temp_file:
285 temp_file.writelines('{0}\n'.format(line) for line in lines)
286 self._AdbPush(temp_file.name, file_path)
287 return
288
289 def RunCommand(self, cmd):
290 self._EmptyDexCache()
291 cmd = _CommandListToCommandString(cmd)
292 cmd = ('adb shell "logcat -c && ANDROID_DATA={0} {1} && '
293 'logcat -d dex2oat:* *:S 1>&2"').format(self._device_env_path, cmd)
294 return _RunCommandForOutputAndLog(shlex.split(cmd), self._shell_env,
295 self._logfile)
296
297 @property
298 def classpath(self):
299 return self._classpath
300
301 @property
302 def logfile(self):
303 return self._logfile
304
305 def _AdbPush(self, what, where):
306 check_call(shlex.split('adb push "{0}" "{1}"'.format(what, where)),
307 stdout=self._logfile, stderr=self._logfile)
308
309 def _AdbMkdir(self, path):
310 check_call(shlex.split('adb shell mkdir "{0}" -p'.format(path)),
311 stdout=self._logfile, stderr=self._logfile)
312
313 def _EmptyDexCache(self):
314 """Empties dex cache."""
315 for arch_cache_path in _DexArchCachePaths(self._device_env_path):
316 cmd = 'adb shell if [ -d "{0}" ]; then rm -f "{0}"/*; fi'.format(
317 arch_cache_path)
318 check_call(shlex.split(cmd), stdout=self._logfile, stderr=self._logfile)