blob: 1282606c08ede2ac684678a2700ecdc1c1bb1e6c [file] [log] [blame]
Primiano Tucciac3fd3e2017-09-29 14:29:15 +01001#!/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
Hector Dearman0d305872017-10-18 16:18:07 +010019import functools
20import logging
Primiano Tucciac3fd3e2017-09-29 14:29:15 +010021import subprocess
22import sys
Hector Dearman273823c2017-10-13 18:12:27 +010023import time
Primiano Tucciac3fd3e2017-09-29 14:29:15 +010024
25
26""" Runs a test executable on Android.
27
28Takes care of pushing the extra shared libraries that might be required by
29some sanitizers. Propagates the test return code to the host, exiting with
300 only if the test execution succeeds on the device.
31"""
32
33ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
34ADB_PATH = os.path.join(ROOT_DIR, 'buildtools/android_sdk/platform-tools/adb')
35
36
Hector Dearman0d305872017-10-18 16:18:07 +010037def RetryOn(exc_type=(), returns_falsy=False, retries=5):
38 """Decorator to retry a function in case of errors or falsy values.
39
40 Implements exponential backoff between retries.
41
42 Args:
43 exc_type: Type of exceptions to catch and retry on. May also pass a tuple
44 of exceptions to catch and retry on any of them. Defaults to catching no
45 exceptions at all.
46 returns_falsy: If True then the function will be retried until it stops
47 returning a "falsy" value (e.g. None, False, 0, [], etc.). If equal to
48 'raise' and the function keeps returning falsy values after all retries,
49 then the decorator will raise a ValueError.
50 retries: Max number of retry attempts. After exhausting that number of
51 attempts the function will be called with no safeguards: any exceptions
52 will be raised and falsy values returned to the caller (except when
53 returns_falsy='raise').
54 """
55 def Decorator(f):
56 @functools.wraps(f)
57 def Wrapper(*args, **kwargs):
58 wait = 1
59 this_retries = kwargs.pop('retries', retries)
60 for _ in range(this_retries):
61 retry_reason = None
62 try:
63 value = f(*args, **kwargs)
64 except exc_type as exc:
65 retry_reason = 'raised %s' % type(exc).__name__
66 if retry_reason is None:
67 if returns_falsy and not value:
68 retry_reason = 'returned %r' % value
69 else:
70 return value # Success!
71 print('{} {}, will retry in {} second{} ...'.format(
72 f.__name__, retry_reason, wait, '' if wait == 1 else 's'))
73 time.sleep(wait)
74 wait *= 2
75 value = f(*args, **kwargs) # Last try to run with no safeguards.
76 if returns_falsy == 'raise' and not value:
77 raise ValueError('%s returned %r' % (f.__name__, value))
78 return value
79 return Wrapper
80 return Decorator
81
82
Primiano Tucciac3fd3e2017-09-29 14:29:15 +010083def AdbCall(*args):
84 cmd = [ADB_PATH] + list(args)
85 print '> adb ' + ' '.join(args)
86 return subprocess.check_call(cmd)
87
88
Hector Dearman273823c2017-10-13 18:12:27 +010089def GetProp(prop):
90 cmd = [ADB_PATH, 'shell', 'getprop', prop]
91 print '> adb ' + ' '.join(cmd)
92 output = subprocess.check_output(cmd)
93 lines = output.splitlines()
Hector Dearman0d305872017-10-18 16:18:07 +010094 assert len(lines) == 1, 'Expected output to have one line: {}'.format(output)
Hector Dearman273823c2017-10-13 18:12:27 +010095 print lines[0]
96 return lines[0]
97
98
Hector Dearman0d305872017-10-18 16:18:07 +010099@RetryOn([subprocess.CalledProcessError], returns_falsy=True, retries=10)
100def WaitForBootCompletion():
Hector Dearman273823c2017-10-13 18:12:27 +0100101 return GetProp('sys.boot_completed') == '1'
102
103
Primiano Tucciac3fd3e2017-09-29 14:29:15 +0100104def Main():
105 parser = argparse.ArgumentParser()
106 parser.add_argument('--no-cleanup', '-n', action='store_true')
107 parser.add_argument('out_dir', help='out/android/')
108 parser.add_argument('test_name', help='libtracing_unittests')
109 args = parser.parse_args()
110
111 test_bin = os.path.join(args.out_dir, args.test_name)
112 assert os.path.exists(test_bin)
113
114 print 'Waiting for device ...'
115 AdbCall('wait-for-device')
Hector Dearman0d305872017-10-18 16:18:07 +0100116 WaitForBootCompletion()
Hector Dearman273823c2017-10-13 18:12:27 +0100117
Primiano Tucciac3fd3e2017-09-29 14:29:15 +0100118 target_dir = '/data/local/tmp/' + args.test_name
119 AdbCall('shell', 'rm -rf "%s"; mkdir -p "%s"' % (2 * (target_dir,)))
120 AdbCall('push', test_bin, target_dir)
121
122 # LLVM sanitizers require to sideload a libclangrtXX.so on the device.
123 sanitizer_libs = os.path.join(args.out_dir, 'sanitizer_libs')
124 env = ''
125 if os.path.exists(sanitizer_libs):
126 AdbCall('push', sanitizer_libs, target_dir)
127 env = 'LD_LIBRARY_PATH="%s/sanitizer_libs" ' % (target_dir)
Primiano Tucci0ed1f692017-10-12 13:15:18 +0200128 cmd = env + target_dir + '/' + args.test_name
129 cmd += '; echo -e "\\nTEST_RET_CODE=$?"'
Primiano Tucciac3fd3e2017-09-29 14:29:15 +0100130 print cmd
131 test_output = subprocess.check_output([ADB_PATH, 'shell', cmd])
132 print test_output
133 retcode = re.search(r'^TEST_RET_CODE=(\d)', test_output, re.MULTILINE)
134 assert retcode, 'Could not find TEST_RET_CODE=N marker'
135 retcode = int(retcode.group(1))
136 if not args.no_cleanup:
137 AdbCall('shell', 'rm -rf "%s"' % target_dir)
138 return retcode
139
140
141if __name__ == '__main__':
142 sys.exit(Main())