xref: /aosp_15_r20/external/perfetto/tools/run_android_test (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*6dbdd20aSAndroid Build Coastguard Worker# Copyright (C) 2017 The Android Open Source Project
3*6dbdd20aSAndroid Build Coastguard Worker#
4*6dbdd20aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
5*6dbdd20aSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
6*6dbdd20aSAndroid Build Coastguard Worker# You may obtain a copy of the License at
7*6dbdd20aSAndroid Build Coastguard Worker#
8*6dbdd20aSAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
9*6dbdd20aSAndroid Build Coastguard Worker#
10*6dbdd20aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
11*6dbdd20aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
12*6dbdd20aSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*6dbdd20aSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
14*6dbdd20aSAndroid Build Coastguard Worker# limitations under the License.
15*6dbdd20aSAndroid Build Coastguard Worker
16*6dbdd20aSAndroid Build Coastguard Workerimport argparse
17*6dbdd20aSAndroid Build Coastguard Workerimport os
18*6dbdd20aSAndroid Build Coastguard Workerimport functools
19*6dbdd20aSAndroid Build Coastguard Workerimport logging
20*6dbdd20aSAndroid Build Coastguard Workerimport subprocess
21*6dbdd20aSAndroid Build Coastguard Workerimport sys
22*6dbdd20aSAndroid Build Coastguard Workerimport time
23*6dbdd20aSAndroid Build Coastguard Worker""" Runs a test executable on Android.
24*6dbdd20aSAndroid Build Coastguard Worker
25*6dbdd20aSAndroid Build Coastguard WorkerTakes care of pushing the extra shared libraries that might be required by
26*6dbdd20aSAndroid Build Coastguard Workersome sanitizers. Propagates the test return code to the host, exiting with
27*6dbdd20aSAndroid Build Coastguard Worker0 only if the test execution succeeds on the device.
28*6dbdd20aSAndroid Build Coastguard Worker"""
29*6dbdd20aSAndroid Build Coastguard Worker
30*6dbdd20aSAndroid Build Coastguard WorkerROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
31*6dbdd20aSAndroid Build Coastguard WorkerADB_PATH = os.path.join(ROOT_DIR, 'buildtools/android_sdk/platform-tools/adb')
32*6dbdd20aSAndroid Build Coastguard Worker
33*6dbdd20aSAndroid Build Coastguard Worker
34*6dbdd20aSAndroid Build Coastguard Workerdef RetryOn(exc_type=(), returns_falsy=False, retries=5):
35*6dbdd20aSAndroid Build Coastguard Worker  """Decorator to retry a function in case of errors or falsy values.
36*6dbdd20aSAndroid Build Coastguard Worker
37*6dbdd20aSAndroid Build Coastguard Worker  Implements exponential backoff between retries.
38*6dbdd20aSAndroid Build Coastguard Worker
39*6dbdd20aSAndroid Build Coastguard Worker  Args:
40*6dbdd20aSAndroid Build Coastguard Worker    exc_type: Type of exceptions to catch and retry on. May also pass a tuple
41*6dbdd20aSAndroid Build Coastguard Worker      of exceptions to catch and retry on any of them. Defaults to catching no
42*6dbdd20aSAndroid Build Coastguard Worker      exceptions at all.
43*6dbdd20aSAndroid Build Coastguard Worker    returns_falsy: If True then the function will be retried until it stops
44*6dbdd20aSAndroid Build Coastguard Worker      returning a "falsy" value (e.g. None, False, 0, [], etc.). If equal to
45*6dbdd20aSAndroid Build Coastguard Worker      'raise' and the function keeps returning falsy values after all retries,
46*6dbdd20aSAndroid Build Coastguard Worker      then the decorator will raise a ValueError.
47*6dbdd20aSAndroid Build Coastguard Worker    retries: Max number of retry attempts. After exhausting that number of
48*6dbdd20aSAndroid Build Coastguard Worker      attempts the function will be called with no safeguards: any exceptions
49*6dbdd20aSAndroid Build Coastguard Worker      will be raised and falsy values returned to the caller (except when
50*6dbdd20aSAndroid Build Coastguard Worker      returns_falsy='raise').
51*6dbdd20aSAndroid Build Coastguard Worker  """
52*6dbdd20aSAndroid Build Coastguard Worker
53*6dbdd20aSAndroid Build Coastguard Worker  def Decorator(f):
54*6dbdd20aSAndroid Build Coastguard Worker
55*6dbdd20aSAndroid Build Coastguard Worker    @functools.wraps(f)
56*6dbdd20aSAndroid Build Coastguard Worker    def Wrapper(*args, **kwargs):
57*6dbdd20aSAndroid Build Coastguard Worker      wait = 1
58*6dbdd20aSAndroid Build Coastguard Worker      this_retries = kwargs.pop('retries', retries)
59*6dbdd20aSAndroid Build Coastguard Worker      for _ in range(this_retries):
60*6dbdd20aSAndroid Build Coastguard Worker        retry_reason = None
61*6dbdd20aSAndroid Build Coastguard Worker        try:
62*6dbdd20aSAndroid Build Coastguard Worker          value = f(*args, **kwargs)
63*6dbdd20aSAndroid Build Coastguard Worker        except exc_type as exc:
64*6dbdd20aSAndroid Build Coastguard Worker          retry_reason = 'raised %s' % type(exc).__name__
65*6dbdd20aSAndroid Build Coastguard Worker        if retry_reason is None:
66*6dbdd20aSAndroid Build Coastguard Worker          if returns_falsy and not value:
67*6dbdd20aSAndroid Build Coastguard Worker            retry_reason = 'returned %r' % value
68*6dbdd20aSAndroid Build Coastguard Worker          else:
69*6dbdd20aSAndroid Build Coastguard Worker            return value  # Success!
70*6dbdd20aSAndroid Build Coastguard Worker        print('{} {}, will retry in {} second{} ...'.format(
71*6dbdd20aSAndroid Build Coastguard Worker            f.__name__, retry_reason, wait, '' if wait == 1 else 's'))
72*6dbdd20aSAndroid Build Coastguard Worker        time.sleep(wait)
73*6dbdd20aSAndroid Build Coastguard Worker        wait *= 2
74*6dbdd20aSAndroid Build Coastguard Worker      value = f(*args, **kwargs)  # Last try to run with no safeguards.
75*6dbdd20aSAndroid Build Coastguard Worker      if returns_falsy == 'raise' and not value:
76*6dbdd20aSAndroid Build Coastguard Worker        raise ValueError('%s returned %r' % (f.__name__, value))
77*6dbdd20aSAndroid Build Coastguard Worker      return value
78*6dbdd20aSAndroid Build Coastguard Worker
79*6dbdd20aSAndroid Build Coastguard Worker    return Wrapper
80*6dbdd20aSAndroid Build Coastguard Worker
81*6dbdd20aSAndroid Build Coastguard Worker  return Decorator
82*6dbdd20aSAndroid Build Coastguard Worker
83*6dbdd20aSAndroid Build Coastguard Worker
84*6dbdd20aSAndroid Build Coastguard Workerdef AdbCall(*args):
85*6dbdd20aSAndroid Build Coastguard Worker  cmd = [ADB_PATH] + list(args)
86*6dbdd20aSAndroid Build Coastguard Worker  print('> adb ' + ' '.join(args))
87*6dbdd20aSAndroid Build Coastguard Worker  return subprocess.check_call(cmd)
88*6dbdd20aSAndroid Build Coastguard Worker
89*6dbdd20aSAndroid Build Coastguard Worker
90*6dbdd20aSAndroid Build Coastguard Workerdef AdbPush(host, device):
91*6dbdd20aSAndroid Build Coastguard Worker  if not os.path.exists(host):
92*6dbdd20aSAndroid Build Coastguard Worker    logging.fatal('Cannot find %s. Was it built?', host)
93*6dbdd20aSAndroid Build Coastguard Worker  cmd = [ADB_PATH, 'push', host, device]
94*6dbdd20aSAndroid Build Coastguard Worker  print('> adb push ' + ' '.join(cmd[2:]))
95*6dbdd20aSAndroid Build Coastguard Worker  with open(os.devnull, 'wb') as devnull:
96*6dbdd20aSAndroid Build Coastguard Worker    return subprocess.check_call(cmd, stdout=devnull)
97*6dbdd20aSAndroid Build Coastguard Worker
98*6dbdd20aSAndroid Build Coastguard Worker
99*6dbdd20aSAndroid Build Coastguard Workerdef GetProp(prop):
100*6dbdd20aSAndroid Build Coastguard Worker  cmd = [ADB_PATH, 'shell', 'getprop', prop]
101*6dbdd20aSAndroid Build Coastguard Worker  print('> adb ' + ' '.join(cmd))
102*6dbdd20aSAndroid Build Coastguard Worker  output = subprocess.check_output(cmd).decode()
103*6dbdd20aSAndroid Build Coastguard Worker  lines = output.splitlines()
104*6dbdd20aSAndroid Build Coastguard Worker  assert len(lines) == 1, 'Expected output to have one line: {}'.format(output)
105*6dbdd20aSAndroid Build Coastguard Worker  print(lines[0])
106*6dbdd20aSAndroid Build Coastguard Worker  return lines[0]
107*6dbdd20aSAndroid Build Coastguard Worker
108*6dbdd20aSAndroid Build Coastguard Worker
109*6dbdd20aSAndroid Build Coastguard Worker@RetryOn([subprocess.CalledProcessError], returns_falsy=True, retries=10)
110*6dbdd20aSAndroid Build Coastguard Workerdef WaitForBootCompletion():
111*6dbdd20aSAndroid Build Coastguard Worker  return GetProp('sys.boot_completed') == '1'
112*6dbdd20aSAndroid Build Coastguard Worker
113*6dbdd20aSAndroid Build Coastguard Worker
114*6dbdd20aSAndroid Build Coastguard Workerdef EnumerateDataDeps():
115*6dbdd20aSAndroid Build Coastguard Worker  with open(os.path.join(ROOT_DIR, 'tools', 'test_data.txt')) as f:
116*6dbdd20aSAndroid Build Coastguard Worker    lines = f.readlines()
117*6dbdd20aSAndroid Build Coastguard Worker  for line in (line.strip() for line in lines if not line.startswith('#')):
118*6dbdd20aSAndroid Build Coastguard Worker    assert os.path.exists(line), line
119*6dbdd20aSAndroid Build Coastguard Worker    yield line
120*6dbdd20aSAndroid Build Coastguard Worker
121*6dbdd20aSAndroid Build Coastguard Worker
122*6dbdd20aSAndroid Build Coastguard Workerdef Main():
123*6dbdd20aSAndroid Build Coastguard Worker  parser = argparse.ArgumentParser()
124*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--no-cleanup', '-n', action='store_true')
125*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--no-data-deps', '-x', action='store_true')
126*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--system-adb', action='store_true')
127*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--env', '-e', action='append')
128*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('out_dir', help='out/android/')
129*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('test_name', help='perfetto_unittests')
130*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('cmd_args', nargs=argparse.REMAINDER)
131*6dbdd20aSAndroid Build Coastguard Worker  args = parser.parse_args()
132*6dbdd20aSAndroid Build Coastguard Worker
133*6dbdd20aSAndroid Build Coastguard Worker  if args.system_adb:
134*6dbdd20aSAndroid Build Coastguard Worker    global ADB_PATH
135*6dbdd20aSAndroid Build Coastguard Worker    ADB_PATH = 'adb'
136*6dbdd20aSAndroid Build Coastguard Worker
137*6dbdd20aSAndroid Build Coastguard Worker  test_bin = os.path.join(args.out_dir, args.test_name)
138*6dbdd20aSAndroid Build Coastguard Worker  assert os.path.exists(test_bin)
139*6dbdd20aSAndroid Build Coastguard Worker
140*6dbdd20aSAndroid Build Coastguard Worker  print('Waiting for device ...')
141*6dbdd20aSAndroid Build Coastguard Worker  AdbCall('wait-for-device')
142*6dbdd20aSAndroid Build Coastguard Worker  # WaitForBootCompletion()
143*6dbdd20aSAndroid Build Coastguard Worker  AdbCall('root')
144*6dbdd20aSAndroid Build Coastguard Worker  AdbCall('wait-for-device')
145*6dbdd20aSAndroid Build Coastguard Worker
146*6dbdd20aSAndroid Build Coastguard Worker  target_dir = '/data/local/tmp/perfetto_tests'
147*6dbdd20aSAndroid Build Coastguard Worker  if not args.no_cleanup:
148*6dbdd20aSAndroid Build Coastguard Worker    AdbCall('shell', 'rm -rf "%s"' % target_dir)
149*6dbdd20aSAndroid Build Coastguard Worker  AdbCall('shell', 'mkdir -p "%s"' % target_dir)
150*6dbdd20aSAndroid Build Coastguard Worker  # Some tests require the trace directory to exist, while true for android
151*6dbdd20aSAndroid Build Coastguard Worker  # devices in general some emulators might not have it set up. So we check to
152*6dbdd20aSAndroid Build Coastguard Worker  # see if it exists, and if not create it.
153*6dbdd20aSAndroid Build Coastguard Worker  trace_dir = '/data/misc/perfetto-traces/bugreport'
154*6dbdd20aSAndroid Build Coastguard Worker  AdbCall('shell', 'test -d "%s" || mkdir -p "%s"' % (2 * (trace_dir,)))
155*6dbdd20aSAndroid Build Coastguard Worker  AdbCall('shell', 'rm -rf "%s/*";  ' % trace_dir)
156*6dbdd20aSAndroid Build Coastguard Worker  AdbCall('shell', 'mkdir -p /data/nativetest')
157*6dbdd20aSAndroid Build Coastguard Worker  AdbCall('shell', 'test -f /sys/kernel/tracing/tracing_on ' +
158*6dbdd20aSAndroid Build Coastguard Worker                   '&& echo 0 > /sys/kernel/tracing/tracing_on || true')
159*6dbdd20aSAndroid Build Coastguard Worker  AdbCall('shell', 'test -f /sys/kernel/debug/tracing/tracing_on ' +
160*6dbdd20aSAndroid Build Coastguard Worker                   '&& echo 0 > /sys/kernel/debug/tracing/tracing_on || true')
161*6dbdd20aSAndroid Build Coastguard Worker
162*6dbdd20aSAndroid Build Coastguard Worker  # This needs to go into /data/nativetest in order to have the system linker
163*6dbdd20aSAndroid Build Coastguard Worker  # namespace applied, which we need in order to link libdexfile.so.
164*6dbdd20aSAndroid Build Coastguard Worker  # This gets linked into our tests via libundwindstack.so.
165*6dbdd20aSAndroid Build Coastguard Worker  #
166*6dbdd20aSAndroid Build Coastguard Worker  # See https://android.googlesource.com/platform/system/core/+/main/rootdir/etc/ld.config.txt.
167*6dbdd20aSAndroid Build Coastguard Worker  AdbPush(test_bin, "/data/nativetest")
168*6dbdd20aSAndroid Build Coastguard Worker
169*6dbdd20aSAndroid Build Coastguard Worker  # These two binaries are required to run perfetto_integrationtests.
170*6dbdd20aSAndroid Build Coastguard Worker  AdbPush(os.path.join(args.out_dir, "perfetto"), "/data/nativetest")
171*6dbdd20aSAndroid Build Coastguard Worker  AdbPush(os.path.join(args.out_dir, "trigger_perfetto"), "/data/nativetest")
172*6dbdd20aSAndroid Build Coastguard Worker
173*6dbdd20aSAndroid Build Coastguard Worker  if not args.no_data_deps:
174*6dbdd20aSAndroid Build Coastguard Worker    for dep in EnumerateDataDeps():
175*6dbdd20aSAndroid Build Coastguard Worker      AdbPush(os.path.join(ROOT_DIR, dep), target_dir + '/' + dep)
176*6dbdd20aSAndroid Build Coastguard Worker
177*6dbdd20aSAndroid Build Coastguard Worker  # LLVM sanitizers require to sideload a libclangrtXX.so on the device.
178*6dbdd20aSAndroid Build Coastguard Worker  sanitizer_libs = os.path.join(args.out_dir, 'sanitizer_libs')
179*6dbdd20aSAndroid Build Coastguard Worker  env = ' '.join(args.env if args.env is not None else []) + ' '
180*6dbdd20aSAndroid Build Coastguard Worker  if os.path.exists(sanitizer_libs):
181*6dbdd20aSAndroid Build Coastguard Worker    AdbPush(sanitizer_libs, target_dir)
182*6dbdd20aSAndroid Build Coastguard Worker    env += 'LD_LIBRARY_PATH="%s/sanitizer_libs" ' % (target_dir)
183*6dbdd20aSAndroid Build Coastguard Worker  cmd = 'cd %s;' % target_dir
184*6dbdd20aSAndroid Build Coastguard Worker  binary = env + '/data/nativetest/%s' % args.test_name
185*6dbdd20aSAndroid Build Coastguard Worker  cmd += binary
186*6dbdd20aSAndroid Build Coastguard Worker  if args.cmd_args:
187*6dbdd20aSAndroid Build Coastguard Worker    actual_args = [arg.replace(args.test_name, binary) for arg in args.cmd_args]
188*6dbdd20aSAndroid Build Coastguard Worker    cmd += ' ' + ' '.join(actual_args)
189*6dbdd20aSAndroid Build Coastguard Worker  print(cmd)
190*6dbdd20aSAndroid Build Coastguard Worker  retcode = subprocess.call([ADB_PATH, 'shell', '-tt', cmd])
191*6dbdd20aSAndroid Build Coastguard Worker  if not args.no_cleanup:
192*6dbdd20aSAndroid Build Coastguard Worker    AdbCall('shell', 'rm -rf "%s"' % target_dir)
193*6dbdd20aSAndroid Build Coastguard Worker
194*6dbdd20aSAndroid Build Coastguard Worker  # Smoke test that adb shell is actually propagating retcode. adb has a history
195*6dbdd20aSAndroid Build Coastguard Worker  # of breaking this.
196*6dbdd20aSAndroid Build Coastguard Worker  test_code = subprocess.call([ADB_PATH, 'shell', '-tt', 'echo Done; exit 42'])
197*6dbdd20aSAndroid Build Coastguard Worker  if test_code != 42:
198*6dbdd20aSAndroid Build Coastguard Worker    logging.fatal('adb is incorrectly propagating the exit code')
199*6dbdd20aSAndroid Build Coastguard Worker    return 1
200*6dbdd20aSAndroid Build Coastguard Worker
201*6dbdd20aSAndroid Build Coastguard Worker  return retcode
202*6dbdd20aSAndroid Build Coastguard Worker
203*6dbdd20aSAndroid Build Coastguard Worker
204*6dbdd20aSAndroid Build Coastguard Workerif __name__ == '__main__':
205*6dbdd20aSAndroid Build Coastguard Worker  sys.exit(Main())
206