Home | History | Annotate | Download | only in tests
      1 #!/usr/bin/env python
      2 
      3 """
      4 Test that aidl generates functional code by running it on an Android device.
      5 """
      6 
      7 import argparse
      8 import pipes
      9 import subprocess
     10 import shlex
     11 
     12 JAVA_OUTPUT_READER = 'aidl_test_sentinel_searcher'
     13 NATIVE_TEST_CLIENT = 'aidl_test_client'
     14 NATIVE_TEST_SERVICE = 'aidl_test_service'
     15 
     16 TEST_FILTER_ALL = 'all'
     17 TEST_FILTER_JAVA = 'java'
     18 TEST_FILTER_NATIVE = 'native'
     19 
     20 JAVA_CLIENT_TIMEOUT_SECONDS = 30
     21 JAVA_LOG_FILE = '/data/data/android.aidl.tests/files/test-client.log'
     22 JAVA_SUCCESS_SENTINEL = '>>> Java Client Success <<<'
     23 JAVA_FAILURE_SENTINEL = '>>> Java Client Failure <<<'
     24 
     25 class TestFail(Exception):
     26     """Raised on test failures."""
     27     pass
     28 
     29 
     30 class 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 
     57 class 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 
    127 def 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 
    184 def 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 
    204 if __name__ == '__main__':
    205     main()
    206