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', 'test -f /sys/kernel/tracing/tracing_on ' + 158 '&& echo 0 > /sys/kernel/tracing/tracing_on || true') 159 AdbCall('shell', 'test -f /sys/kernel/debug/tracing/tracing_on ' + 160 '&& echo 0 > /sys/kernel/debug/tracing/tracing_on || true') 161 162 # This needs to go into /data/nativetest in order to have the system linker 163 # namespace applied, which we need in order to link libdexfile.so. 164 # This gets linked into our tests via libundwindstack.so. 165 # 166 # See https://android.googlesource.com/platform/system/core/+/main/rootdir/etc/ld.config.txt. 167 AdbPush(test_bin, "/data/nativetest") 168 169 # These two binaries are required to run perfetto_integrationtests. 170 AdbPush(os.path.join(args.out_dir, "perfetto"), "/data/nativetest") 171 AdbPush(os.path.join(args.out_dir, "trigger_perfetto"), "/data/nativetest") 172 173 if not args.no_data_deps: 174 for dep in EnumerateDataDeps(): 175 AdbPush(os.path.join(ROOT_DIR, dep), target_dir + '/' + dep) 176 177 # LLVM sanitizers require to sideload a libclangrtXX.so on the device. 178 sanitizer_libs = os.path.join(args.out_dir, 'sanitizer_libs') 179 env = ' '.join(args.env if args.env is not None else []) + ' ' 180 if os.path.exists(sanitizer_libs): 181 AdbPush(sanitizer_libs, target_dir) 182 env += 'LD_LIBRARY_PATH="%s/sanitizer_libs" ' % (target_dir) 183 cmd = 'cd %s;' % target_dir 184 binary = env + '/data/nativetest/%s' % args.test_name 185 cmd += binary 186 if args.cmd_args: 187 actual_args = [arg.replace(args.test_name, binary) for arg in args.cmd_args] 188 cmd += ' ' + ' '.join(actual_args) 189 print(cmd) 190 retcode = subprocess.call([ADB_PATH, 'shell', '-tt', cmd]) 191 if not args.no_cleanup: 192 AdbCall('shell', 'rm -rf "%s"' % target_dir) 193 194 # Smoke test that adb shell is actually propagating retcode. adb has a history 195 # of breaking this. 196 test_code = subprocess.call([ADB_PATH, 'shell', '-tt', 'echo Done; exit 42']) 197 if test_code != 42: 198 logging.fatal('adb is incorrectly propagating the exit code') 199 return 1 200 201 return retcode 202 203 204if __name__ == '__main__': 205 sys.exit(Main()) 206