1#!/usr/bin/env python3
2
3from itertools import product
4from time import sleep
5
6import pipes
7import re
8import subprocess
9import sys
10import textwrap
11import unittest
12
13BITNESS_32 = ("", "32")
14BITNESS_64 = ("64", "64")
15
16APP_PROCESS_FOR_PRETTY_BITNESS = 'app_process%s'
17CPP_TEST_SERVICE_FOR_BITNESS = ' /data/nativetest%s/aidl_test_service/aidl_test_service%s'
18CPP_TEST_CLIENT_FOR_BITNESS = ' /data/nativetest%s/aidl_test_client/aidl_test_client%s'
19CPP_TEST_V1_CLIENT_FOR_BITNESS = ' /data/nativetest%s/aidl_test_v1_client/aidl_test_v1_client%s'
20NDK_TEST_SERVICE_FOR_BITNESS = ' /data/nativetest%s/aidl_test_service_ndk/aidl_test_service_ndk%s'
21NDK_TEST_CLIENT_FOR_BITNESS = ' /data/nativetest%s/aidl_test_client_ndk/aidl_test_client_ndk%s'
22RUST_TEST_CLIENT_FOR_BITNESS = ' /data/nativetest%s/aidl_test_rust_client/aidl_test_rust_client%s'
23RUST_TEST_SERVICE_FOR_BITNESS = ' /data/nativetest%s/aidl_test_rust_service/aidl_test_rust_service%s'
24RUST_TEST_SERVICE_ASYNC_FOR_BITNESS = ' /data/nativetest%s/aidl_test_rust_service_async/aidl_test_rust_service_async%s'
25
26# From AidlTestsJava.java
27INSTRUMENTATION_SUCCESS_PATTERN = r'TEST SUCCESS\n$'
28
29class ShellResultFail(Exception):
30    """Raised on test failures."""
31    def __init__(self, err):
32        stderr = textwrap.indent(err.stderr, "    ")
33        stdout = textwrap.indent(err.stdout, "    ")
34
35        super().__init__(f"STDOUT:\n{stdout}\nSTDERR:\n{stderr}\nRESULT:{err.exit_status}")
36
37def pretty_bitness(bitness):
38    """Returns a human readable version of bitness, corresponding to BITNESS_* variable"""
39    return bitness[-1]
40
41class ShellResult(object):
42    """Represents the result of running a shell command."""
43
44    def __init__(self, exit_status, stdout, stderr):
45        """Construct an instance.
46
47        Args:
48            exit_status: integer exit code of shell command
49            stdout: string stdout of shell command
50            stderr: string stderr of shell command
51        """
52        self.stdout = stdout
53        self.stderr = stderr
54        self.exit_status = exit_status
55
56    def printable_string(self):
57        """Get a string we could print to the logs and understand."""
58        output = []
59        output.append('stdout:')
60        for line in self.stdout.splitlines():
61            output.append('  > %s' % line)
62        output.append('stderr:')
63        for line in self.stderr.splitlines():
64            output.append('  > %s' % line)
65        return '\n'.join(output)
66
67
68class AdbHost(object):
69    """Represents a device connected via ADB."""
70
71    def run(self, command, background=False, ignore_status=False):
72        """Run a command on the device via adb shell.
73
74        Args:
75            command: string containing a shell command to run.
76            background: True iff we should run this command in the background.
77            ignore_status: True iff we should ignore the command's exit code.
78
79        Returns:
80            instance of ShellResult.
81
82        Raises:
83            subprocess.CalledProcessError on command exit != 0.
84        """
85        if background:
86            command = '( %s ) </dev/null >/dev/null 2>&1 &' % command
87        return self.adb('shell %s' % pipes.quote(command),
88                        ignore_status=ignore_status)
89
90    def adb(self, command, ignore_status=False):
91        """Run an ADB command (e.g. `adb sync`).
92
93        Args:
94            command: string containing command to run
95            ignore_status: True iff we should ignore the command's exit code.
96
97        Returns:
98            instance of ShellResult.
99
100        Raises:
101            subprocess.CalledProcessError on command exit != 0.
102        """
103        command = 'adb %s' % command
104        p = subprocess.Popen(command, shell=True, close_fds=True,
105                             stdout=subprocess.PIPE, stderr=subprocess.PIPE,
106                             universal_newlines=True)
107        stdout, stderr = p.communicate()
108        if not ignore_status and p.returncode:
109            raise subprocess.CalledProcessError(p.returncode, command)
110        return ShellResult(p.returncode, stdout, stderr)
111
112class NativeServer:
113    def cleanup(self):
114        self.host.run('killall %s' % self.binary, ignore_status=True)
115    def run(self):
116        return self.host.run(self.binary, background=True)
117
118class NativeClient:
119    def cleanup(self):
120        self.host.run('killall %s' % self.binary, ignore_status=True)
121    def run(self):
122        result = self.host.run(self.binary + ' --gtest_color=yes', ignore_status=True)
123        print(result.printable_string())
124        if result.exit_status:
125            raise ShellResultFail(result)
126
127class CppServer(NativeServer):
128    def __init__(self, host, bitness):
129        self.name = "%s_bit_cpp_server" % pretty_bitness(bitness)
130        self.host = host
131        self.binary = CPP_TEST_SERVICE_FOR_BITNESS % bitness
132
133class CppClient(NativeClient):
134    def __init__(self, host, bitness):
135        self.name = "%s_bit_cpp_client" % pretty_bitness(bitness)
136        self.host = host
137        self.binary = CPP_TEST_CLIENT_FOR_BITNESS % bitness
138
139class CppV1Client(NativeClient):
140    def __init__(self, host, bitness):
141        self.name = "%s_bit_cpp_v1_client" % pretty_bitness(bitness)
142        self.host = host
143        self.binary = CPP_TEST_V1_CLIENT_FOR_BITNESS % bitness
144
145class NdkServer(NativeServer):
146    def __init__(self, host, bitness):
147        self.name = "%s_bit_ndk_server" % pretty_bitness(bitness)
148        self.host = host
149        self.binary = NDK_TEST_SERVICE_FOR_BITNESS % bitness
150
151class NdkClient(NativeClient):
152    def __init__(self, host, bitness):
153        self.name = "%s_bit_ndk_client" % pretty_bitness(bitness)
154        self.host = host
155        self.binary = NDK_TEST_CLIENT_FOR_BITNESS % bitness
156
157class JavaServer:
158    def __init__(self, host, bitness):
159        self.name = "java_server_%s" % pretty_bitness(bitness)
160        self.host = host
161        self.bitness = bitness
162    def cleanup(self):
163        self.host.run('killall ' + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness),
164                      ignore_status=True)
165    def run(self):
166        return self.host.run('CLASSPATH=/data/framework/aidl_test_java_service.jar '
167                             + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness) +
168                             ' /data/framework android.aidl.service.TestServiceServer',
169                             background=True)
170
171class JavaClient:
172    def __init__(self, host, bitness):
173        self.name = "java_client_%s" % pretty_bitness(bitness)
174        self.host = host
175        self.bitness = bitness
176    def cleanup(self):
177        self.host.run('killall ' + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness),
178                      ignore_status=True)
179    def run(self):
180        result = self.host.run('CLASSPATH=/data/framework/aidl_test_java_client.jar '
181                               + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness) +
182                               ' /data/framework android.aidl.tests.AidlJavaTests')
183        print(result.printable_string())
184        if re.search(INSTRUMENTATION_SUCCESS_PATTERN, result.stdout) is None:
185            raise ShellResultFail(result)
186
187class JavaVersionTestClient:
188    def __init__(self, host, bitness, ver):
189        self.name = "java_client_sdk%d_%s" % (ver, pretty_bitness(bitness))
190        self.host = host
191        self.bitness = bitness
192        self.ver = ver
193    def cleanup(self):
194        self.host.run('killall ' + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness),
195                      ignore_status=True)
196    def run(self):
197        result = self.host.run('CLASSPATH=/data/framework/aidl_test_java_client_sdk%d.jar ' % self.ver
198                               + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness) +
199                               ' /data/framework android.aidl.sdkversion.tests.AidlJavaVersionTests')
200        print(result.printable_string())
201        if re.search(INSTRUMENTATION_SUCCESS_PATTERN, result.stdout) is None:
202            raise ShellResultFail(result)
203
204class JavaVersionTestServer:
205    def __init__(self, host, bitness, ver):
206        self.name = "java_server_sdk%s_%s" % (ver, pretty_bitness(bitness))
207        self.host = host
208        self.bitness = bitness
209        self.ver = ver
210    def cleanup(self):
211        self.host.run('killall ' + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness),
212                      ignore_status=True)
213    def run(self):
214        return self.host.run('CLASSPATH=/data/framework/aidl_test_java_service_sdk%d.jar ' % self.ver
215                             + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness) +
216                             ' /data/framework android.aidl.sdkversion.service.AidlJavaVersionTestService',
217                             background=True)
218
219class JavaPermissionClient:
220    def __init__(self, host, bitness):
221        self.name = "java_client_permission_%s" % pretty_bitness(bitness)
222        self.host = host
223        self.bitness = bitness
224    def cleanup(self):
225        self.host.run('killall ' + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness),
226                      ignore_status=True)
227    def run(self):
228        result = self.host.run('CLASSPATH=/data/framework/aidl_test_java_client_permission.jar '
229                               + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness) +
230                               ' /data/framework android.aidl.permission.tests.PermissionTests')
231        print(result.printable_string())
232        if re.search(INSTRUMENTATION_SUCCESS_PATTERN, result.stdout) is None:
233            raise ShellResultFail(result)
234
235class JavaPermissionServer:
236    def __init__(self, host, bitness):
237        self.name = "java_server_permission_%s" % pretty_bitness(bitness)
238        self.host = host
239        self.bitness = bitness
240    def cleanup(self):
241        self.host.run('killall ' + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness),
242                      ignore_status=True)
243    def run(self):
244        return self.host.run('CLASSPATH=/data/framework/aidl_test_java_service_permission.jar '
245                             + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness) +
246                             ' /data/framework android.aidl.permission.service.PermissionTestService',
247                             background=True)
248
249def getprop(host, prop):
250    return host.run('getprop "%s"' % prop).stdout.strip()
251
252class RustClient:
253    def __init__(self, host, bitness):
254        self.name = "%s_bit_rust_client" % pretty_bitness(bitness)
255        self.host = host
256        self.binary = RUST_TEST_CLIENT_FOR_BITNESS % bitness
257    def cleanup(self):
258        self.host.run('killall %s' % self.binary, ignore_status=True)
259    def run(self):
260        result = self.host.run(self.binary, ignore_status=True)
261        print(result.printable_string())
262        if result.exit_status:
263            raise ShellResultFail(result)
264
265class RustServer:
266    def __init__(self, host, bitness):
267        self.name = "%s_bit_rust_server" % pretty_bitness(bitness)
268        self.host = host
269        self.binary = RUST_TEST_SERVICE_FOR_BITNESS % bitness
270    def cleanup(self):
271        self.host.run('killall %s' % self.binary, ignore_status=True)
272    def run(self):
273        return self.host.run(self.binary, background=True)
274
275class RustAsyncServer:
276    def __init__(self, host, bitness):
277        self.name = "%s_bit_rust_server_async" % pretty_bitness(bitness)
278        self.host = host
279        self.binary = RUST_TEST_SERVICE_ASYNC_FOR_BITNESS % bitness
280    def cleanup(self):
281        self.host.run('killall %s' % self.binary, ignore_status=True)
282    def run(self):
283        return self.host.run(self.binary, background=True)
284
285def supported_bitnesses(host):
286    bitnesses = []
287    if getprop(host, "ro.product.cpu.abilist32") != "":
288        bitnesses += [BITNESS_32]
289    if getprop(host, "ro.product.cpu.abilist64") != "":
290        bitnesses += [BITNESS_64]
291    return bitnesses
292
293# tests added dynamically below
294class TestAidl(unittest.TestCase):
295    pass
296
297def make_test(client, server):
298    def test(self):
299        try:
300            # Server is unregistered first so that we give more time
301            # for servicemanager to clear the old notification.
302            # Otherwise, it may race that the client gets ahold
303            # of the service.
304            server.cleanup()
305            client.cleanup()
306            sleep(0.2)
307
308            server.run()
309            client.run()
310        finally:
311            client.cleanup()
312            server.cleanup()
313    return test
314
315def add_test(client, server):
316    test_name = 'test_%s_to_%s' % (client.name, server.name)
317    test = make_test(client, server)
318    setattr(TestAidl, test_name, test)
319
320if __name__ == '__main__':
321    host = AdbHost()
322    bitnesses = supported_bitnesses(host)
323    if len(bitnesses) == 0:
324        print("No clients installed")
325        exit(1)
326
327    clients = []
328    servers = []
329
330    for bitness in bitnesses:
331        clients += [NdkClient(host, bitness)]
332        servers += [NdkServer(host, bitness)]
333
334        clients += [CppClient(host, bitness)]
335        clients += [CppV1Client(host, bitness)]
336        servers += [CppServer(host, bitness)]
337
338        clients += [JavaClient(host, bitness)]
339        servers += [JavaServer(host, bitness)]
340
341        clients += [RustClient(host, bitness)]
342        servers += [RustServer(host, bitness)]
343        servers += [RustAsyncServer(host, bitness)]
344
345    for client in clients:
346        for server in servers:
347            add_test(client, server)
348
349    # boolean: >= 29
350    # typed:   >= 23
351    versions = [1, 29]
352    for c_version, c_bitness, s_version, s_bitness in product(versions, bitnesses, repeat=2):
353        client = JavaVersionTestClient(host, c_bitness, c_version)
354        server = JavaVersionTestServer(host, s_bitness, s_version)
355        add_test(client, server)
356
357    # TODO(b/218914259): Interfaces with permission are only supported for the
358    # Java backend. Once C++ and/or Rust are supported, move the test back into
359    # JavaClient and JavaServer.
360    for bitness in bitnesses:
361        add_test(JavaPermissionClient(host, bitness), JavaPermissionServer(host, bitness))
362
363    suite = unittest.TestLoader().loadTestsFromTestCase(TestAidl)
364    sys.exit(not unittest.TextTestRunner(verbosity=2).run(suite).wasSuccessful())
365