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