• 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', 'echo 0 > /d/tracing/tracing_on')
158
159  # This needs to go into /data/nativetest in order to have the system linker
160  # namespace applied, which we need in order to link libdexfile.so.
161  # This gets linked into our tests via libundwindstack.so.
162  #
163  # See https://android.googlesource.com/platform/system/core/+/master/rootdir/etc/ld.config.txt.
164  AdbPush(test_bin, "/data/nativetest")
165
166  # These two binaries are required to run perfetto_integrationtests.
167  AdbPush(os.path.join(args.out_dir, "perfetto"), "/data/nativetest")
168  AdbPush(os.path.join(args.out_dir, "trigger_perfetto"), "/data/nativetest")
169
170  if not args.no_data_deps:
171    for dep in EnumerateDataDeps():
172      AdbPush(os.path.join(ROOT_DIR, dep), target_dir + '/' + dep)
173
174  # LLVM sanitizers require to sideload a libclangrtXX.so on the device.
175  sanitizer_libs = os.path.join(args.out_dir, 'sanitizer_libs')
176  env = ' '.join(args.env if args.env is not None else []) + ' '
177  if os.path.exists(sanitizer_libs):
178    AdbPush(sanitizer_libs, target_dir)
179    env += 'LD_LIBRARY_PATH="%s/sanitizer_libs" ' % (target_dir)
180  cmd = 'cd %s;' % target_dir
181  binary = env + '/data/nativetest/%s' % args.test_name
182  cmd += binary
183  if args.cmd_args:
184    actual_args = [arg.replace(args.test_name, binary) for arg in args.cmd_args]
185    cmd += ' ' + ' '.join(actual_args)
186  print(cmd)
187  retcode = subprocess.call([ADB_PATH, 'shell', '-tt', cmd])
188  if not args.no_cleanup:
189    AdbCall('shell', 'rm -rf "%s"' % target_dir)
190
191  # Smoke test that adb shell is actually propagating retcode. adb has a history
192  # of breaking this.
193  test_code = subprocess.call([ADB_PATH, 'shell', '-tt', 'echo Done; exit 42'])
194  if test_code != 42:
195    logging.fatal('adb is incorrectly propagating the exit code')
196    return 1
197
198  return retcode
199
200
201if __name__ == '__main__':
202  sys.exit(Main())
203