Home | History | Annotate | Download | only in pylib
      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().
     14 Prepend test command line with tool.GetTestWrapper().
     15 
     16 2. For tests that spawn an activity:
     17 
     18 Call tool.CopyFiles().
     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   def CopyFiles(self):
     66     """Copies tool-specific files to the device, create directories, etc."""
     67     pass
     68 
     69   def SetupEnvironment(self):
     70     """Sets up the system environment for a test.
     71 
     72     This is a good place to set system properties.
     73     """
     74     pass
     75 
     76   def CleanUpEnvironment(self):
     77     """Cleans up environment."""
     78     pass
     79 
     80   def GetTimeoutScale(self):
     81     """Returns a multiplier that should be applied to timeout values."""
     82     return 1.0
     83 
     84   def NeedsDebugInfo(self):
     85     """Whether this tool requires debug info.
     86 
     87     Returns:
     88       True if this tool can not work with stripped binaries.
     89     """
     90     return False
     91 
     92 
     93 class AddressSanitizerTool(BaseTool):
     94   """AddressSanitizer tool."""
     95 
     96   WRAPPER_NAME = '/system/bin/asanwrapper'
     97   # Disable memcmp overlap check.There are blobs (gl drivers)
     98   # on some android devices that use memcmp on overlapping regions,
     99   # nothing we can do about that.
    100   EXTRA_OPTIONS = 'strict_memcmp=0,use_sigaltstack=1'
    101 
    102   def __init__(self, device):
    103     super(AddressSanitizerTool, self).__init__()
    104     self._device = device
    105     # Configure AndroidCommands to run utils (such as md5sum_bin) under ASan.
    106     # This is required because ASan is a compiler-based tool, and md5sum
    107     # includes instrumented code from base.
    108     device.old_interface.SetUtilWrapper(self.GetUtilWrapper())
    109     libs = glob.glob(os.path.join(DIR_SOURCE_ROOT,
    110                                   'third_party/llvm-build/Release+Asserts/',
    111                                   'lib/clang/*/lib/linux/',
    112                                   'libclang_rt.asan-arm-android.so'))
    113     assert len(libs) == 1
    114     self._lib = libs[0]
    115 
    116   def CopyFiles(self):
    117     """Copies ASan tools to the device."""
    118     subprocess.call([os.path.join(DIR_SOURCE_ROOT,
    119                                   'tools/android/asan/asan_device_setup.sh'),
    120                      '--device', str(self._device),
    121                      '--lib', self._lib,
    122                      '--extra-options', AddressSanitizerTool.EXTRA_OPTIONS])
    123     self._device.WaitUntilFullyBooted()
    124 
    125   def GetTestWrapper(self):
    126     return AddressSanitizerTool.WRAPPER_NAME
    127 
    128   def GetUtilWrapper(self):
    129     """Returns the wrapper for utilities, such as forwarder.
    130 
    131     AddressSanitizer wrapper must be added to all instrumented binaries,
    132     including forwarder and the like. This can be removed if such binaries
    133     were built without instrumentation. """
    134     return self.GetTestWrapper()
    135 
    136   def SetupEnvironment(self):
    137     try:
    138       self._device.EnableRoot()
    139     except device_errors.CommandFailedError as e:
    140       # Try to set the timeout scale anyway.
    141       # TODO(jbudorick) Handle this exception appropriately after interface
    142       #                 conversions are finished.
    143       logging.error(str(e))
    144     SetChromeTimeoutScale(self._device, self.GetTimeoutScale())
    145 
    146   def CleanUpEnvironment(self):
    147     SetChromeTimeoutScale(self._device, None)
    148 
    149   def GetTimeoutScale(self):
    150     # Very slow startup.
    151     return 20.0
    152 
    153 
    154 class ValgrindTool(BaseTool):
    155   """Base abstract class for Valgrind tools."""
    156 
    157   VG_DIR = '/data/local/tmp/valgrind'
    158   VGLOGS_DIR = '/data/local/tmp/vglogs'
    159 
    160   def __init__(self, device):
    161     super(ValgrindTool, self).__init__()
    162     self._device = device
    163     # exactly 31 chars, SystemProperties::PROP_NAME_MAX
    164     self._wrap_properties = ['wrap.com.google.android.apps.ch',
    165                              'wrap.org.chromium.native_test']
    166 
    167   def CopyFiles(self):
    168     """Copies Valgrind tools to the device."""
    169     self._device.RunShellCommand(
    170         'rm -r %s; mkdir %s' % (ValgrindTool.VG_DIR, ValgrindTool.VG_DIR))
    171     self._device.RunShellCommand(
    172         'rm -r %s; mkdir %s' % (ValgrindTool.VGLOGS_DIR,
    173                                 ValgrindTool.VGLOGS_DIR))
    174     files = self.GetFilesForTool()
    175     for f in files:
    176       self._device.PushChangedFiles(
    177           os.path.join(DIR_SOURCE_ROOT, f),
    178           os.path.join(ValgrindTool.VG_DIR, os.path.basename(f)))
    179 
    180   def SetupEnvironment(self):
    181     """Sets up device environment."""
    182     self._device.RunShellCommand('chmod 777 /data/local/tmp')
    183     self._device.RunShellCommand('setenforce 0')
    184     for prop in self._wrap_properties:
    185       self._device.RunShellCommand(
    186           'setprop %s "logwrapper %s"' % (prop, self.GetTestWrapper()))
    187     SetChromeTimeoutScale(self._device, self.GetTimeoutScale())
    188 
    189   def CleanUpEnvironment(self):
    190     """Cleans up device environment."""
    191     for prop in self._wrap_properties:
    192       self._device.RunShellCommand('setprop %s ""' % (prop,))
    193     SetChromeTimeoutScale(self._device, None)
    194 
    195   def GetFilesForTool(self):
    196     """Returns a list of file names for the tool."""
    197     raise NotImplementedError()
    198 
    199   def NeedsDebugInfo(self):
    200     """Whether this tool requires debug info.
    201 
    202     Returns:
    203       True if this tool can not work with stripped binaries.
    204     """
    205     return True
    206 
    207 
    208 class MemcheckTool(ValgrindTool):
    209   """Memcheck tool."""
    210 
    211   def __init__(self, device):
    212     super(MemcheckTool, self).__init__(device)
    213 
    214   def GetFilesForTool(self):
    215     """Returns a list of file names for the tool."""
    216     return ['tools/valgrind/android/vg-chrome-wrapper.sh',
    217             'tools/valgrind/memcheck/suppressions.txt',
    218             'tools/valgrind/memcheck/suppressions_android.txt']
    219 
    220   def GetTestWrapper(self):
    221     """Returns a string that is to be prepended to the test command line."""
    222     return ValgrindTool.VG_DIR + '/' + 'vg-chrome-wrapper.sh'
    223 
    224   def GetTimeoutScale(self):
    225     """Returns a multiplier that should be applied to timeout values."""
    226     return 30
    227 
    228 
    229 class TSanTool(ValgrindTool):
    230   """ThreadSanitizer tool. See http://code.google.com/p/data-race-test ."""
    231 
    232   def __init__(self, device):
    233     super(TSanTool, self).__init__(device)
    234 
    235   def GetFilesForTool(self):
    236     """Returns a list of file names for the tool."""
    237     return ['tools/valgrind/android/vg-chrome-wrapper-tsan.sh',
    238             'tools/valgrind/tsan/suppressions.txt',
    239             'tools/valgrind/tsan/suppressions_android.txt',
    240             'tools/valgrind/tsan/ignores.txt']
    241 
    242   def GetTestWrapper(self):
    243     """Returns a string that is to be prepended to the test command line."""
    244     return ValgrindTool.VG_DIR + '/' + 'vg-chrome-wrapper-tsan.sh'
    245 
    246   def GetTimeoutScale(self):
    247     """Returns a multiplier that should be applied to timeout values."""
    248     return 30.0
    249 
    250 
    251 TOOL_REGISTRY = {
    252     'memcheck': MemcheckTool,
    253     'memcheck-renderer': MemcheckTool,
    254     'tsan': TSanTool,
    255     'tsan-renderer': TSanTool,
    256     'asan': AddressSanitizerTool,
    257 }
    258 
    259 
    260 def CreateTool(tool_name, device):
    261   """Creates a tool with the specified tool name.
    262 
    263   Args:
    264     tool_name: Name of the tool to create.
    265     device: A DeviceUtils instance.
    266   Returns:
    267     A tool for the specified tool_name.
    268   """
    269   if not tool_name:
    270     return BaseTool()
    271 
    272   ctor = TOOL_REGISTRY.get(tool_name)
    273   if ctor:
    274     return ctor(device)
    275   else:
    276     print 'Unknown tool %s, available tools: %s' % (
    277         tool_name, ', '.join(sorted(TOOL_REGISTRY.keys())))
    278     sys.exit(1)
    279