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 24 import os.path 25 import sys 26 from glob import glob 27 28 from constants import DIR_SOURCE_ROOT 29 30 31 def 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 41 class 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 85 class 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 142 class 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 194 class 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 215 class 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 237 TOOL_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 246 def 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