blob: f22138dc0732231f1a7e2f40fa41ef86dae08acd [file] [log] [blame]
Primiano Tucciac3fd3e2017-09-29 14:29:15 +01001#!/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
Hector Dearman0d305872017-10-18 16:18:07 +010019import functools
20import logging
Primiano Tucciac3fd3e2017-09-29 14:29:15 +010021import subprocess
22import sys
Primiano Tuccic6259a92017-11-10 13:51:55 +000023import tempfile
Hector Dearman273823c2017-10-13 18:12:27 +010024import time
Primiano Tucciac3fd3e2017-09-29 14:29:15 +010025
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
Hector Dearman0d305872017-10-18 16:18:07 +010038def 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
Primiano Tucciac3fd3e2017-09-29 14:29:15 +010084def AdbCall(*args):
85 cmd = [ADB_PATH] + list(args)
86 print '> adb ' + ' '.join(args)
87 return subprocess.check_call(cmd)
88
89
Hector Dearman273823c2017-10-13 18:12:27 +010090def GetProp(prop):
91 cmd = [ADB_PATH, 'shell', 'getprop', prop]
92 print '> adb ' + ' '.join(cmd)
93 output = subprocess.check_output(cmd)
94 lines = output.splitlines()
Hector Dearman0d305872017-10-18 16:18:07 +010095 assert len(lines) == 1, 'Expected output to have one line: {}'.format(output)
Hector Dearman273823c2017-10-13 18:12:27 +010096 print lines[0]
97 return lines[0]
98
99
Hector Dearman0d305872017-10-18 16:18:07 +0100100@RetryOn([subprocess.CalledProcessError], returns_falsy=True, retries=10)
101def WaitForBootCompletion():
Hector Dearman273823c2017-10-13 18:12:27 +0100102 return GetProp('sys.boot_completed') == '1'
103
104
Primiano Tuccic6259a92017-11-10 13:51:55 +0000105def EnumerateDataDeps():
Primiano Tucci7a40e4d2017-12-06 09:51:09 +0000106 with open(os.path.join(ROOT_DIR, 'tools', 'test_data.txt')) as f:
Primiano Tuccic6259a92017-11-10 13:51:55 +0000107 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
Primiano Tucciac3fd3e2017-09-29 14:29:15 +0100113def Main():
114 parser = argparse.ArgumentParser()
115 parser.add_argument('--no-cleanup', '-n', action='store_true')
Primiano Tucci480c68b2017-12-19 09:18:00 +0100116 parser.add_argument('--no-data-deps', '-x', action='store_true')
Lalit Maganti131b6e52018-03-29 18:29:31 +0100117 parser.add_argument('--env', '-e', action='append')
Primiano Tucciac3fd3e2017-09-29 14:29:15 +0100118 parser.add_argument('out_dir', help='out/android/')
Lalit Maganti79f2d7b2018-01-23 18:27:33 +0000119 parser.add_argument('test_name', help='perfetto_unittests')
Sami Kyostila4473d9f2017-11-24 17:45:24 +0000120 parser.add_argument('cmd_args', nargs=argparse.REMAINDER)
Primiano Tucciac3fd3e2017-09-29 14:29:15 +0100121 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')
Lalit Maganti367fcd52018-02-05 16:06:13 +0000128 # WaitForBootCompletion()
Lalit Maganti79f2d7b2018-01-23 18:27:33 +0000129 AdbCall('root')
130 AdbCall('wait-for-device')
Hector Dearman273823c2017-10-13 18:12:27 +0100131
Primiano Tucciac3fd3e2017-09-29 14:29:15 +0100132 target_dir = '/data/local/tmp/' + args.test_name
133 AdbCall('shell', 'rm -rf "%s"; mkdir -p "%s"' % (2 * (target_dir,)))
Stephen Nuskoe46a57b2019-04-15 15:12:05 +0100134 # 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)
Florian Mayerfd60cc82019-02-06 12:11:09 +0000140 AdbCall('shell', 'mkdir -p /data/nativetest')
Florian Mayer2c9d7412019-02-01 16:09:18 +0000141 # 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 #
Florian Mayer98b17532019-04-05 17:32:13 +0100145 # See https://android.googlesource.com/platform/system/core/+/master/rootdir/etc/ld.config.txt.
Florian Mayer2c9d7412019-02-01 16:09:18 +0000146 AdbCall('push', test_bin, "/data/nativetest")
Primiano Tucciac3fd3e2017-09-29 14:29:15 +0100147
Primiano Tucci480c68b2017-12-19 09:18:00 +0100148 if not args.no_data_deps:
149 for dep in EnumerateDataDeps():
150 AdbCall('push', os.path.join(ROOT_DIR, dep), target_dir + '/' + dep)
Primiano Tuccic6259a92017-11-10 13:51:55 +0000151
Primiano Tucciac3fd3e2017-09-29 14:29:15 +0100152 # LLVM sanitizers require to sideload a libclangrtXX.so on the device.
153 sanitizer_libs = os.path.join(args.out_dir, 'sanitizer_libs')
Lalit Maganti131b6e52018-03-29 18:29:31 +0100154 env = ' '.join(args.env if args.env is not None else []) + ' '
Primiano Tucciac3fd3e2017-09-29 14:29:15 +0100155 if os.path.exists(sanitizer_libs):
156 AdbCall('push', sanitizer_libs, target_dir)
Lalit Maganti131b6e52018-03-29 18:29:31 +0100157 env += 'LD_LIBRARY_PATH="%s/sanitizer_libs" ' % (target_dir)
Hector Dearman72f697c2017-11-13 11:06:34 +0000158 cmd = 'cd %s;' % target_dir;
Florian Mayer2c9d7412019-02-01 16:09:18 +0000159 binary = env + '/data/nativetest/%s' % args.test_name
Lalit Magantibfc3d3e2018-03-22 20:28:38 +0000160 cmd += binary
Sami Kyostila4473d9f2017-11-24 17:45:24 +0000161 if args.cmd_args:
Lalit Magantibfc3d3e2018-03-22 20:28:38 +0000162 actual_args = [arg.replace(args.test_name, binary) for arg in args.cmd_args]
163 cmd += ' ' + ' '.join(actual_args)
Sami Kyostila4473d9f2017-11-24 17:45:24 +0000164 cmd += ';echo -e "\\nTEST_RET_CODE=$?"'
Primiano Tucciac3fd3e2017-09-29 14:29:15 +0100165 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())