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