1#!/usr/bin/env python 2# Copyright (C) 2017 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import argparse 17import os 18import re 19import functools 20import logging 21import subprocess 22import sys 23import tempfile 24import time 25""" Runs a test executable on Android. 26 27Takes care of pushing the extra shared libraries that might be required by 28some sanitizers. Propagates the test return code to the host, exiting with 290 only if the test execution succeeds on the device. 30""" 31 32ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 33ADB_PATH = os.path.join(ROOT_DIR, 'buildtools/android_sdk/platform-tools/adb') 34 35 36def RetryOn(exc_type=(), returns_falsy=False, retries=5): 37 """Decorator to retry a function in case of errors or falsy values. 38 39 Implements exponential backoff between retries. 40 41 Args: 42 exc_type: Type of exceptions to catch and retry on. May also pass a tuple 43 of exceptions to catch and retry on any of them. Defaults to catching no 44 exceptions at all. 45 returns_falsy: If True then the function will be retried until it stops 46 returning a "falsy" value (e.g. None, False, 0, [], etc.). If equal to 47 'raise' and the function keeps returning falsy values after all retries, 48 then the decorator will raise a ValueError. 49 retries: Max number of retry attempts. After exhausting that number of 50 attempts the function will be called with no safeguards: any exceptions 51 will be raised and falsy values returned to the caller (except when 52 returns_falsy='raise'). 53 """ 54 55 def Decorator(f): 56 57 @functools.wraps(f) 58 def Wrapper(*args, **kwargs): 59 wait = 1 60 this_retries = kwargs.pop('retries', retries) 61 for _ in range(this_retries): 62 retry_reason = None 63 try: 64 value = f(*args, **kwargs) 65 except exc_type as exc: 66 retry_reason = 'raised %s' % type(exc).__name__ 67 if retry_reason is None: 68 if returns_falsy and not value: 69 retry_reason = 'returned %r' % value 70 else: 71 return value # Success! 72 print('{} {}, will retry in {} second{} ...'.format( 73 f.__name__, retry_reason, wait, '' if wait == 1 else 's')) 74 time.sleep(wait) 75 wait *= 2 76 value = f(*args, **kwargs) # Last try to run with no safeguards. 77 if returns_falsy == 'raise' and not value: 78 raise ValueError('%s returned %r' % (f.__name__, value)) 79 return value 80 81 return Wrapper 82 83 return Decorator 84 85 86def AdbCall(*args): 87 cmd = [ADB_PATH] + list(args) 88 print '> adb ' + ' '.join(args) 89 return subprocess.check_call(cmd) 90 91 92def AdbPush(host, device): 93 if not os.path.exists(host): 94 logging.fatal('Cannot find %s. Was it built?', host) 95 cmd = [ADB_PATH, 'push', host, device] 96 print '> adb push ' + ' '.join(cmd[2:]) 97 with open(os.devnull) as devnull: 98 return subprocess.check_call(cmd, stdout=devnull) 99 100 101def GetProp(prop): 102 cmd = [ADB_PATH, 'shell', 'getprop', prop] 103 print '> adb ' + ' '.join(cmd) 104 output = subprocess.check_output(cmd) 105 lines = output.splitlines() 106 assert len(lines) == 1, 'Expected output to have one line: {}'.format(output) 107 print lines[0] 108 return lines[0] 109 110 111@RetryOn([subprocess.CalledProcessError], returns_falsy=True, retries=10) 112def WaitForBootCompletion(): 113 return GetProp('sys.boot_completed') == '1' 114 115 116def EnumerateDataDeps(): 117 with open(os.path.join(ROOT_DIR, 'tools', 'test_data.txt')) as f: 118 lines = f.readlines() 119 for line in (line.strip() for line in lines if not line.startswith('#')): 120 assert os.path.exists(line), line 121 yield line 122 123 124def Main(): 125 parser = argparse.ArgumentParser() 126 parser.add_argument('--no-cleanup', '-n', action='store_true') 127 parser.add_argument('--no-data-deps', '-x', action='store_true') 128 parser.add_argument('--env', '-e', action='append') 129 parser.add_argument('out_dir', help='out/android/') 130 parser.add_argument('test_name', help='perfetto_unittests') 131 parser.add_argument('cmd_args', nargs=argparse.REMAINDER) 132 args = parser.parse_args() 133 134 test_bin = os.path.join(args.out_dir, args.test_name) 135 assert os.path.exists(test_bin) 136 137 print 'Waiting for device ...' 138 AdbCall('wait-for-device') 139 # WaitForBootCompletion() 140 AdbCall('root') 141 AdbCall('wait-for-device') 142 143 target_dir = '/data/local/tmp/' + args.test_name 144 AdbCall('shell', 'rm -rf "%s"; mkdir -p "%s"' % (2 * (target_dir,))) 145 # Some tests require the trace directory to exist, while true for android 146 # devices in general some emulators might not have it set up. So we check to 147 # see if it exists, and if not create it. 148 trace_dir = '/data/misc/perfetto-traces' 149 AdbCall('shell', 'test -d "%s" || mkdir -p "%s"' % (2 * (trace_dir,))) 150 AdbCall('shell', 'rm -rf "%s/*"; ' % trace_dir) 151 AdbCall('shell', 'mkdir -p /data/nativetest') 152 # This needs to go into /data/nativetest in order to have the system linker 153 # namespace applied, which we need in order to link libdexfile_external.so. 154 # This gets linked into our tests via libundwindstack.so. 155 # 156 # See https://android.googlesource.com/platform/system/core/+/master/rootdir/etc/ld.config.txt. 157 AdbPush(test_bin, "/data/nativetest") 158 159 # These two binaries are required to run perfetto_integrationtests. 160 AdbPush(os.path.join(args.out_dir, "perfetto"), "/data/nativetest") 161 AdbPush(os.path.join(args.out_dir, "trigger_perfetto"), "/data/nativetest") 162 163 if not args.no_data_deps: 164 for dep in EnumerateDataDeps(): 165 AdbPush(os.path.join(ROOT_DIR, dep), target_dir + '/' + dep) 166 167 # LLVM sanitizers require to sideload a libclangrtXX.so on the device. 168 sanitizer_libs = os.path.join(args.out_dir, 'sanitizer_libs') 169 env = ' '.join(args.env if args.env is not None else []) + ' ' 170 if os.path.exists(sanitizer_libs): 171 AdbPush(sanitizer_libs, target_dir) 172 env += 'LD_LIBRARY_PATH="%s/sanitizer_libs" ' % (target_dir) 173 cmd = 'cd %s;' % target_dir 174 binary = env + '/data/nativetest/%s' % args.test_name 175 cmd += binary 176 if args.cmd_args: 177 actual_args = [arg.replace(args.test_name, binary) for arg in args.cmd_args] 178 cmd += ' ' + ' '.join(actual_args) 179 print cmd 180 retcode = subprocess.call([ADB_PATH, 'shell', cmd]) 181 if not args.no_cleanup: 182 AdbCall('shell', 'rm -rf "%s"' % target_dir) 183 184 # Smoke test that adb shell is actually propagating retcode. adb has a history 185 # of breaking this. 186 test_code = subprocess.call([ADB_PATH, 'shell', 'echo Done; exit 42']) 187 if test_code != 42: 188 logging.fatal('adb is incorrectly propagating the exit code') 189 return 1 190 191 return retcode 192 193 194if __name__ == '__main__': 195 sys.exit(Main()) 196