| #!/usr/bin/env python |
| |
| """ |
| Test that aidl generates functional code by running it on an Android device. |
| """ |
| |
| import argparse |
| import pipes |
| import subprocess |
| import shlex |
| |
| JAVA_OUTPUT_READER = 'aidl_test_sentinel_searcher' |
| NATIVE_TEST_CLIENT = 'aidl_test_client' |
| NATIVE_TEST_SERVICE = 'aidl_test_service' |
| |
| TEST_FILTER_ALL = 'all' |
| TEST_FILTER_JAVA = 'java' |
| TEST_FILTER_NATIVE = 'native' |
| |
| JAVA_CLIENT_TIMEOUT_SECONDS = 30 |
| JAVA_LOG_FILE = '/data/data/android.aidl.tests/files/test-client.log' |
| JAVA_SUCCESS_SENTINEL = '>>> Java Client Success <<<' |
| JAVA_FAILURE_SENTINEL = '>>> Java Client Failure <<<' |
| |
| class TestFail(Exception): |
| """Raised on test failures.""" |
| pass |
| |
| |
| class ShellResult(object): |
| """Represents the result of running a shell command.""" |
| |
| def __init__(self, exit_status, stdout, stderr): |
| """Construct an instance. |
| |
| Args: |
| exit_status: integer exit code of shell command |
| stdout: string stdout of shell command |
| stderr: string stderr of shell command |
| """ |
| self.stdout = stdout |
| self.stderr = stderr |
| self.exit_status = exit_status |
| |
| def printable_string(self): |
| """Get a string we could print to the logs and understand.""" |
| output = [] |
| output.append('stdout:') |
| for line in self.stdout.splitlines(): |
| output.append(' > %s' % line) |
| output.append('stderr:') |
| for line in self.stderr.splitlines(): |
| output.append(' > %s' % line) |
| return '\n'.join(output) |
| |
| |
| class AdbHost(object): |
| """Represents a device connected via ADB.""" |
| |
| def __init__(self, device_serial=None, verbose=None): |
| """Construct an instance. |
| |
| Args: |
| device_serial: options string serial number of attached device. |
| verbose: True iff we should print out ADB commands we run. |
| """ |
| self._device_serial = device_serial |
| self._verbose = verbose |
| |
| def run(self, command, background=False, ignore_status=False): |
| """Run a command on the device via adb shell. |
| |
| Args: |
| command: string containing a shell command to run. |
| background: True iff we should run this command in the background. |
| ignore_status: True iff we should ignore the command's exit code. |
| |
| Returns: |
| instance of ShellResult. |
| |
| Raises: |
| subprocess.CalledProcessError on command exit != 0. |
| """ |
| if background: |
| command = '( %s ) </dev/null >/dev/null 2>&1 &' % command |
| return self.adb('shell %s' % pipes.quote(command), |
| ignore_status=ignore_status) |
| |
| def mktemp(self): |
| """Make a temp file on the device. |
| |
| Returns: |
| path to created file as a string |
| |
| Raises: |
| subprocess.CalledProcessError on failure. |
| """ |
| # Work around b/19635681 |
| result = self.run('source /system/etc/mkshrc && mktemp') |
| return result.stdout.strip() |
| |
| def adb(self, command, ignore_status=False): |
| """Run an ADB command (e.g. `adb sync`). |
| |
| Args: |
| command: string containing command to run |
| ignore_status: True iff we should ignore the command's exit code. |
| |
| Returns: |
| instance of ShellResult. |
| |
| Raises: |
| subprocess.CalledProcessError on command exit != 0. |
| """ |
| command = 'adb %s' % command |
| if self._verbose: |
| print(command) |
| p = subprocess.Popen(command, shell=True, close_fds=True, |
| stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
| universal_newlines=True) |
| stdout, stderr = p.communicate() |
| if not ignore_status and p.returncode: |
| raise subprocess.CalledProcessError(p.returncode, command) |
| return ShellResult(p.returncode, stdout, stderr) |
| |
| |
| def run_test(host, test_native, test_java): |
| """Body of the test. |
| |
| Args: |
| host: AdbHost object to run tests on |
| test_native: True iff we should test native Binder clients. |
| test_java: True iff we shoudl test Java Binder clients. |
| """ |
| |
| print('Starting aidl integration testing...') |
| |
| # Kill any previous test context |
| host.run('rm -f %s' % JAVA_LOG_FILE, ignore_status=True) |
| host.run('pkill %s' % NATIVE_TEST_SERVICE, ignore_status=True) |
| |
| # Start up a native server |
| host.run(NATIVE_TEST_SERVICE, background=True) |
| |
| # Start up clients |
| if test_native: |
| host.run('pkill %s' % NATIVE_TEST_CLIENT, ignore_status=True) |
| result = host.run(NATIVE_TEST_CLIENT, ignore_status=True) |
| if result.exit_status: |
| print(result.printable_string()) |
| raise TestFail('%s returned status code %d' % |
| (NATIVE_TEST_CLIENT, result.exit_status)) |
| |
| if test_java: |
| host.run('am start -S -a android.intent.action.MAIN ' |
| '-n android.aidl.tests/.TestServiceClient ' |
| '--es sentinel.success "%s" ' |
| '--es sentinel.failure "%s"' % |
| (JAVA_SUCCESS_SENTINEL, JAVA_FAILURE_SENTINEL)) |
| result = host.run('%s %d %s "%s" "%s"' % |
| (JAVA_OUTPUT_READER, JAVA_CLIENT_TIMEOUT_SECONDS, |
| JAVA_LOG_FILE, JAVA_SUCCESS_SENTINEL, |
| JAVA_FAILURE_SENTINEL), |
| ignore_status=True) |
| if result.exit_status: |
| print(result.printable_string()) |
| raise TestFail('Java client did not complete successfully.') |
| |
| print('Success!') |
| |
| |
| def main(): |
| """Main entry point.""" |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument( |
| '--test-filter', default=TEST_FILTER_ALL, |
| choices=[TEST_FILTER_ALL, TEST_FILTER_JAVA, TEST_FILTER_NATIVE]) |
| parser.add_argument('--verbose', '-v', action='store_true', default=False) |
| args = parser.parse_args() |
| host = AdbHost(verbose=args.verbose) |
| try: |
| # Tragically, SELinux interferes with our testing |
| host.run('setenforce 0') |
| run_test(host, |
| args.test_filter in (TEST_FILTER_ALL, TEST_FILTER_NATIVE), |
| args.test_filter in (TEST_FILTER_ALL, TEST_FILTER_JAVA)) |
| finally: |
| host.run('setenforce 1') |
| |
| |
| if __name__ == '__main__': |
| main() |