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