1#!/usr/bin/env python 2 3""" 4Test that aidl generates functional code by running it on an Android device. 5""" 6 7import argparse 8import pipes 9import subprocess 10import shlex 11 12JAVA_OUTPUT_READER = 'aidl_test_sentinel_searcher' 13NATIVE_TEST_CLIENT = 'aidl_test_client' 14NATIVE_TEST_SERVICE = 'aidl_test_service' 15 16TEST_FILTER_ALL = 'all' 17TEST_FILTER_JAVA = 'java' 18TEST_FILTER_NATIVE = 'native' 19 20JAVA_CLIENT_TIMEOUT_SECONDS = 30 21JAVA_LOG_FILE = '/data/data/android.aidl.tests/files/test-client.log' 22JAVA_SUCCESS_SENTINEL = '>>> Java Client Success <<<' 23JAVA_FAILURE_SENTINEL = '>>> Java Client Failure <<<' 24 25class TestFail(Exception): 26 """Raised on test failures.""" 27 pass 28 29 30class ShellResult(object): 31 """Represents the result of running a shell command.""" 32 33 def __init__(self, exit_status, stdout, stderr): 34 """Construct an instance. 35 36 Args: 37 exit_status: integer exit code of shell command 38 stdout: string stdout of shell command 39 stderr: string stderr of shell command 40 """ 41 self.stdout = stdout 42 self.stderr = stderr 43 self.exit_status = exit_status 44 45 def printable_string(self): 46 """Get a string we could print to the logs and understand.""" 47 output = [] 48 output.append('stdout:') 49 for line in self.stdout.splitlines(): 50 output.append(' > %s' % line) 51 output.append('stderr:') 52 for line in self.stderr.splitlines(): 53 output.append(' > %s' % line) 54 return '\n'.join(output) 55 56 57class AdbHost(object): 58 """Represents a device connected via ADB.""" 59 60 def __init__(self, device_serial=None, verbose=None): 61 """Construct an instance. 62 63 Args: 64 device_serial: options string serial number of attached device. 65 verbose: True iff we should print out ADB commands we run. 66 """ 67 self._device_serial = device_serial 68 self._verbose = verbose 69 70 def run(self, command, background=False, ignore_status=False): 71 """Run a command on the device via adb shell. 72 73 Args: 74 command: string containing a shell command to run. 75 background: True iff we should run this command in the background. 76 ignore_status: True iff we should ignore the command's exit code. 77 78 Returns: 79 instance of ShellResult. 80 81 Raises: 82 subprocess.CalledProcessError on command exit != 0. 83 """ 84 if background: 85 command = '( %s ) </dev/null >/dev/null 2>&1 &' % command 86 return self.adb('shell %s' % pipes.quote(command), 87 ignore_status=ignore_status) 88 89 def mktemp(self): 90 """Make a temp file on the device. 91 92 Returns: 93 path to created file as a string 94 95 Raises: 96 subprocess.CalledProcessError on failure. 97 """ 98 # Work around b/19635681 99 result = self.run('source /system/etc/mkshrc && mktemp') 100 return result.stdout.strip() 101 102 def adb(self, command, ignore_status=False): 103 """Run an ADB command (e.g. `adb sync`). 104 105 Args: 106 command: string containing command to run 107 ignore_status: True iff we should ignore the command's exit code. 108 109 Returns: 110 instance of ShellResult. 111 112 Raises: 113 subprocess.CalledProcessError on command exit != 0. 114 """ 115 command = 'adb %s' % command 116 if self._verbose: 117 print(command) 118 p = subprocess.Popen(command, shell=True, close_fds=True, 119 stdout=subprocess.PIPE, stderr=subprocess.PIPE, 120 universal_newlines=True) 121 stdout, stderr = p.communicate() 122 if not ignore_status and p.returncode: 123 raise subprocess.CalledProcessError(p.returncode, command) 124 return ShellResult(p.returncode, stdout, stderr) 125 126 127def run_test(test_native, test_java, apk_path=None, refresh_binaries=False, 128 device_serial=None, verbose=False): 129 """Body of the test. 130 131 Args: 132 test_native: True iff we should test native Binder clients. 133 test_java: True iff we shoudl test Java Binder clients. 134 apk_path: Optional path to an APK to install via `adb install` 135 refresh_binaries: True iff we should `adb sync` new binaries to the 136 device. 137 device_serial: Optional string containing the serial number of the 138 device under test. 139 verbose: True iff we should enable verbose output during the test. 140 """ 141 142 print('Starting aidl integration testing...') 143 host = AdbHost(device_serial=device_serial, verbose=verbose) 144 if apk_path is not None: 145 host.adb('install -r %s' % apk_path) 146 if refresh_binaries: 147 host.adb('remount') 148 host.adb('sync') 149 host.run('setenforce 0') 150 # Kill any previous test context 151 host.run('rm -f %s' % JAVA_LOG_FILE, ignore_status=True) 152 host.run('pkill %s' % NATIVE_TEST_SERVICE, ignore_status=True) 153 154 # Start up a native server 155 host.run(NATIVE_TEST_SERVICE, background=True) 156 157 # Start up clients 158 if test_native: 159 host.run('pkill %s' % NATIVE_TEST_CLIENT, ignore_status=True) 160 result = host.run(NATIVE_TEST_CLIENT, ignore_status=True) 161 if result.exit_status: 162 print(result.printable_string()) 163 raise TestFail('%s returned status code %d' % 164 (NATIVE_TEST_CLIENT, result.exit_status)) 165 166 if test_java: 167 host.run('am start -S -a android.intent.action.MAIN ' 168 '-n android.aidl.tests/.TestServiceClient ' 169 '--es sentinel.success "%s" ' 170 '--es sentinel.failure "%s"' % 171 (JAVA_SUCCESS_SENTINEL, JAVA_FAILURE_SENTINEL)) 172 result = host.run('%s %d %s "%s" "%s"' % 173 (JAVA_OUTPUT_READER, JAVA_CLIENT_TIMEOUT_SECONDS, 174 JAVA_LOG_FILE, JAVA_SUCCESS_SENTINEL, 175 JAVA_FAILURE_SENTINEL), 176 ignore_status=True) 177 if result.exit_status: 178 print(result.printable_string()) 179 raise TestFail('Java client did not complete successfully.') 180 181 print('Success!') 182 183 184def main(): 185 """Main entry point.""" 186 parser = argparse.ArgumentParser(description=__doc__) 187 parser.add_argument('--apk', dest='apk_path', type=str, default=None, 188 help='Path to an APK to install on the device.') 189 parser.add_argument('--refresh-bins', action='store_true', default=False, 190 help='Pass this flag to have the test run adb sync') 191 parser.add_argument('--serial', '-s', type=str, default=None, 192 help='Serial number of device to test against') 193 parser.add_argument( 194 '--test-filter', default=TEST_FILTER_ALL, 195 choices=[TEST_FILTER_ALL, TEST_FILTER_JAVA, TEST_FILTER_NATIVE]) 196 parser.add_argument('--verbose', '-v', action='store_true', default=False) 197 args = parser.parse_args() 198 run_test(args.test_filter in (TEST_FILTER_ALL, TEST_FILTER_NATIVE), 199 args.test_filter in (TEST_FILTER_ALL, TEST_FILTER_JAVA), 200 apk_path=args.apk_path, refresh_binaries=args.refresh_bins, 201 device_serial=args.serial, verbose=args.verbose) 202 203 204if __name__ == '__main__': 205 main() 206