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 26 27""" Runs a test executable on Android. 28 29Takes care of pushing the extra shared libraries that might be required by 30some sanitizers. Propagates the test return code to the host, exiting with 310 only if the test execution succeeds on the device. 32""" 33 34ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 35ADB_PATH = os.path.join(ROOT_DIR, 'buildtools/android_sdk/platform-tools/adb') 36 37 38def RetryOn(exc_type=(), returns_falsy=False, retries=5): 39 """Decorator to retry a function in case of errors or falsy values. 40 41 Implements exponential backoff between retries. 42 43 Args: 44 exc_type: Type of exceptions to catch and retry on. May also pass a tuple 45 of exceptions to catch and retry on any of them. Defaults to catching no 46 exceptions at all. 47 returns_falsy: If True then the function will be retried until it stops 48 returning a "falsy" value (e.g. None, False, 0, [], etc.). If equal to 49 'raise' and the function keeps returning falsy values after all retries, 50 then the decorator will raise a ValueError. 51 retries: Max number of retry attempts. After exhausting that number of 52 attempts the function will be called with no safeguards: any exceptions 53 will be raised and falsy values returned to the caller (except when 54 returns_falsy='raise'). 55 """ 56 def Decorator(f): 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 return Wrapper 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 GetProp(prop): 91 cmd = [ADB_PATH, 'shell', 'getprop', prop] 92 print '> adb ' + ' '.join(cmd) 93 output = subprocess.check_output(cmd) 94 lines = output.splitlines() 95 assert len(lines) == 1, 'Expected output to have one line: {}'.format(output) 96 print lines[0] 97 return lines[0] 98 99 100@RetryOn([subprocess.CalledProcessError], returns_falsy=True, retries=10) 101def WaitForBootCompletion(): 102 return GetProp('sys.boot_completed') == '1' 103 104 105def EnumerateDataDeps(): 106 with open(os.path.join(ROOT_DIR, 'tools', 'test_data.txt')) as f: 107 lines = f.readlines() 108 for line in (line.strip() for line in lines if not line.startswith('#')): 109 assert os.path.exists(line), line 110 yield line 111 112 113def Main(): 114 parser = argparse.ArgumentParser() 115 parser.add_argument('--no-cleanup', '-n', action='store_true') 116 parser.add_argument('--no-data-deps', '-x', action='store_true') 117 parser.add_argument('--env', '-e', action='append') 118 parser.add_argument('out_dir', help='out/android/') 119 parser.add_argument('test_name', help='perfetto_unittests') 120 parser.add_argument('cmd_args', nargs=argparse.REMAINDER) 121 args = parser.parse_args() 122 123 test_bin = os.path.join(args.out_dir, args.test_name) 124 assert os.path.exists(test_bin) 125 126 print 'Waiting for device ...' 127 AdbCall('wait-for-device') 128 # WaitForBootCompletion() 129 AdbCall('root') 130 AdbCall('wait-for-device') 131 132 target_dir = '/data/local/tmp/' + args.test_name 133 AdbCall('shell', 'rm -rf "%s"; mkdir -p "%s"' % (2 * (target_dir,))) 134 # Some tests require the trace directory to exist, while true for android 135 # devices in general some emulators might not have it set up. So we check to 136 # see if it exists, and if not create it. 137 trace_dir = '/data/misc/perfetto-traces' 138 AdbCall('shell', 'test -d "%s" || mkdir -p "%s"' % (2 * (trace_dir,))) 139 AdbCall('shell', 'rm -rf "%s/*"; ' % trace_dir) 140 AdbCall('shell', 'mkdir -p /data/nativetest') 141 # This needs to go into /data/nativetest in order to have the system linker 142 # namespace applied, which we need in order to link libdexfile_external.so. 143 # This gets linked into our tests via libundwindstack.so. 144 # 145 # See https://android.googlesource.com/platform/system/core/+/master/rootdir/etc/ld.config.txt. 146 AdbCall('push', test_bin, "/data/nativetest") 147 148 if not args.no_data_deps: 149 for dep in EnumerateDataDeps(): 150 AdbCall('push', os.path.join(ROOT_DIR, dep), target_dir + '/' + dep) 151 152 # LLVM sanitizers require to sideload a libclangrtXX.so on the device. 153 sanitizer_libs = os.path.join(args.out_dir, 'sanitizer_libs') 154 env = ' '.join(args.env if args.env is not None else []) + ' ' 155 if os.path.exists(sanitizer_libs): 156 AdbCall('push', sanitizer_libs, target_dir) 157 env += 'LD_LIBRARY_PATH="%s/sanitizer_libs" ' % (target_dir) 158 cmd = 'cd %s;' % target_dir; 159 binary = env + '/data/nativetest/%s' % args.test_name 160 cmd += binary 161 if args.cmd_args: 162 actual_args = [arg.replace(args.test_name, binary) for arg in args.cmd_args] 163 cmd += ' ' + ' '.join(actual_args) 164 cmd += ';echo -e "\\nTEST_RET_CODE=$?"' 165 print cmd 166 test_output = subprocess.check_output([ADB_PATH, 'shell', cmd]) 167 print test_output 168 retcode = re.search(r'^TEST_RET_CODE=(\d)', test_output, re.MULTILINE) 169 assert retcode, 'Could not find TEST_RET_CODE=N marker' 170 retcode = int(retcode.group(1)) 171 if not args.no_cleanup: 172 AdbCall('shell', 'rm -rf "%s"' % target_dir) 173 return retcode 174 175 176if __name__ == '__main__': 177 sys.exit(Main()) 178