blob: d5029bb970ae9abe65e45d26084fc2f18841a6d2 [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
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -070026from subprocess import STDOUT
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -070027from subprocess import TimeoutExpired
28
29from tempfile import mkdtemp
30from tempfile import NamedTemporaryFile
31
32# Temporary directory path on device.
33DEVICE_TMP_PATH = '/data/local/tmp'
34
35# Architectures supported in dalvik cache.
36DALVIK_CACHE_ARCHS = ['arm', 'arm64', 'x86', 'x86_64']
37
38
39def GetEnvVariableOrError(variable_name):
40 """Gets value of an environmental variable.
41
42 If the variable is not set raises FatalError.
43
44 Args:
45 variable_name: string, name of variable to get.
46
47 Returns:
48 string, value of requested variable.
49
50 Raises:
51 FatalError: Requested variable is not set.
52 """
53 top = os.environ.get(variable_name)
54 if top is None:
55 raise FatalError('{0} environmental variable not set.'.format(
56 variable_name))
57 return top
58
59
60def _DexArchCachePaths(android_data_path):
61 """Returns paths to architecture specific caches.
62
63 Args:
64 android_data_path: string, path dalvik-cache resides in.
65
66 Returns:
67 Iterable paths to architecture specific caches.
68 """
69 return ('{0}/dalvik-cache/{1}'.format(android_data_path, arch)
70 for arch in DALVIK_CACHE_ARCHS)
71
72
73def _RunCommandForOutputAndLog(cmd, env, logfile, timeout=60):
74 """Runs command and logs its output. Returns the output.
75
76 Args:
77 cmd: list of strings, command to run.
78 env: shell environment to run the command with.
79 logfile: file handle to logfile.
80 timeout: int, timeout in seconds
81
82 Returns:
83 tuple (string, string, int) stdout output, stderr output, return code.
84 """
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -070085 proc = Popen(cmd, stderr=STDOUT, stdout=PIPE, env=env,
86 universal_newlines=True)
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -070087 timeouted = False
88 try:
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -070089 (output, _) = proc.communicate(timeout=timeout)
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -070090 except TimeoutExpired:
91 timeouted = True
92 proc.kill()
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -070093 (output, _) = proc.communicate()
94 logfile.write('Command:\n{0}\n{1}\nReturn code: {2}\n'.format(
95 _CommandListToCommandString(cmd), output,
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -070096 'TIMEOUT' if timeouted else proc.returncode))
97 ret_code = 1 if timeouted else proc.returncode
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -070098 return (output, ret_code)
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -070099
100
101def _CommandListToCommandString(cmd):
102 """Converts shell command represented as list of strings to a single string.
103
104 Each element of the list is wrapped in double quotes.
105
106 Args:
107 cmd: list of strings, shell command.
108
109 Returns:
110 string, shell command.
111 """
112 return ' '.join(['"{0}"'.format(segment) for segment in cmd])
113
114
115class FatalError(Exception):
116 """Fatal error in script."""
117
118
119class ITestEnv(object):
120 """Test environment abstraction.
121
122 Provides unified interface for interacting with host and device test
123 environments. Creates a test directory and expose methods to modify test files
124 and run commands.
125 """
126 __meta_class__ = abc.ABCMeta
127
128 @abc.abstractmethod
129 def CreateFile(self, name=None):
130 """Creates a file in test directory.
131
132 Returned path to file can be used in commands run in the environment.
133
134 Args:
135 name: string, file name. If None file is named arbitrarily.
136
137 Returns:
138 string, environment specific path to file.
139 """
140
141 @abc.abstractmethod
142 def WriteLines(self, file_path, lines):
143 """Writes lines to a file in test directory.
144
145 If file exists it gets overwritten. If file doest not exist it is created.
146
147 Args:
148 file_path: string, environment specific path to file.
149 lines: list of strings to write.
150 """
151
152 @abc.abstractmethod
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -0700153 def RunCommand(self, cmd, env_updates=None):
154 """Runs command in environment with updated environmental variables.
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700155
156 Args:
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -0700157 cmd: list of strings, command to run.
158 env_updates: dict, string to string, maps names of variables to their
159 updated values.
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700160 Returns:
161 tuple (string, string, int) stdout output, stderr output, return code.
162 """
163
164 @abc.abstractproperty
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700165 def logfile(self):
166 """Gets file handle to logfile residing on host."""
167
168
169class HostTestEnv(ITestEnv):
170 """Host test environment. Concrete implementation of ITestEnv.
171
172 Maintains a test directory in /tmp/. Runs commands on the host in modified
173 shell environment. Mimics art script behavior.
174
175 For methods documentation see base class.
176 """
177
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -0700178 def __init__(self, x64):
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700179 """Constructor.
180
181 Args:
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700182 x64: boolean, whether to setup in x64 mode.
183 """
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700184 self._env_path = mkdtemp(dir='/tmp/', prefix='bisection_search_')
185 self._logfile = open('{0}/log'.format(self._env_path), 'w+')
186 os.mkdir('{0}/dalvik-cache'.format(self._env_path))
187 for arch_cache_path in _DexArchCachePaths(self._env_path):
188 os.mkdir(arch_cache_path)
189 lib = 'lib64' if x64 else 'lib'
190 android_root = GetEnvVariableOrError('ANDROID_HOST_OUT')
191 library_path = android_root + '/' + lib
192 path = android_root + '/bin'
193 self._shell_env = os.environ.copy()
194 self._shell_env['ANDROID_DATA'] = self._env_path
195 self._shell_env['ANDROID_ROOT'] = android_root
196 self._shell_env['LD_LIBRARY_PATH'] = library_path
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -0700197 self._shell_env['DYLD_LIBRARY_PATH'] = library_path
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700198 self._shell_env['PATH'] = (path + ':' + self._shell_env['PATH'])
199 # Using dlopen requires load bias on the host.
200 self._shell_env['LD_USE_LOAD_BIAS'] = '1'
201
202 def CreateFile(self, name=None):
203 if name is None:
204 f = NamedTemporaryFile(dir=self._env_path, delete=False)
205 else:
206 f = open('{0}/{1}'.format(self._env_path, name), 'w+')
207 return f.name
208
209 def WriteLines(self, file_path, lines):
210 with open(file_path, 'w') as f:
211 f.writelines('{0}\n'.format(line) for line in lines)
212 return
213
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -0700214 def RunCommand(self, cmd, env_updates=None):
215 if not env_updates:
216 env_updates = {}
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700217 self._EmptyDexCache()
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -0700218 env = self._shell_env.copy()
219 env.update(env_updates)
220 return _RunCommandForOutputAndLog(cmd, env, self._logfile)
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700221
222 @property
223 def logfile(self):
224 return self._logfile
225
226 def _EmptyDexCache(self):
227 """Empties dex cache.
228
229 Iterate over files in architecture specific cache directories and remove
230 them.
231 """
232 for arch_cache_path in _DexArchCachePaths(self._env_path):
233 for file_path in os.listdir(arch_cache_path):
234 file_path = '{0}/{1}'.format(arch_cache_path, file_path)
235 if os.path.isfile(file_path):
236 os.unlink(file_path)
237
238
239class DeviceTestEnv(ITestEnv):
240 """Device test environment. Concrete implementation of ITestEnv.
241
242 Makes use of HostTestEnv to maintain a test directory on host. Creates an
243 on device test directory which is kept in sync with the host one.
244
245 For methods documentation see base class.
246 """
247
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -0700248 def __init__(self):
249 """Constructor."""
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700250 self._host_env_path = mkdtemp(dir='/tmp/', prefix='bisection_search_')
251 self._logfile = open('{0}/log'.format(self._host_env_path), 'w+')
252 self._device_env_path = '{0}/{1}'.format(
253 DEVICE_TMP_PATH, os.path.basename(self._host_env_path))
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -0700254 self._shell_env = os.environ.copy()
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700255
256 self._AdbMkdir('{0}/dalvik-cache'.format(self._device_env_path))
257 for arch_cache_path in _DexArchCachePaths(self._device_env_path):
258 self._AdbMkdir(arch_cache_path)
259
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700260 def CreateFile(self, name=None):
261 with NamedTemporaryFile(mode='w') as temp_file:
262 self._AdbPush(temp_file.name, self._device_env_path)
263 if name is None:
264 name = os.path.basename(temp_file.name)
265 return '{0}/{1}'.format(self._device_env_path, name)
266
267 def WriteLines(self, file_path, lines):
268 with NamedTemporaryFile(mode='w') as temp_file:
269 temp_file.writelines('{0}\n'.format(line) for line in lines)
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -0700270 temp_file.flush()
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700271 self._AdbPush(temp_file.name, file_path)
272 return
273
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -0700274 def RunCommand(self, cmd, env_updates=None):
275 if not env_updates:
276 env_updates = {}
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700277 self._EmptyDexCache()
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -0700278 if 'ANDROID_DATA' not in env_updates:
279 env_updates['ANDROID_DATA'] = self._device_env_path
280 env_updates_cmd = ' '.join(['{0}={1}'.format(var, val) for var, val
281 in env_updates.items()])
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700282 cmd = _CommandListToCommandString(cmd)
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -0700283 cmd = ('adb shell "logcat -c && {0} {1} ; logcat -d -s dex2oat:* dex2oatd:*'
284 '| grep -v "^---------" 1>&2"').format(env_updates_cmd, cmd)
285 return _RunCommandForOutputAndLog(
286 shlex.split(cmd), self._shell_env, self._logfile)
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700287
288 @property
289 def logfile(self):
290 return self._logfile
291
Wojciech Staszkiewicz86379942016-09-01 14:36:13 -0700292 def PushClasspath(self, classpath):
293 """Push classpath to on-device test directory.
294
295 Classpath can contain multiple colon separated file paths, each file is
296 pushed. Returns analogous classpath with paths valid on device.
297
298 Args:
299 classpath: string, classpath in format 'a/b/c:d/e/f'.
300 Returns:
301 string, classpath valid on device.
302 """
303 paths = classpath.split(':')
304 device_paths = []
305 for path in paths:
306 device_paths.append('{0}/{1}'.format(
307 self._device_env_path, os.path.basename(path)))
308 self._AdbPush(path, self._device_env_path)
309 return ':'.join(device_paths)
310
Wojciech Staszkiewicz0fa3cbd2016-08-11 14:04:20 -0700311 def _AdbPush(self, what, where):
312 check_call(shlex.split('adb push "{0}" "{1}"'.format(what, where)),
313 stdout=self._logfile, stderr=self._logfile)
314
315 def _AdbMkdir(self, path):
316 check_call(shlex.split('adb shell mkdir "{0}" -p'.format(path)),
317 stdout=self._logfile, stderr=self._logfile)
318
319 def _EmptyDexCache(self):
320 """Empties dex cache."""
321 for arch_cache_path in _DexArchCachePaths(self._device_env_path):
322 cmd = 'adb shell if [ -d "{0}" ]; then rm -f "{0}"/*; fi'.format(
323 arch_cache_path)
324 check_call(shlex.split(cmd), stdout=self._logfile, stderr=self._logfile)