• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  if "perfetto_integrationtest" in args.test_name:
171    AdbPush(os.path.join(args.out_dir, "perfetto"), "/data/nativetest")
172    AdbPush(os.path.join(args.out_dir, "trigger_perfetto"), "/data/nativetest")
173
174  if not args.no_data_deps:
175    for dep in EnumerateDataDeps():
176      AdbPush(os.path.join(ROOT_DIR, dep), target_dir + '/' + dep)
177
178  # LLVM sanitizers require to sideload a libclangrtXX.so on the device.
179  sanitizer_libs = os.path.join(args.out_dir, 'sanitizer_libs')
180  env = ' '.join(args.env if args.env is not None else []) + ' '
181  if os.path.exists(sanitizer_libs):
182    AdbPush(sanitizer_libs, target_dir)
183    env += 'LD_LIBRARY_PATH="%s/sanitizer_libs" ' % (target_dir)
184  cmd = 'cd %s;' % target_dir
185  binary = env + '/data/nativetest/%s' % args.test_name
186  cmd += binary
187  if args.cmd_args:
188    actual_args = [arg.replace(args.test_name, binary) for arg in args.cmd_args]
189    cmd += ' ' + ' '.join(actual_args)
190  print(cmd)
191  retcode = subprocess.call([ADB_PATH, 'shell', '-tt', cmd])
192  if not args.no_cleanup:
193    AdbCall('shell', 'rm -rf "%s"' % target_dir)
194
195  # Smoke test that adb shell is actually propagating retcode. adb has a history
196  # of breaking this.
197  test_code = subprocess.call([ADB_PATH, 'shell', '-tt', 'echo Done; exit 42'])
198  if test_code != 42:
199    logging.fatal('adb is incorrectly propagating the exit code')
200    return 1
201
202  return retcode
203
204
205if __name__ == '__main__':
206  sys.exit(Main())
207