• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/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
19import functools
20import logging
21import subprocess
22import sys
23import tempfile
24import time
25""" Runs a test executable on Android.
26
27Takes care of pushing the extra shared libraries that might be required by
28some sanitizers. Propagates the test return code to the host, exiting with
290 only if the test execution succeeds on the device.
30"""
31
32ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
33ADB_PATH = os.path.join(ROOT_DIR, 'buildtools/android_sdk/platform-tools/adb')
34
35
36def RetryOn(exc_type=(), returns_falsy=False, retries=5):
37  """Decorator to retry a function in case of errors or falsy values.
38
39  Implements exponential backoff between retries.
40
41  Args:
42    exc_type: Type of exceptions to catch and retry on. May also pass a tuple
43      of exceptions to catch and retry on any of them. Defaults to catching no
44      exceptions at all.
45    returns_falsy: If True then the function will be retried until it stops
46      returning a "falsy" value (e.g. None, False, 0, [], etc.). If equal to
47      'raise' and the function keeps returning falsy values after all retries,
48      then the decorator will raise a ValueError.
49    retries: Max number of retry attempts. After exhausting that number of
50      attempts the function will be called with no safeguards: any exceptions
51      will be raised and falsy values returned to the caller (except when
52      returns_falsy='raise').
53  """
54
55  def Decorator(f):
56
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
81    return Wrapper
82
83  return Decorator
84
85
86def AdbCall(*args):
87  cmd = [ADB_PATH] + list(args)
88  print '> adb ' + ' '.join(args)
89  return subprocess.check_call(cmd)
90
91
92def AdbPush(host, device):
93  if not os.path.exists(host):
94    logging.fatal('Cannot find %s. Was it built?', host)
95  cmd = [ADB_PATH, 'push', host, device]
96  print '> adb push ' + ' '.join(cmd[2:])
97  with open(os.devnull) as devnull:
98    return subprocess.check_call(cmd, stdout=devnull)
99
100
101def GetProp(prop):
102  cmd = [ADB_PATH, 'shell', 'getprop', prop]
103  print '> adb ' + ' '.join(cmd)
104  output = subprocess.check_output(cmd)
105  lines = output.splitlines()
106  assert len(lines) == 1, 'Expected output to have one line: {}'.format(output)
107  print lines[0]
108  return lines[0]
109
110
111@RetryOn([subprocess.CalledProcessError], returns_falsy=True, retries=10)
112def WaitForBootCompletion():
113  return GetProp('sys.boot_completed') == '1'
114
115
116def EnumerateDataDeps():
117  with open(os.path.join(ROOT_DIR, 'tools', 'test_data.txt')) as f:
118    lines = f.readlines()
119  for line in (line.strip() for line in lines if not line.startswith('#')):
120    assert os.path.exists(line), line
121    yield line
122
123
124def Main():
125  parser = argparse.ArgumentParser()
126  parser.add_argument('--no-cleanup', '-n', action='store_true')
127  parser.add_argument('--no-data-deps', '-x', action='store_true')
128  parser.add_argument('--env', '-e', action='append')
129  parser.add_argument('out_dir', help='out/android/')
130  parser.add_argument('test_name', help='perfetto_unittests')
131  parser.add_argument('cmd_args', nargs=argparse.REMAINDER)
132  args = parser.parse_args()
133
134  test_bin = os.path.join(args.out_dir, args.test_name)
135  assert os.path.exists(test_bin)
136
137  print 'Waiting for device ...'
138  AdbCall('wait-for-device')
139  # WaitForBootCompletion()
140  AdbCall('root')
141  AdbCall('wait-for-device')
142
143  target_dir = '/data/local/tmp/' + args.test_name
144  AdbCall('shell', 'rm -rf "%s"; mkdir -p "%s"' % (2 * (target_dir,)))
145  # Some tests require the trace directory to exist, while true for android
146  # devices in general some emulators might not have it set up. So we check to
147  # see if it exists, and if not create it.
148  trace_dir = '/data/misc/perfetto-traces'
149  AdbCall('shell', 'test -d "%s" || mkdir -p "%s"' % (2 * (trace_dir,)))
150  AdbCall('shell', 'rm -rf "%s/*";  ' % trace_dir)
151  AdbCall('shell', 'mkdir -p /data/nativetest')
152  # This needs to go into /data/nativetest in order to have the system linker
153  # namespace applied, which we need in order to link libdexfile_external.so.
154  # This gets linked into our tests via libundwindstack.so.
155  #
156  # See https://android.googlesource.com/platform/system/core/+/master/rootdir/etc/ld.config.txt.
157  AdbPush(test_bin, "/data/nativetest")
158
159  # These two binaries are required to run perfetto_integrationtests.
160  AdbPush(os.path.join(args.out_dir, "perfetto"), "/data/nativetest")
161  AdbPush(os.path.join(args.out_dir, "trigger_perfetto"), "/data/nativetest")
162
163  if not args.no_data_deps:
164    for dep in EnumerateDataDeps():
165      AdbPush(os.path.join(ROOT_DIR, dep), target_dir + '/' + dep)
166
167  # LLVM sanitizers require to sideload a libclangrtXX.so on the device.
168  sanitizer_libs = os.path.join(args.out_dir, 'sanitizer_libs')
169  env = ' '.join(args.env if args.env is not None else []) + ' '
170  if os.path.exists(sanitizer_libs):
171    AdbPush(sanitizer_libs, target_dir)
172    env += 'LD_LIBRARY_PATH="%s/sanitizer_libs" ' % (target_dir)
173  cmd = 'cd %s;' % target_dir
174  binary = env + '/data/nativetest/%s' % args.test_name
175  cmd += binary
176  if args.cmd_args:
177    actual_args = [arg.replace(args.test_name, binary) for arg in args.cmd_args]
178    cmd += ' ' + ' '.join(actual_args)
179  print cmd
180  retcode = subprocess.call([ADB_PATH, 'shell', cmd])
181  if not args.no_cleanup:
182    AdbCall('shell', 'rm -rf "%s"' % target_dir)
183
184  # Smoke test that adb shell is actually propagating retcode. adb has a history
185  # of breaking this.
186  test_code = subprocess.call([ADB_PATH, 'shell', 'echo Done; exit 42'])
187  if test_code != 42:
188    logging.fatal('adb is incorrectly propagating the exit code')
189    return 1
190
191  return retcode
192
193
194if __name__ == '__main__':
195  sys.exit(Main())
196