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 AdbCall('push', test_bin, target_dir) 135 136 if not args.no_data_deps: 137 for dep in EnumerateDataDeps(): 138 AdbCall('push', os.path.join(ROOT_DIR, dep), target_dir + '/' + dep) 139 140 # LLVM sanitizers require to sideload a libclangrtXX.so on the device. 141 sanitizer_libs = os.path.join(args.out_dir, 'sanitizer_libs') 142 env = ' '.join(args.env if args.env is not None else []) + ' ' 143 if os.path.exists(sanitizer_libs): 144 AdbCall('push', sanitizer_libs, target_dir) 145 env += 'LD_LIBRARY_PATH="%s/sanitizer_libs" ' % (target_dir) 146 cmd = 'cd %s;' % target_dir; 147 binary = env + './%s' % args.test_name 148 cmd += binary 149 if args.cmd_args: 150 actual_args = [arg.replace(args.test_name, binary) for arg in args.cmd_args] 151 cmd += ' ' + ' '.join(actual_args) 152 cmd += ';echo -e "\\nTEST_RET_CODE=$?"' 153 print cmd 154 test_output = subprocess.check_output([ADB_PATH, 'shell', cmd]) 155 print test_output 156 retcode = re.search(r'^TEST_RET_CODE=(\d)', test_output, re.MULTILINE) 157 assert retcode, 'Could not find TEST_RET_CODE=N marker' 158 retcode = int(retcode.group(1)) 159 if not args.no_cleanup: 160 AdbCall('shell', 'rm -rf "%s"' % target_dir) 161 return retcode 162 163 164if __name__ == '__main__': 165 sys.exit(Main()) 166