xref: /aosp_15_r20/external/perfetto/tools/run_android_test (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1#!/usr/bin/env python3
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 functools
19import logging
20import subprocess
21import sys
22import time
23""" Runs a test executable on Android.
24
25Takes care of pushing the extra shared libraries that might be required by
26some sanitizers. Propagates the test return code to the host, exiting with
270 only if the test execution succeeds on the device.
28"""
29
30ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
31ADB_PATH = os.path.join(ROOT_DIR, 'buildtools/android_sdk/platform-tools/adb')
32
33
34def 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  """
52
53  def Decorator(f):
54
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
78
79    return Wrapper
80
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 AdbPush(host, device):
91  if not os.path.exists(host):
92    logging.fatal('Cannot find %s. Was it built?', host)
93  cmd = [ADB_PATH, 'push', host, device]
94  print('> adb push ' + ' '.join(cmd[2:]))
95  with open(os.devnull, 'wb') as devnull:
96    return subprocess.check_call(cmd, stdout=devnull)
97
98
99def GetProp(prop):
100  cmd = [ADB_PATH, 'shell', 'getprop', prop]
101  print('> adb ' + ' '.join(cmd))
102  output = subprocess.check_output(cmd).decode()
103  lines = output.splitlines()
104  assert len(lines) == 1, 'Expected output to have one line: {}'.format(output)
105  print(lines[0])
106  return lines[0]
107
108
109@RetryOn([subprocess.CalledProcessError], returns_falsy=True, retries=10)
110def WaitForBootCompletion():
111  return GetProp('sys.boot_completed') == '1'
112
113
114def EnumerateDataDeps():
115  with open(os.path.join(ROOT_DIR, 'tools', 'test_data.txt')) as f:
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
122def Main():
123  parser = argparse.ArgumentParser()
124  parser.add_argument('--no-cleanup', '-n', action='store_true')
125  parser.add_argument('--no-data-deps', '-x', action='store_true')
126  parser.add_argument('--system-adb', action='store_true')
127  parser.add_argument('--env', '-e', action='append')
128  parser.add_argument('out_dir', help='out/android/')
129  parser.add_argument('test_name', help='perfetto_unittests')
130  parser.add_argument('cmd_args', nargs=argparse.REMAINDER)
131  args = parser.parse_args()
132
133  if args.system_adb:
134    global ADB_PATH
135    ADB_PATH = 'adb'
136
137  test_bin = os.path.join(args.out_dir, args.test_name)
138  assert os.path.exists(test_bin)
139
140  print('Waiting for device ...')
141  AdbCall('wait-for-device')
142  # WaitForBootCompletion()
143  AdbCall('root')
144  AdbCall('wait-for-device')
145
146  target_dir = '/data/local/tmp/perfetto_tests'
147  if not args.no_cleanup:
148    AdbCall('shell', 'rm -rf "%s"' % target_dir)
149  AdbCall('shell', 'mkdir -p "%s"' % target_dir)
150  # Some tests require the trace directory to exist, while true for android
151  # devices in general some emulators might not have it set up. So we check to
152  # see if it exists, and if not create it.
153  trace_dir = '/data/misc/perfetto-traces/bugreport'
154  AdbCall('shell', 'test -d "%s" || mkdir -p "%s"' % (2 * (trace_dir,)))
155  AdbCall('shell', 'rm -rf "%s/*";  ' % trace_dir)
156  AdbCall('shell', 'mkdir -p /data/nativetest')
157  AdbCall('shell', 'test -f /sys/kernel/tracing/tracing_on ' +
158                   '&& echo 0 > /sys/kernel/tracing/tracing_on || true')
159  AdbCall('shell', 'test -f /sys/kernel/debug/tracing/tracing_on ' +
160                   '&& echo 0 > /sys/kernel/debug/tracing/tracing_on || true')
161
162  # This needs to go into /data/nativetest in order to have the system linker
163  # namespace applied, which we need in order to link libdexfile.so.
164  # This gets linked into our tests via libundwindstack.so.
165  #
166  # See https://android.googlesource.com/platform/system/core/+/main/rootdir/etc/ld.config.txt.
167  AdbPush(test_bin, "/data/nativetest")
168
169  # These two binaries are required to run perfetto_integrationtests.
170  AdbPush(os.path.join(args.out_dir, "perfetto"), "/data/nativetest")
171  AdbPush(os.path.join(args.out_dir, "trigger_perfetto"), "/data/nativetest")
172
173  if not args.no_data_deps:
174    for dep in EnumerateDataDeps():
175      AdbPush(os.path.join(ROOT_DIR, dep), target_dir + '/' + dep)
176
177  # LLVM sanitizers require to sideload a libclangrtXX.so on the device.
178  sanitizer_libs = os.path.join(args.out_dir, 'sanitizer_libs')
179  env = ' '.join(args.env if args.env is not None else []) + ' '
180  if os.path.exists(sanitizer_libs):
181    AdbPush(sanitizer_libs, target_dir)
182    env += 'LD_LIBRARY_PATH="%s/sanitizer_libs" ' % (target_dir)
183  cmd = 'cd %s;' % target_dir
184  binary = env + '/data/nativetest/%s' % args.test_name
185  cmd += binary
186  if args.cmd_args:
187    actual_args = [arg.replace(args.test_name, binary) for arg in args.cmd_args]
188    cmd += ' ' + ' '.join(actual_args)
189  print(cmd)
190  retcode = subprocess.call([ADB_PATH, 'shell', '-tt', cmd])
191  if not args.no_cleanup:
192    AdbCall('shell', 'rm -rf "%s"' % target_dir)
193
194  # Smoke test that adb shell is actually propagating retcode. adb has a history
195  # of breaking this.
196  test_code = subprocess.call([ADB_PATH, 'shell', '-tt', 'echo Done; exit 42'])
197  if test_code != 42:
198    logging.fatal('adb is incorrectly propagating the exit code')
199    return 1
200
201  return retcode
202
203
204if __name__ == '__main__':
205  sys.exit(Main())
206