OLD | NEW |
| (Empty) |
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 """ | |
6 Classes in this file define additional actions that need to be taken to run a | |
7 test under some kind of runtime error detection tool. | |
8 | |
9 The interface is intended to be used as follows. | |
10 | |
11 1. For tests that simply run a native process (i.e. no activity is spawned): | |
12 | |
13 Call tool.CopyFiles(device). | |
14 Prepend test command line with tool.GetTestWrapper(). | |
15 | |
16 2. For tests that spawn an activity: | |
17 | |
18 Call tool.CopyFiles(device). | |
19 Call tool.SetupEnvironment(). | |
20 Run the test as usual. | |
21 Call tool.CleanUpEnvironment(). | |
22 """ | |
23 # pylint: disable=R0201 | |
24 | |
25 import glob | |
26 import logging | |
27 import os.path | |
28 import subprocess | |
29 import sys | |
30 | |
31 from pylib.constants import DIR_SOURCE_ROOT | |
32 from pylib.device import device_errors | |
33 | |
34 | |
35 def SetChromeTimeoutScale(device, scale): | |
36 """Sets the timeout scale in /data/local/tmp/chrome_timeout_scale to scale.""" | |
37 path = '/data/local/tmp/chrome_timeout_scale' | |
38 if not scale or scale == 1.0: | |
39 # Delete if scale is None/0.0/1.0 since the default timeout scale is 1.0 | |
40 device.RunShellCommand('rm %s' % path) | |
41 else: | |
42 device.WriteFile(path, '%f' % scale, as_root=True) | |
43 | |
44 | |
45 class BaseTool(object): | |
46 """A tool that does nothing.""" | |
47 | |
48 def __init__(self): | |
49 """Does nothing.""" | |
50 pass | |
51 | |
52 def GetTestWrapper(self): | |
53 """Returns a string that is to be prepended to the test command line.""" | |
54 return '' | |
55 | |
56 def GetUtilWrapper(self): | |
57 """Returns the wrapper name for the utilities. | |
58 | |
59 Returns: | |
60 A string that is to be prepended to the command line of utility | |
61 processes (forwarder, etc.). | |
62 """ | |
63 return '' | |
64 | |
65 @classmethod | |
66 def CopyFiles(cls, device): | |
67 """Copies tool-specific files to the device, create directories, etc.""" | |
68 pass | |
69 | |
70 def SetupEnvironment(self): | |
71 """Sets up the system environment for a test. | |
72 | |
73 This is a good place to set system properties. | |
74 """ | |
75 pass | |
76 | |
77 def CleanUpEnvironment(self): | |
78 """Cleans up environment.""" | |
79 pass | |
80 | |
81 def GetTimeoutScale(self): | |
82 """Returns a multiplier that should be applied to timeout values.""" | |
83 return 1.0 | |
84 | |
85 def NeedsDebugInfo(self): | |
86 """Whether this tool requires debug info. | |
87 | |
88 Returns: | |
89 True if this tool can not work with stripped binaries. | |
90 """ | |
91 return False | |
92 | |
93 | |
94 class AddressSanitizerTool(BaseTool): | |
95 """AddressSanitizer tool.""" | |
96 | |
97 WRAPPER_NAME = '/system/bin/asanwrapper' | |
98 # Disable memcmp overlap check.There are blobs (gl drivers) | |
99 # on some android devices that use memcmp on overlapping regions, | |
100 # nothing we can do about that. | |
101 EXTRA_OPTIONS = 'strict_memcmp=0,use_sigaltstack=1' | |
102 | |
103 def __init__(self, device): | |
104 super(AddressSanitizerTool, self).__init__() | |
105 self._device = device | |
106 # Configure AndroidCommands to run utils (such as md5sum_bin) under ASan. | |
107 # This is required because ASan is a compiler-based tool, and md5sum | |
108 # includes instrumented code from base. | |
109 device.old_interface.SetUtilWrapper(self.GetUtilWrapper()) | |
110 | |
111 @classmethod | |
112 def CopyFiles(cls, device): | |
113 """Copies ASan tools to the device.""" | |
114 libs = glob.glob(os.path.join(DIR_SOURCE_ROOT, | |
115 'third_party/llvm-build/Release+Asserts/', | |
116 'lib/clang/*/lib/linux/', | |
117 'libclang_rt.asan-arm-android.so')) | |
118 assert len(libs) == 1 | |
119 subprocess.call( | |
120 [os.path.join( | |
121 DIR_SOURCE_ROOT, | |
122 'tools/android/asan/third_party/asan_device_setup.sh'), | |
123 '--device', str(device), | |
124 '--lib', libs[0], | |
125 '--extra-options', AddressSanitizerTool.EXTRA_OPTIONS]) | |
126 device.WaitUntilFullyBooted() | |
127 | |
128 def GetTestWrapper(self): | |
129 return AddressSanitizerTool.WRAPPER_NAME | |
130 | |
131 def GetUtilWrapper(self): | |
132 """Returns the wrapper for utilities, such as forwarder. | |
133 | |
134 AddressSanitizer wrapper must be added to all instrumented binaries, | |
135 including forwarder and the like. This can be removed if such binaries | |
136 were built without instrumentation. """ | |
137 return self.GetTestWrapper() | |
138 | |
139 def SetupEnvironment(self): | |
140 try: | |
141 self._device.EnableRoot() | |
142 except device_errors.CommandFailedError as e: | |
143 # Try to set the timeout scale anyway. | |
144 # TODO(jbudorick) Handle this exception appropriately after interface | |
145 # conversions are finished. | |
146 logging.error(str(e)) | |
147 SetChromeTimeoutScale(self._device, self.GetTimeoutScale()) | |
148 | |
149 def CleanUpEnvironment(self): | |
150 SetChromeTimeoutScale(self._device, None) | |
151 | |
152 def GetTimeoutScale(self): | |
153 # Very slow startup. | |
154 return 20.0 | |
155 | |
156 | |
157 class ValgrindTool(BaseTool): | |
158 """Base abstract class for Valgrind tools.""" | |
159 | |
160 VG_DIR = '/data/local/tmp/valgrind' | |
161 VGLOGS_DIR = '/data/local/tmp/vglogs' | |
162 | |
163 def __init__(self, device): | |
164 super(ValgrindTool, self).__init__() | |
165 self._device = device | |
166 # exactly 31 chars, SystemProperties::PROP_NAME_MAX | |
167 self._wrap_properties = ['wrap.com.google.android.apps.ch', | |
168 'wrap.org.chromium.native_test'] | |
169 | |
170 @classmethod | |
171 def CopyFiles(cls, device): | |
172 """Copies Valgrind tools to the device.""" | |
173 device.RunShellCommand( | |
174 'rm -r %s; mkdir %s' % (ValgrindTool.VG_DIR, ValgrindTool.VG_DIR)) | |
175 device.RunShellCommand( | |
176 'rm -r %s; mkdir %s' % (ValgrindTool.VGLOGS_DIR, | |
177 ValgrindTool.VGLOGS_DIR)) | |
178 files = cls.GetFilesForTool() | |
179 device.PushChangedFiles( | |
180 [((os.path.join(DIR_SOURCE_ROOT, f), | |
181 os.path.join(ValgrindTool.VG_DIR, os.path.basename(f))) | |
182 for f in files)]) | |
183 | |
184 def SetupEnvironment(self): | |
185 """Sets up device environment.""" | |
186 self._device.RunShellCommand('chmod 777 /data/local/tmp') | |
187 self._device.RunShellCommand('setenforce 0') | |
188 for prop in self._wrap_properties: | |
189 self._device.RunShellCommand( | |
190 'setprop %s "logwrapper %s"' % (prop, self.GetTestWrapper())) | |
191 SetChromeTimeoutScale(self._device, self.GetTimeoutScale()) | |
192 | |
193 def CleanUpEnvironment(self): | |
194 """Cleans up device environment.""" | |
195 for prop in self._wrap_properties: | |
196 self._device.RunShellCommand('setprop %s ""' % (prop,)) | |
197 SetChromeTimeoutScale(self._device, None) | |
198 | |
199 @staticmethod | |
200 def GetFilesForTool(): | |
201 """Returns a list of file names for the tool.""" | |
202 raise NotImplementedError() | |
203 | |
204 def NeedsDebugInfo(self): | |
205 """Whether this tool requires debug info. | |
206 | |
207 Returns: | |
208 True if this tool can not work with stripped binaries. | |
209 """ | |
210 return True | |
211 | |
212 | |
213 class MemcheckTool(ValgrindTool): | |
214 """Memcheck tool.""" | |
215 | |
216 def __init__(self, device): | |
217 super(MemcheckTool, self).__init__(device) | |
218 | |
219 @staticmethod | |
220 def GetFilesForTool(): | |
221 """Returns a list of file names for the tool.""" | |
222 return ['tools/valgrind/android/vg-chrome-wrapper.sh', | |
223 'tools/valgrind/memcheck/suppressions.txt', | |
224 'tools/valgrind/memcheck/suppressions_android.txt'] | |
225 | |
226 def GetTestWrapper(self): | |
227 """Returns a string that is to be prepended to the test command line.""" | |
228 return ValgrindTool.VG_DIR + '/' + 'vg-chrome-wrapper.sh' | |
229 | |
230 def GetTimeoutScale(self): | |
231 """Returns a multiplier that should be applied to timeout values.""" | |
232 return 30 | |
233 | |
234 | |
235 class TSanTool(ValgrindTool): | |
236 """ThreadSanitizer tool. See http://code.google.com/p/data-race-test .""" | |
237 | |
238 def __init__(self, device): | |
239 super(TSanTool, self).__init__(device) | |
240 | |
241 @staticmethod | |
242 def GetFilesForTool(): | |
243 """Returns a list of file names for the tool.""" | |
244 return ['tools/valgrind/android/vg-chrome-wrapper-tsan.sh', | |
245 'tools/valgrind/tsan/suppressions.txt', | |
246 'tools/valgrind/tsan/suppressions_android.txt', | |
247 'tools/valgrind/tsan/ignores.txt'] | |
248 | |
249 def GetTestWrapper(self): | |
250 """Returns a string that is to be prepended to the test command line.""" | |
251 return ValgrindTool.VG_DIR + '/' + 'vg-chrome-wrapper-tsan.sh' | |
252 | |
253 def GetTimeoutScale(self): | |
254 """Returns a multiplier that should be applied to timeout values.""" | |
255 return 30.0 | |
256 | |
257 | |
258 TOOL_REGISTRY = { | |
259 'memcheck': MemcheckTool, | |
260 'memcheck-renderer': MemcheckTool, | |
261 'tsan': TSanTool, | |
262 'tsan-renderer': TSanTool, | |
263 'asan': AddressSanitizerTool, | |
264 } | |
265 | |
266 | |
267 def CreateTool(tool_name, device): | |
268 """Creates a tool with the specified tool name. | |
269 | |
270 Args: | |
271 tool_name: Name of the tool to create. | |
272 device: A DeviceUtils instance. | |
273 Returns: | |
274 A tool for the specified tool_name. | |
275 """ | |
276 if not tool_name: | |
277 return BaseTool() | |
278 | |
279 ctor = TOOL_REGISTRY.get(tool_name) | |
280 if ctor: | |
281 return ctor(device) | |
282 else: | |
283 print 'Unknown tool %s, available tools: %s' % ( | |
284 tool_name, ', '.join(sorted(TOOL_REGISTRY.keys()))) | |
285 sys.exit(1) | |
286 | |
287 def PushFilesForTool(tool_name, device): | |
288 """Pushes the files required for |tool_name| to |device|. | |
289 | |
290 Args: | |
291 tool_name: Name of the tool to create. | |
292 device: A DeviceUtils instance. | |
293 """ | |
294 if not tool_name: | |
295 return | |
296 | |
297 clazz = TOOL_REGISTRY.get(tool_name) | |
298 if clazz: | |
299 clazz.CopyFiles(device) | |
300 else: | |
301 print 'Unknown tool %s, available tools: %s' % ( | |
302 tool_name, ', '.join(sorted(TOOL_REGISTRY.keys()))) | |
303 sys.exit(1) | |
304 | |
OLD | NEW |