Primiano Tucci | 34bc559 | 2021-02-19 17:53:36 +0100 | [diff] [blame^] | 1 | #!/usr/bin/env python3 |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 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 | |
| 16 | import argparse |
| 17 | import os |
Hector Dearman | 0d30587 | 2017-10-18 16:18:07 +0100 | [diff] [blame] | 18 | import functools |
| 19 | import logging |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 20 | import subprocess |
| 21 | import sys |
Hector Dearman | 273823c | 2017-10-13 18:12:27 +0100 | [diff] [blame] | 22 | import time |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 23 | """ Runs a test executable on Android. |
| 24 | |
| 25 | Takes care of pushing the extra shared libraries that might be required by |
| 26 | some sanitizers. Propagates the test return code to the host, exiting with |
| 27 | 0 only if the test execution succeeds on the device. |
| 28 | """ |
| 29 | |
| 30 | ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| 31 | ADB_PATH = os.path.join(ROOT_DIR, 'buildtools/android_sdk/platform-tools/adb') |
| 32 | |
| 33 | |
Hector Dearman | 0d30587 | 2017-10-18 16:18:07 +0100 | [diff] [blame] | 34 | def 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 | """ |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 52 | |
Hector Dearman | 0d30587 | 2017-10-18 16:18:07 +0100 | [diff] [blame] | 53 | def Decorator(f): |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 54 | |
Hector Dearman | 0d30587 | 2017-10-18 16:18:07 +0100 | [diff] [blame] | 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 |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 78 | |
Hector Dearman | 0d30587 | 2017-10-18 16:18:07 +0100 | [diff] [blame] | 79 | return Wrapper |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 80 | |
Hector Dearman | 0d30587 | 2017-10-18 16:18:07 +0100 | [diff] [blame] | 81 | return Decorator |
| 82 | |
| 83 | |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 84 | def AdbCall(*args): |
| 85 | cmd = [ADB_PATH] + list(args) |
Primiano Tucci | 34bc559 | 2021-02-19 17:53:36 +0100 | [diff] [blame^] | 86 | print('> adb ' + ' '.join(args)) |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 87 | return subprocess.check_call(cmd) |
| 88 | |
| 89 | |
Primiano Tucci | d4aa37b | 2019-08-21 13:38:06 +0200 | [diff] [blame] | 90 | def AdbPush(host, device): |
Primiano Tucci | e094eec | 2020-03-18 16:58:21 +0000 | [diff] [blame] | 91 | if not os.path.exists(host): |
| 92 | logging.fatal('Cannot find %s. Was it built?', host) |
Primiano Tucci | d4aa37b | 2019-08-21 13:38:06 +0200 | [diff] [blame] | 93 | cmd = [ADB_PATH, 'push', host, device] |
Primiano Tucci | 34bc559 | 2021-02-19 17:53:36 +0100 | [diff] [blame^] | 94 | print('> adb push ' + ' '.join(cmd[2:])) |
| 95 | with open(os.devnull, 'wb') as devnull: |
Primiano Tucci | d4aa37b | 2019-08-21 13:38:06 +0200 | [diff] [blame] | 96 | return subprocess.check_call(cmd, stdout=devnull) |
| 97 | |
| 98 | |
Hector Dearman | 273823c | 2017-10-13 18:12:27 +0100 | [diff] [blame] | 99 | def GetProp(prop): |
| 100 | cmd = [ADB_PATH, 'shell', 'getprop', prop] |
Primiano Tucci | 34bc559 | 2021-02-19 17:53:36 +0100 | [diff] [blame^] | 101 | print('> adb ' + ' '.join(cmd)) |
| 102 | output = subprocess.check_output(cmd).decode() |
Hector Dearman | 273823c | 2017-10-13 18:12:27 +0100 | [diff] [blame] | 103 | lines = output.splitlines() |
Hector Dearman | 0d30587 | 2017-10-18 16:18:07 +0100 | [diff] [blame] | 104 | assert len(lines) == 1, 'Expected output to have one line: {}'.format(output) |
Primiano Tucci | 34bc559 | 2021-02-19 17:53:36 +0100 | [diff] [blame^] | 105 | print(lines[0]) |
Hector Dearman | 273823c | 2017-10-13 18:12:27 +0100 | [diff] [blame] | 106 | return lines[0] |
| 107 | |
| 108 | |
Hector Dearman | 0d30587 | 2017-10-18 16:18:07 +0100 | [diff] [blame] | 109 | @RetryOn([subprocess.CalledProcessError], returns_falsy=True, retries=10) |
| 110 | def WaitForBootCompletion(): |
Hector Dearman | 273823c | 2017-10-13 18:12:27 +0100 | [diff] [blame] | 111 | return GetProp('sys.boot_completed') == '1' |
| 112 | |
| 113 | |
Primiano Tucci | c6259a9 | 2017-11-10 13:51:55 +0000 | [diff] [blame] | 114 | def EnumerateDataDeps(): |
Primiano Tucci | 7a40e4d | 2017-12-06 09:51:09 +0000 | [diff] [blame] | 115 | with open(os.path.join(ROOT_DIR, 'tools', 'test_data.txt')) as f: |
Primiano Tucci | c6259a9 | 2017-11-10 13:51:55 +0000 | [diff] [blame] | 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 | |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 122 | def Main(): |
| 123 | parser = argparse.ArgumentParser() |
| 124 | parser.add_argument('--no-cleanup', '-n', action='store_true') |
Primiano Tucci | 480c68b | 2017-12-19 09:18:00 +0100 | [diff] [blame] | 125 | parser.add_argument('--no-data-deps', '-x', action='store_true') |
Florian Mayer | 95d86c6 | 2021-02-18 19:09:26 +0000 | [diff] [blame] | 126 | parser.add_argument('--system-adb', action='store_true') |
Lalit Maganti | 131b6e5 | 2018-03-29 18:29:31 +0100 | [diff] [blame] | 127 | parser.add_argument('--env', '-e', action='append') |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 128 | parser.add_argument('out_dir', help='out/android/') |
Lalit Maganti | 79f2d7b | 2018-01-23 18:27:33 +0000 | [diff] [blame] | 129 | parser.add_argument('test_name', help='perfetto_unittests') |
Sami Kyostila | 4473d9f | 2017-11-24 17:45:24 +0000 | [diff] [blame] | 130 | parser.add_argument('cmd_args', nargs=argparse.REMAINDER) |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 131 | args = parser.parse_args() |
| 132 | |
Florian Mayer | 95d86c6 | 2021-02-18 19:09:26 +0000 | [diff] [blame] | 133 | if args.system_adb: |
| 134 | global ADB_PATH |
| 135 | ADB_PATH = 'adb' |
| 136 | |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 137 | test_bin = os.path.join(args.out_dir, args.test_name) |
| 138 | assert os.path.exists(test_bin) |
| 139 | |
Primiano Tucci | 34bc559 | 2021-02-19 17:53:36 +0100 | [diff] [blame^] | 140 | print('Waiting for device ...') |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 141 | AdbCall('wait-for-device') |
Lalit Maganti | 367fcd5 | 2018-02-05 16:06:13 +0000 | [diff] [blame] | 142 | # WaitForBootCompletion() |
Lalit Maganti | 79f2d7b | 2018-01-23 18:27:33 +0000 | [diff] [blame] | 143 | AdbCall('root') |
| 144 | AdbCall('wait-for-device') |
Hector Dearman | 273823c | 2017-10-13 18:12:27 +0100 | [diff] [blame] | 145 | |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 146 | target_dir = '/data/local/tmp/' + args.test_name |
| 147 | AdbCall('shell', 'rm -rf "%s"; mkdir -p "%s"' % (2 * (target_dir,))) |
Stephen Nusko | e46a57b | 2019-04-15 15:12:05 +0100 | [diff] [blame] | 148 | # Some tests require the trace directory to exist, while true for android |
| 149 | # devices in general some emulators might not have it set up. So we check to |
| 150 | # see if it exists, and if not create it. |
Primiano Tucci | f0706d1 | 2021-01-14 15:20:16 +0100 | [diff] [blame] | 151 | trace_dir = '/data/misc/perfetto-traces/bugreport' |
Stephen Nusko | e46a57b | 2019-04-15 15:12:05 +0100 | [diff] [blame] | 152 | AdbCall('shell', 'test -d "%s" || mkdir -p "%s"' % (2 * (trace_dir,))) |
| 153 | AdbCall('shell', 'rm -rf "%s/*"; ' % trace_dir) |
Florian Mayer | fd60cc8 | 2019-02-06 12:11:09 +0000 | [diff] [blame] | 154 | AdbCall('shell', 'mkdir -p /data/nativetest') |
Primiano Tucci | 587750b | 2020-10-27 15:37:01 +0100 | [diff] [blame] | 155 | AdbCall('shell', 'echo 0 > /d/tracing/tracing_on') |
| 156 | |
Florian Mayer | 2c9d741 | 2019-02-01 16:09:18 +0000 | [diff] [blame] | 157 | # This needs to go into /data/nativetest in order to have the system linker |
| 158 | # namespace applied, which we need in order to link libdexfile_external.so. |
| 159 | # This gets linked into our tests via libundwindstack.so. |
| 160 | # |
Florian Mayer | 98b1753 | 2019-04-05 17:32:13 +0100 | [diff] [blame] | 161 | # See https://android.googlesource.com/platform/system/core/+/master/rootdir/etc/ld.config.txt. |
Primiano Tucci | d4aa37b | 2019-08-21 13:38:06 +0200 | [diff] [blame] | 162 | AdbPush(test_bin, "/data/nativetest") |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 163 | |
Primiano Tucci | e094eec | 2020-03-18 16:58:21 +0000 | [diff] [blame] | 164 | # These two binaries are required to run perfetto_integrationtests. |
| 165 | AdbPush(os.path.join(args.out_dir, "perfetto"), "/data/nativetest") |
| 166 | AdbPush(os.path.join(args.out_dir, "trigger_perfetto"), "/data/nativetest") |
| 167 | |
Primiano Tucci | 480c68b | 2017-12-19 09:18:00 +0100 | [diff] [blame] | 168 | if not args.no_data_deps: |
| 169 | for dep in EnumerateDataDeps(): |
Primiano Tucci | d4aa37b | 2019-08-21 13:38:06 +0200 | [diff] [blame] | 170 | AdbPush(os.path.join(ROOT_DIR, dep), target_dir + '/' + dep) |
Primiano Tucci | c6259a9 | 2017-11-10 13:51:55 +0000 | [diff] [blame] | 171 | |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 172 | # LLVM sanitizers require to sideload a libclangrtXX.so on the device. |
| 173 | sanitizer_libs = os.path.join(args.out_dir, 'sanitizer_libs') |
Lalit Maganti | 131b6e5 | 2018-03-29 18:29:31 +0100 | [diff] [blame] | 174 | env = ' '.join(args.env if args.env is not None else []) + ' ' |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 175 | if os.path.exists(sanitizer_libs): |
Primiano Tucci | d4aa37b | 2019-08-21 13:38:06 +0200 | [diff] [blame] | 176 | AdbPush(sanitizer_libs, target_dir) |
Lalit Maganti | 131b6e5 | 2018-03-29 18:29:31 +0100 | [diff] [blame] | 177 | env += 'LD_LIBRARY_PATH="%s/sanitizer_libs" ' % (target_dir) |
Primiano Tucci | 834fdc7 | 2019-10-04 11:33:44 +0100 | [diff] [blame] | 178 | cmd = 'cd %s;' % target_dir |
Florian Mayer | 2c9d741 | 2019-02-01 16:09:18 +0000 | [diff] [blame] | 179 | binary = env + '/data/nativetest/%s' % args.test_name |
Lalit Maganti | bfc3d3e | 2018-03-22 20:28:38 +0000 | [diff] [blame] | 180 | cmd += binary |
Sami Kyostila | 4473d9f | 2017-11-24 17:45:24 +0000 | [diff] [blame] | 181 | if args.cmd_args: |
Lalit Maganti | bfc3d3e | 2018-03-22 20:28:38 +0000 | [diff] [blame] | 182 | actual_args = [arg.replace(args.test_name, binary) for arg in args.cmd_args] |
| 183 | cmd += ' ' + ' '.join(actual_args) |
Primiano Tucci | 34bc559 | 2021-02-19 17:53:36 +0100 | [diff] [blame^] | 184 | print(cmd) |
| 185 | retcode = subprocess.call([ADB_PATH, 'shell', '-tt', cmd]) |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 186 | if not args.no_cleanup: |
| 187 | AdbCall('shell', 'rm -rf "%s"' % target_dir) |
Primiano Tucci | d4aa37b | 2019-08-21 13:38:06 +0200 | [diff] [blame] | 188 | |
| 189 | # Smoke test that adb shell is actually propagating retcode. adb has a history |
| 190 | # of breaking this. |
Primiano Tucci | 34bc559 | 2021-02-19 17:53:36 +0100 | [diff] [blame^] | 191 | test_code = subprocess.call([ADB_PATH, 'shell', '-tt', 'echo Done; exit 42']) |
Primiano Tucci | d4aa37b | 2019-08-21 13:38:06 +0200 | [diff] [blame] | 192 | if test_code != 42: |
| 193 | logging.fatal('adb is incorrectly propagating the exit code') |
| 194 | return 1 |
| 195 | |
Primiano Tucci | ac3fd3e | 2017-09-29 14:29:15 +0100 | [diff] [blame] | 196 | return retcode |
| 197 | |
| 198 | |
| 199 | if __name__ == '__main__': |
| 200 | sys.exit(Main()) |