1# Copyright (C) 2016 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#      http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15'''Module that contains the class UtilBundle, representing a collection of RS
16binaries.'''
17
18from __future__ import absolute_import
19
20import os
21import time
22from . import util_constants
23from . import util_log
24from .exception import TestSuiteException
25
26
27class UtilBundle(object):
28    '''Represents the collection of RS binaries that are debugged.'''
29
30    # Map of binary name to package name of all Java apps debugged
31    _tests_apk = {
32        'JavaInfiniteLoop': 'com.android.rs.infiniteloop',
33        'JavaDebugWaitAttach': 'com.android.rs.waitattachdebug',
34        'JavaNoDebugWaitAttach': 'com.android.rs.waitattachnodebug',
35        'BranchingFunCalls': 'com.android.rs.branchingfuncalls',
36        'KernelVariables': 'com.android.rs.kernelvariables',
37        'Allocations': 'com.android.rs.allocations',
38        'MultipleRSFiles': 'com.android.rs.multiplersfiles',
39        'SingleSource': 'com.android.rs.singlesource',
40        'ScriptGroup': 'com.android.rs.scriptgroup',
41        'Reduction': 'com.android.rs.lldbreductiontest',
42    }
43
44    _tests_jni = {
45        'JNIInfiniteLoop': 'com.android.rs.jniinfiniteloop',
46        'JNIDebugWaitAttach': 'com.android.rs.jnidebugwaitattach',
47        'JNINoDebugWaitAttach': 'com.android.rs.jninodebugwaitattach',
48        'JNIBranchingFunCalls': 'com.android.rs.jnibranchingfuncalls',
49        'JNIKernelVariables': 'com.android.rs.jnikernelvariables',
50        'JNIAllocations': 'com.android.rs.jniallocations',
51        'JNIMultipleRSFiles': 'com.android.rs.jnimultiplersfiles'
52    }
53
54    _tests_ndk = {'CppInfiniteLoop', 'CppNoDebugWaitAttach',
55                  'CppDebugWaitAttach', 'CppBranchingFunCalls',
56                  'CppKernelVariables', 'CppAllocations', 'CppMultipleRSFiles'}
57
58    _missing_path_msg = (
59        'No product path has been provided. If using `lunch` ensure '
60        'the `ANDROID_PRODUCT_OUT` environment variable has been set correctly. '
61        'Alternatively, include it in the config file or specify it explicitly '
62        'on the command line (`--aosp-product-path`)'
63    )
64
65    def __init__(self, android, aosp_product_path):
66        assert android
67        self._android = android # Link to the android module
68        self._aosp_product_path = aosp_product_path
69        self._log = util_log.get_logger()
70
71    def is_apk(self, name):
72        '''Checks if a binary of a given name is an apk.
73
74        Checks whether the name of the apk is in the dictionary of apks.
75
76        Args:
77            name: The string that is the name of the binary to check.
78
79        Returns:
80            True if the binary is an apk, False if it is not.
81
82        Raises:
83            TestSuiteException: The string does not match any item in the list
84            of APK or NDK binaries.
85        '''
86        if name in self._tests_apk:
87            return True
88        if name not in self._tests_ndk and name not in self._tests_jni:
89            raise TestSuiteException('test not apk or ndk')
90        return False
91
92    def uninstall_all(self):
93        '''Uninstall/Delete all the testsuite's apks and binaries on the device.
94
95        Raises:
96            TestSuiteException: One or more apks could not be uninstalled.
97        '''
98        self.uninstall_all_apk()
99        self._delete_all_ndk()
100        self._uninstall_all_jni()
101
102    def uninstall_all_apk(self):
103        '''Uninstall all apks used by the test suite from the device.
104
105        Raises:
106            TestSuiteException: An apk could not be uninstalled.
107        '''
108        max_num_attempts = 3
109        timeout = 180
110
111        for app, package in self._tests_apk.items():
112            self._log.info('Uninstalling the application: %s', app)
113            output = self._android.adb_retry('uninstall ' + package,
114                                             max_num_attempts, timeout)
115
116            if output is None:
117                raise TestSuiteException('Repeated timeouts when uninstalling '
118                                         'the application: ' + app)
119            elif 'Success' not in output:
120                outmsg = '\n' + output.rstrip() if output else '<empty>'
121                self._log.error('Cannot match the string "Success" in the '
122                                'output: %s', outmsg)
123                raise TestSuiteException('Unable to uninstall app ' + app)
124            else:
125                self._log.debug('Application uninstalled: %r', app)
126
127            if 'Success' not in output:
128                self._log.warning('unable to uninstall app ' + app)
129
130    def _uninstall_all_jni(self):
131        '''Uninstall all apks used by the test suite from the device.
132
133        Raises:
134            TestSuiteException: An apk could not be uninstalled.
135        '''
136        for app, package in self._tests_jni.items():
137            output = self._android.adb('uninstall ' + package)
138
139            if 'Success' not in output:
140                raise TestSuiteException('unable to uninstall app ' + app)
141
142    def _delete_all_ndk(self):
143        '''Delete all ndk binaries that were pushed to the device.
144
145        Raises:
146            TestSuiteException: A binary could not be deleted from the device.
147        '''
148        for app in self._tests_ndk:
149            output = self._android.shell('rm /data/' + app)
150            if 'No such file or directory' in output:
151                self._log.warning('unable to uninstall app ' + app)
152
153
154    def push_all(self):
155        '''Push all apk and ndk binaries required by the testsuite to the device
156
157        Raises:
158            TestSuiteException: One or more apks could not be installed or
159                                previously running processes thereof could not
160                                be killed.
161        '''
162        self._push_all_java()
163        self._push_all_ndk()
164        self._push_all_jni()
165
166    def _install_apk(self, app, package):
167        '''Push an apk files to the device.
168
169        This involves uninstalling any old installation and installing again.
170
171        Args:
172            app: A string that is the name of the apk.
173            package: A string that is the name of the package of the apk.
174
175        Raises:
176            TestSuiteException: The apk could not be installed.
177        '''
178        self._log.info('pushing {0}'.format(app))
179
180        self._android.stop_app(package)
181
182        self._android.adb('uninstall ' + package)
183        # Ignore the output of uninstall.
184        # The app may not have been installed in the first place. That's ok.
185
186        flags = ''
187
188        product_folder = self._aosp_product_path
189        if not product_folder:
190            raise TestSuiteException(self._missing_path_msg)
191
192        app_folder = os.path.join(product_folder, 'data/app')
193
194        cmd = 'install {0} {1}/{2}/{2}.apk'.format(flags, app_folder, app)
195        output = self._android.adb(cmd, False, True,
196                                   util_constants.PUSH_TIMEOUT)
197        if ('Success' not in output) or ("can't find" in output):
198            raise TestSuiteException('unable to install app {}: {}'.format(
199                app, output))
200
201    def _push_all_java(self):
202        '''Push all apk files to the device.
203
204        This involves uninstalling any old installations and installing again.
205
206        Raises:
207            TestSuiteException: An apk could not be installed.
208        '''
209        for app, package in self._tests_apk.items():
210            self._install_apk(app, package)
211
212    def _push_all_ndk(self):
213        '''Push all ndk binaries to the device.
214
215        Raises:
216            TestSuiteException: A binary could not be pushed to the device or
217                                a previous process could not be killed.
218        '''
219        product_folder = self._aosp_product_path
220        if not product_folder:
221            raise TestSuiteException(self._missing_path_msg)
222
223        bin_folder = os.path.join(product_folder, 'system/bin')
224
225        for app in self._tests_ndk:
226            self._log.info('pushing {0}'.format(app))
227
228            self._android.kill_all_processes(app)
229
230            cmd = 'push %s/%s /data' % (bin_folder, app)
231            output = self._android.adb(cmd, False, True,
232                                       util_constants.PUSH_TIMEOUT)
233            if ('failed to copy' in output or
234                'No such file or directory' in output):
235                raise TestSuiteException('unable to push binary ' + app)
236
237            # be sure to set the execute bit for NDK binaries
238            self._android.shell('chmod 777 /data/{0}'.format(app))
239
240    def _push_all_jni(self):
241        '''Push all JNI apk files to the device.
242
243        This involves uninstalling any old installations and installing again.
244
245        Raises:
246            TestSuiteException: An apk could not be installed.
247        '''
248        product_folder = self._aosp_product_path
249        if not product_folder:
250            raise TestSuiteException(self._missing_path_msg)
251
252        app_folder = os.path.join(product_folder, 'system/lib')
253
254        # Ensure the system/lib directory is writable
255        self._android.make_device_writeable()
256
257        for app, package in self._tests_jni.items():
258            self._install_apk(app, package)
259
260    def delete_ndk_cache(self):
261        '''Deletes NDK cached scripts from the device.
262
263        The NDK caches compiled scripts as shared libraries in
264        the folder specified when calling `rs->init()`.
265
266        For all out tests this is set to '/data/rscache'.
267        '''
268        self._android.shell('rm -r /data/rscache')
269
270    def get_package(self, app_name):
271        '''From a given apk name get the name of its package.
272
273        Args:
274            app_name: The string that is the name of the apk.
275
276        Returns:
277            A string representing the name of the package of the app.
278
279        Raises:
280            TestSuiteException: The app name is not in the list of apks.
281        '''
282        if app_name in self._tests_apk:
283            return self._tests_apk[app_name]
284        elif app_name in self._tests_jni:
285            return self._tests_jni[app_name]
286        else:
287            msg = ('unknown app %s. (Do you need to add an '
288                  'entry to bundle.py :: test_apps_?)' % app_name)
289            raise TestSuiteException(msg)
290        return self._tests_apk[app_name]
291
292    def launch(self, app_name):
293        '''Launch an apk/ndk app on a remote device.
294
295        Args:
296            app_name: The string that is the name of the APK or NDK executable.
297
298        Returns:
299            The Process ID of the launched executable, otherwise None
300
301        Raises:
302            TestSuiteException: Previous processes of this apk could not be
303                                killed.
304        '''
305        process_name = ''
306        success = False
307        if app_name in self._tests_apk:
308            process_name = self._tests_apk[app_name]
309
310            self._android.kill_all_processes(process_name)
311
312            success = self._android.launch_app(process_name, 'MainActivity')
313        elif app_name in self._tests_ndk:
314            process_name = app_name
315            self._android.kill_all_processes(process_name)
316            success = self._android.launch_elf(process_name)
317        elif app_name in self._tests_jni:
318            package = self._tests_jni[app_name]
319
320            self._android.kill_process(package)
321
322            success = self._android.launch_app(package, 'MainActivity')
323            if not success:
324                self._log.log_and_print(app_name +
325                    ' is not installed. Try removing the --no-install option?')
326                return None
327
328            return self._android.find_app_pid(package)
329        else:
330            self._log.error('Executable {0} neither Java nor NDK.'
331                            .format(app_name))
332
333            self._log.fatal('Failed to launch test executable {0}'
334                            .format(app_name))
335            return None
336
337        if not success:
338            self._log.log_and_print(app_name +
339                ' is not installed. Try removing the --no-install option?')
340            return None
341
342        return self._android.find_app_pid(process_name)
343
344    def check_apps_installed(self, java_only):
345        ''' Check whether all Java/JNI/NDK apps are installed on the device.
346
347        Args:
348            java_only: Boolean to specify whether only the Java apks should be
349                       checked (in case of --wimpy mode for example).
350
351        Raises:
352            TestSuiteException: Not all apps are installed.
353        '''
354        java_and_jni_apks = self._tests_apk.copy()
355
356        if not java_only:
357            java_and_jni_apks.update(self._tests_jni)
358
359        installed = self._android.shell('pm list packages -f')
360
361        for app, package in java_and_jni_apks.items():
362            if package not in installed:
363                raise TestSuiteException('apk %s is not installed.' % app)
364
365        if not java_only:
366            ls_data = self._android.shell('ls /data')
367            for app in self._tests_ndk:
368                if app not in ls_data:
369                    raise TestSuiteException('app %s is not installed.' % app)
370