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 if "perfetto_integrationtest" in args.test_name: 171 AdbPush(os.path.join(args.out_dir, "perfetto"), "/data/nativetest") 172 AdbPush(os.path.join(args.out_dir, "trigger_perfetto"), "/data/nativetest") 173 174 if not args.no_data_deps: 175 for dep in EnumerateDataDeps(): 176 AdbPush(os.path.join(ROOT_DIR, dep), target_dir + '/' + dep) 177 178 # LLVM sanitizers require to sideload a libclangrtXX.so on the device. 179 sanitizer_libs = os.path.join(args.out_dir, 'sanitizer_libs') 180 env = ' '.join(args.env if args.env is not None else []) + ' ' 181 if os.path.exists(sanitizer_libs): 182 AdbPush(sanitizer_libs, target_dir) 183 env += 'LD_LIBRARY_PATH="%s/sanitizer_libs" ' % (target_dir) 184 cmd = 'cd %s;' % target_dir 185 binary = env + '/data/nativetest/%s' % args.test_name 186 cmd += binary 187 if args.cmd_args: 188 actual_args = [arg.replace(args.test_name, binary) for arg in args.cmd_args] 189 cmd += ' ' + ' '.join(actual_args) 190 print(cmd) 191 retcode = subprocess.call([ADB_PATH, 'shell', '-tt', cmd]) 192 if not args.no_cleanup: 193 AdbCall('shell', 'rm -rf "%s"' % target_dir) 194 195 # Smoke test that adb shell is actually propagating retcode. adb has a history 196 # of breaking this. 197 test_code = subprocess.call([ADB_PATH, 'shell', '-tt', 'echo Done; exit 42']) 198 if test_code != 42: 199 logging.fatal('adb is incorrectly propagating the exit code') 200 return 1 201 202 return retcode 203 204 205if __name__ == '__main__': 206 sys.exit(Main()) 207