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=None, 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            # outer redirection to /dev/null required to avoid subprocess.Popen blocking
87            # on the FDs being closed
88            command = '(( %s ) </dev/null 2>&1 | log -t %s &) >/dev/null 2>&1' % (command, background)
89        return self.adb('shell %s' % pipes.quote(command),
90                        ignore_status=ignore_status)
91
92    def adb(self, command, ignore_status=False):
93        """Run an ADB command (e.g. `adb sync`).
94
95        Args:
96            command: string containing command to run
97            ignore_status: True iff we should ignore the command's exit code.
98
99        Returns:
100            instance of ShellResult.
101
102        Raises:
103            subprocess.CalledProcessError on command exit != 0.
104        """
105        command = 'adb %s' % command
106        p = subprocess.Popen(command, shell=True, close_fds=True,
107                             stdout=subprocess.PIPE, stderr=subprocess.PIPE,
108                             universal_newlines=True)
109        stdout, stderr = p.communicate()
110        if not ignore_status and p.returncode:
111            raise subprocess.CalledProcessError(p.returncode, command)
112        return ShellResult(p.returncode, stdout, stderr)
113
114class NativeServer:
115    def cleanup(self):
116        self.host.run('killall %s' % self.binary, ignore_status=True)
117    def run(self):
118        return self.host.run(self.binary, background=self.binary)
119
120class NativeClient:
121    def cleanup(self):
122        self.host.run('killall %s' % self.binary, ignore_status=True)
123    def run(self):
124        result = self.host.run(self.binary + ' --gtest_color=yes', ignore_status=True)
125        print(result.printable_string())
126        if result.exit_status:
127            raise ShellResultFail(result)
128
129class CppServer(NativeServer):
130    def __init__(self, host, bitness):
131        self.name = "%s_bit_cpp_server" % pretty_bitness(bitness)
132        self.host = host
133        self.binary = CPP_TEST_SERVICE_FOR_BITNESS % bitness
134
135class CppClient(NativeClient):
136    def __init__(self, host, bitness):
137        self.name = "%s_bit_cpp_client" % pretty_bitness(bitness)
138        self.host = host
139        self.binary = CPP_TEST_CLIENT_FOR_BITNESS % bitness
140
141class CppV1Client(NativeClient):
142    def __init__(self, host, bitness):
143        self.name = "%s_bit_cpp_v1_client" % pretty_bitness(bitness)
144        self.host = host
145        self.binary = CPP_TEST_V1_CLIENT_FOR_BITNESS % bitness
146
147class NdkServer(NativeServer):
148    def __init__(self, host, bitness):
149        self.name = "%s_bit_ndk_server" % pretty_bitness(bitness)
150        self.host = host
151        self.binary = NDK_TEST_SERVICE_FOR_BITNESS % bitness
152
153class NdkClient(NativeClient):
154    def __init__(self, host, bitness):
155        self.name = "%s_bit_ndk_client" % pretty_bitness(bitness)
156        self.host = host
157        self.binary = NDK_TEST_CLIENT_FOR_BITNESS % bitness
158
159class JavaServer:
160    def __init__(self, host, bitness):
161        self.name = "java_server_%s" % pretty_bitness(bitness)
162        self.host = host
163        self.bitness = bitness
164    def cleanup(self):
165        self.host.run('killall ' + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness),
166                      ignore_status=True)
167    def run(self):
168        return self.host.run('CLASSPATH=/data/framework/aidl_test_java_service.jar '
169                             + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness) +
170                             ' /data/framework android.aidl.service.TestServiceServer',
171                             background=self.name)
172
173class JavaClient:
174    def __init__(self, host, bitness):
175        self.name = "java_client_%s" % pretty_bitness(bitness)
176        self.host = host
177        self.bitness = bitness
178    def cleanup(self):
179        self.host.run('killall ' + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness),
180                      ignore_status=True)
181    def run(self):
182        result = self.host.run('CLASSPATH=/data/framework/aidl_test_java_client.jar '
183                               + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness) +
184                               ' /data/framework android.aidl.tests.AidlJavaTests')
185        print(result.printable_string())
186        if re.search(INSTRUMENTATION_SUCCESS_PATTERN, result.stdout) is None:
187            raise ShellResultFail(result)
188
189class JavaVersionTestClient:
190    def __init__(self, host, bitness, ver):
191        self.name = "java_client_sdk%d_%s" % (ver, pretty_bitness(bitness))
192        self.host = host
193        self.bitness = bitness
194        self.ver = ver
195    def cleanup(self):
196        self.host.run('killall ' + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness),
197                      ignore_status=True)
198    def run(self):
199        result = self.host.run('CLASSPATH=/data/framework/aidl_test_java_client_sdk%d.jar ' % self.ver
200                               + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness) +
201                               ' /data/framework android.aidl.sdkversion.tests.AidlJavaVersionTests')
202        print(result.printable_string())
203        if re.search(INSTRUMENTATION_SUCCESS_PATTERN, result.stdout) is None:
204            raise ShellResultFail(result)
205
206class JavaVersionTestServer:
207    def __init__(self, host, bitness, ver):
208        self.name = "java_server_sdk%s_%s" % (ver, pretty_bitness(bitness))
209        self.host = host
210        self.bitness = bitness
211        self.ver = ver
212    def cleanup(self):
213        self.host.run('killall ' + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness),
214                      ignore_status=True)
215    def run(self):
216        return self.host.run('CLASSPATH=/data/framework/aidl_test_java_service_sdk%d.jar ' % self.ver
217                             + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness) +
218                             ' /data/framework android.aidl.sdkversion.service.AidlJavaVersionTestService',
219                             background=self.name)
220
221class JavaPermissionClient:
222    def __init__(self, host, bitness):
223        self.name = "java_client_permission_%s" % pretty_bitness(bitness)
224        self.host = host
225        self.bitness = bitness
226    def cleanup(self):
227        self.host.run('killall ' + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness),
228                      ignore_status=True)
229    def run(self):
230        result = self.host.run('CLASSPATH=/data/framework/aidl_test_java_client_permission.jar '
231                               + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness) +
232                               ' /data/framework android.aidl.permission.tests.PermissionTests')
233        print(result.printable_string())
234        if re.search(INSTRUMENTATION_SUCCESS_PATTERN, result.stdout) is None:
235            raise ShellResultFail(result)
236
237class JavaPermissionServer:
238    def __init__(self, host, bitness):
239        self.name = "java_server_permission_%s" % pretty_bitness(bitness)
240        self.host = host
241        self.bitness = bitness
242    def cleanup(self):
243        self.host.run('killall ' + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness),
244                      ignore_status=True)
245    def run(self):
246        return self.host.run('CLASSPATH=/data/framework/aidl_test_java_service_permission.jar '
247                             + APP_PROCESS_FOR_PRETTY_BITNESS % pretty_bitness(self.bitness) +
248                             ' /data/framework android.aidl.permission.service.PermissionTestService',
249                             background=self.name)
250
251def getprop(host, prop):
252    return host.run('getprop "%s"' % prop).stdout.strip()
253
254class RustClient:
255    def __init__(self, host, bitness):
256        self.name = "%s_bit_rust_client" % pretty_bitness(bitness)
257        self.host = host
258        self.binary = RUST_TEST_CLIENT_FOR_BITNESS % bitness
259    def cleanup(self):
260        self.host.run('killall %s' % self.binary, ignore_status=True)
261    def run(self):
262        result = self.host.run(self.binary, ignore_status=True)
263        print(result.printable_string())
264        if result.exit_status:
265            raise ShellResultFail(result)
266
267class RustServer:
268    def __init__(self, host, bitness):
269        self.name = "%s_bit_rust_server" % pretty_bitness(bitness)
270        self.host = host
271        self.binary = RUST_TEST_SERVICE_FOR_BITNESS % bitness
272    def cleanup(self):
273        self.host.run('killall %s' % self.binary, ignore_status=True)
274    def run(self):
275        return self.host.run(self.binary, background=self.name)
276
277class RustAsyncServer:
278    def __init__(self, host, bitness):
279        self.name = "%s_bit_rust_server_async" % pretty_bitness(bitness)
280        self.host = host
281        self.binary = RUST_TEST_SERVICE_ASYNC_FOR_BITNESS % bitness
282    def cleanup(self):
283        self.host.run('killall %s' % self.binary, ignore_status=True)
284    def run(self):
285        return self.host.run(self.binary, background=self.name)
286
287def supported_bitnesses(host):
288    bitnesses = []
289    if getprop(host, "ro.product.cpu.abilist32") != "":
290        bitnesses += [BITNESS_32]
291    if getprop(host, "ro.product.cpu.abilist64") != "":
292        bitnesses += [BITNESS_64]
293    return bitnesses
294
295# tests added dynamically below
296class TestAidl(unittest.TestCase):
297    pass
298
299def make_test(client, server):
300    def test(self):
301        try:
302            # Server is unregistered first so that we give more time
303            # for servicemanager to clear the old notification.
304            # Otherwise, it may race that the client gets ahold
305            # of the service.
306            server.cleanup()
307            client.cleanup()
308            sleep(0.2)
309
310            server.run()
311            client.run()
312        finally:
313            client.cleanup()
314            server.cleanup()
315    return test
316
317def add_test(client, server):
318    test_name = 'test_%s_to_%s' % (client.name, server.name)
319    test = make_test(client, server)
320    setattr(TestAidl, test_name, test)
321
322if __name__ == '__main__':
323    host = AdbHost()
324    bitnesses = supported_bitnesses(host)
325    if len(bitnesses) == 0:
326        print("No clients installed")
327        exit(1)
328
329    clients = []
330    servers = []
331
332    for bitness in bitnesses:
333        clients += [NdkClient(host, bitness)]
334        servers += [NdkServer(host, bitness)]
335
336        clients += [CppClient(host, bitness)]
337        clients += [CppV1Client(host, bitness)]
338        servers += [CppServer(host, bitness)]
339
340        clients += [JavaClient(host, bitness)]
341        servers += [JavaServer(host, bitness)]
342
343        clients += [RustClient(host, bitness)]
344        servers += [RustServer(host, bitness)]
345        servers += [RustAsyncServer(host, bitness)]
346
347    for client in clients:
348        for server in servers:
349            add_test(client, server)
350
351    # boolean: >= 29
352    # typed:   >= 23
353    versions = [1, 29]
354    for c_version, c_bitness, s_version, s_bitness in product(versions, bitnesses, repeat=2):
355        client = JavaVersionTestClient(host, c_bitness, c_version)
356        server = JavaVersionTestServer(host, s_bitness, s_version)
357        add_test(client, server)
358
359    # TODO(b/218914259): Interfaces with permission are only supported for the
360    # Java backend. Once C++ and/or Rust are supported, move the test back into
361    # JavaClient and JavaServer.
362    for bitness in bitnesses:
363        add_test(JavaPermissionClient(host, bitness), JavaPermissionServer(host, bitness))
364
365    suite = unittest.TestLoader().loadTestsFromTestCase(TestAidl)
366    sys.exit(not unittest.TextTestRunner(verbosity=2).run(suite).wasSuccessful())
367