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