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