Adding Peter Thatcher to the owners file.
[chromium-blink-merge.git] / build / android / pylib / valgrind_tools.py
blob99719d08114d26522c446eb66122a85756372747
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.
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.
9 The interface is intended to be used as follows.
11 1. For tests that simply run a native process (i.e. no activity is spawned):
13 Call tool.CopyFiles(device).
14 Prepend test command line with tool.GetTestWrapper().
16 2. For tests that spawn an activity:
18 Call tool.CopyFiles(device).
19 Call tool.SetupEnvironment().
20 Run the test as usual.
21 Call tool.CleanUpEnvironment().
22 """
23 # pylint: disable=R0201
25 import glob
26 import logging
27 import os.path
28 import subprocess
29 import sys
31 from pylib.constants import DIR_SOURCE_ROOT
32 from pylib.device import device_errors
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)
45 class BaseTool(object):
46 """A tool that does nothing."""
48 def __init__(self):
49 """Does nothing."""
50 pass
52 def GetTestWrapper(self):
53 """Returns a string that is to be prepended to the test command line."""
54 return ''
56 def GetUtilWrapper(self):
57 """Returns the wrapper name for the utilities.
59 Returns:
60 A string that is to be prepended to the command line of utility
61 processes (forwarder, etc.).
62 """
63 return ''
65 @classmethod
66 def CopyFiles(cls, device):
67 """Copies tool-specific files to the device, create directories, etc."""
68 pass
70 def SetupEnvironment(self):
71 """Sets up the system environment for a test.
73 This is a good place to set system properties.
74 """
75 pass
77 def CleanUpEnvironment(self):
78 """Cleans up environment."""
79 pass
81 def GetTimeoutScale(self):
82 """Returns a multiplier that should be applied to timeout values."""
83 return 1.0
85 def NeedsDebugInfo(self):
86 """Whether this tool requires debug info.
88 Returns:
89 True if this tool can not work with stripped binaries.
90 """
91 return False
94 class AddressSanitizerTool(BaseTool):
95 """AddressSanitizer tool."""
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'
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())
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()
128 def GetTestWrapper(self):
129 return AddressSanitizerTool.WRAPPER_NAME
131 def GetUtilWrapper(self):
132 """Returns the wrapper for utilities, such as forwarder.
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()
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())
149 def CleanUpEnvironment(self):
150 SetChromeTimeoutScale(self._device, None)
152 def GetTimeoutScale(self):
153 # Very slow startup.
154 return 20.0
157 class ValgrindTool(BaseTool):
158 """Base abstract class for Valgrind tools."""
160 VG_DIR = '/data/local/tmp/valgrind'
161 VGLOGS_DIR = '/data/local/tmp/vglogs'
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']
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)])
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())
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)
199 @staticmethod
200 def GetFilesForTool():
201 """Returns a list of file names for the tool."""
202 raise NotImplementedError()
204 def NeedsDebugInfo(self):
205 """Whether this tool requires debug info.
207 Returns:
208 True if this tool can not work with stripped binaries.
210 return True
213 class MemcheckTool(ValgrindTool):
214 """Memcheck tool."""
216 def __init__(self, device):
217 super(MemcheckTool, self).__init__(device)
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']
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'
230 def GetTimeoutScale(self):
231 """Returns a multiplier that should be applied to timeout values."""
232 return 30
235 class TSanTool(ValgrindTool):
236 """ThreadSanitizer tool. See http://code.google.com/p/data-race-test ."""
238 def __init__(self, device):
239 super(TSanTool, self).__init__(device)
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']
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'
253 def GetTimeoutScale(self):
254 """Returns a multiplier that should be applied to timeout values."""
255 return 30.0
258 TOOL_REGISTRY = {
259 'memcheck': MemcheckTool,
260 'memcheck-renderer': MemcheckTool,
261 'tsan': TSanTool,
262 'tsan-renderer': TSanTool,
263 'asan': AddressSanitizerTool,
267 def CreateTool(tool_name, device):
268 """Creates a tool with the specified tool name.
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.
276 if not tool_name:
277 return BaseTool()
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)
287 def PushFilesForTool(tool_name, device):
288 """Pushes the files required for |tool_name| to |device|.
290 Args:
291 tool_name: Name of the tool to create.
292 device: A DeviceUtils instance.
294 if not tool_name:
295 return
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)