| '''Module that contains the class UtilBundle, representing a collection of RS |
| binaries.''' |
| |
| from __future__ import absolute_import |
| |
| import os |
| import time |
| from . import util_constants |
| from . import util_log |
| from .exception import TestSuiteException |
| |
| |
| class UtilBundle(object): |
| '''Represents the collection of RS binaries that are debugged.''' |
| |
| # Map of binary name to package name of all Java apps debugged |
| _tests_apk = { |
| 'JavaInfiniteLoop': 'com.android.rs.infiniteloop', |
| 'JavaDebugWaitAttach': 'com.android.rs.waitattachdebug', |
| 'JavaNoDebugWaitAttach': 'com.android.rs.waitattachnodebug', |
| 'BranchingFunCalls': 'com.android.rs.branchingfuncalls', |
| 'KernelVariables': 'com.android.rs.kernelvariables', |
| 'Allocations': 'com.android.rs.allocations', |
| 'MultipleRSFiles': 'com.android.rs.multiplersfiles', |
| 'SingleSource': 'com.android.rs.singlesource' |
| } |
| |
| _tests_jni = { |
| 'JNIInfiniteLoop': 'com.android.rs.jniinfiniteloop', |
| 'JNIDebugWaitAttach': 'com.android.rs.jnidebugwaitattach', |
| 'JNINoDebugWaitAttach': 'com.android.rs.jninodebugwaitattach', |
| 'JNIBranchingFunCalls': 'com.android.rs.jnibranchingfuncalls', |
| 'JNIKernelVariables': 'com.android.rs.jnikernelvariables', |
| 'JNIAllocations': 'com.android.rs.jniallocations', |
| 'JNIMultipleRSFiles': 'com.android.rs.jnimultiplersfiles' |
| } |
| |
| _tests_ndk = {'CppInfiniteLoop', 'CppNoDebugWaitAttach', |
| 'CppDebugWaitAttach', 'CppBranchingFunCalls', |
| 'CppKernelVariables', 'CppAllocations', 'CppMultipleRSFiles'} |
| |
| _missing_path_msg = ( |
| 'No product path has been provided. If using `lunch` ensure ' |
| 'the `ANDROID_PRODUCT_OUT` environment variable has been set correctly. ' |
| 'Alternatively, include it in the config file or specify it explicitly ' |
| 'on the command line (`--aosp-product-path`)' |
| ) |
| |
| def __init__(self, android, aosp_product_path): |
| assert android |
| self._android = android # Link to the android module |
| self._aosp_product_path = aosp_product_path |
| self._log = util_log.get_logger() |
| |
| def is_apk(self, name): |
| '''Checks if a binary of a given name is an apk. |
| |
| Checks whether the name of the apk is in the dictionary of apks. |
| |
| Args: |
| name: The string that is the name of the binary to check. |
| |
| Returns: |
| True if the binary is an apk, False if it is not. |
| |
| Raises: |
| TestSuiteException: The string does not match any item in the list |
| of APK or NDK binaries. |
| ''' |
| if name in self._tests_apk: |
| return True |
| if name not in self._tests_ndk and name not in self._tests_jni: |
| raise TestSuiteException('test not apk or ndk') |
| return False |
| |
| def uninstall_all(self): |
| '''Uninstall/Delete all the testsuite's apks and binaries on the device. |
| |
| Raises: |
| TestSuiteException: One or more apks could not be uninstalled. |
| ''' |
| self.uninstall_all_apk() |
| self._delete_all_ndk() |
| self._uninstall_all_jni() |
| |
| def uninstall_all_apk(self): |
| '''Uninstall all apks used by the test suite from the device. |
| |
| Raises: |
| TestSuiteException: An apk could not be uninstalled. |
| ''' |
| max_num_attempts = 3 |
| timeout = 180 |
| |
| for app, package in self._tests_apk.items(): |
| self._log.info('Uninstalling the application: %s', app) |
| output = self._android.adb_retry('uninstall ' + package, |
| max_num_attempts, timeout) |
| |
| if output is None: |
| raise TestSuiteException('Repeated timeouts when uninstalling ' |
| 'the application: ' + app) |
| elif 'Success' not in output: |
| outmsg = '\n' + output.rstrip() if output else '<empty>' |
| self._log.error('Cannot match the string "Success" in the ' |
| 'output: %s', outmsg) |
| raise TestSuiteException('Unable to uninstall app ' + app) |
| else: |
| self._log.debug('Application uninstalled: %r', app) |
| |
| if 'Success' not in output: |
| self._log.warning('unable to uninstall app ' + app) |
| |
| def _uninstall_all_jni(self): |
| '''Uninstall all apks used by the test suite from the device. |
| |
| Raises: |
| TestSuiteException: An apk could not be uninstalled. |
| ''' |
| for app, package in self._tests_jni.items(): |
| output = self._android.adb('uninstall ' + package) |
| |
| if 'Success' not in output: |
| raise TestSuiteException('unable to uninstall app ' + app) |
| |
| output = self._android.shell('rm /system/lib/lib%s.so' |
| % app.lower()) |
| if 'No such file or directory' in output: |
| self._log.warning( |
| 'unable to remove library file for app ' + app) |
| |
| def _delete_all_ndk(self): |
| '''Delete all ndk binaries that were pushed to the device. |
| |
| Raises: |
| TestSuiteException: A binary could not be deleted from the device. |
| ''' |
| for app in self._tests_ndk: |
| output = self._android.shell('rm /data/' + app) |
| if 'No such file or directory' in output: |
| self._log.warning('unable to uninstall app ' + app) |
| |
| |
| def push_all(self): |
| '''Push all apk and ndk binaries required by the testsuite to the device |
| |
| Raises: |
| TestSuiteException: One or more apks could not be installed or |
| previously running processes thereof could not |
| be killed. |
| ''' |
| self._push_all_java() |
| self._push_all_ndk() |
| self._push_all_jni() |
| |
| def _install_apk(self, app, package): |
| '''Push an apk files to the device. |
| |
| This involves uninstalling any old installation and installing again. |
| |
| Args: |
| app: A string that is the name of the apk. |
| package: A string that is the name of the package of the apk. |
| |
| Raises: |
| TestSuiteException: The apk could not be installed. |
| ''' |
| self._log.info('pushing {0}'.format(app)) |
| |
| self._android.stop_app(package) |
| |
| self._android.adb('uninstall ' + package) |
| # Ignore the output of uninstall. |
| # The app may not have been installed in the first place. That's ok. |
| |
| flags = '' |
| |
| product_folder = self._aosp_product_path |
| if not product_folder: |
| raise TestSuiteException(self._missing_path_msg) |
| |
| app_folder = os.path.join(product_folder, 'data/app') |
| |
| cmd = 'install {0} {1}/{2}/{2}.apk'.format(flags, app_folder, app) |
| output = self._android.adb(cmd, False, True, |
| util_constants.PUSH_TIMEOUT) |
| if ('Success' not in output) or ("can't find" in output): |
| raise TestSuiteException('unable to install app ' + app) |
| |
| def _push_all_java(self): |
| '''Push all apk files to the device. |
| |
| This involves uninstalling any old installations and installing again. |
| |
| Raises: |
| TestSuiteException: An apk could not be installed. |
| ''' |
| for app, package in self._tests_apk.items(): |
| self._install_apk(app, package) |
| |
| def _push_all_ndk(self): |
| '''Push all ndk binaries to the device. |
| |
| Raises: |
| TestSuiteException: A binary could not be pushed to the device or |
| a previous process could not be killed. |
| ''' |
| product_folder = self._aosp_product_path |
| if not product_folder: |
| raise TestSuiteException(self._missing_path_msg) |
| |
| bin_folder = os.path.join(product_folder, 'system/bin') |
| |
| for app in self._tests_ndk: |
| self._log.info('pushing {0}'.format(app)) |
| |
| self._android.kill_all_processes(app) |
| |
| cmd = 'push %s/%s /data' % (bin_folder, app) |
| output = self._android.adb(cmd, False, True, |
| util_constants.PUSH_TIMEOUT) |
| if ('failed to copy' in output or |
| 'No such file or directory' in output): |
| raise TestSuiteException('unable to push binary ' + app) |
| |
| # be sure to set the execute bit for NDK binaries |
| self._android.shell('chmod 777 /data/{0}'.format(app)) |
| |
| def _push_all_jni(self): |
| '''Push all JNI apk files to the device. |
| |
| This involves uninstalling any old installations and installing again. |
| |
| Raises: |
| TestSuiteException: An apk could not be installed. |
| ''' |
| product_folder = self._aosp_product_path |
| if not product_folder: |
| raise TestSuiteException(self._missing_path_msg) |
| |
| app_folder = os.path.join(product_folder, 'system/lib') |
| |
| # Ensure the system/lib directory is writable |
| self._android.make_device_writeable() |
| |
| for app, package in self._tests_jni.items(): |
| self._install_apk(app, package) |
| output = self._android.adb('push %s/lib%s.so /system/lib' % |
| (app_folder, app.lower()), False, True, |
| util_constants.PUSH_TIMEOUT) |
| if ("error" in output or |
| "failed to copy" in output): |
| raise TestSuiteException( |
| 'unable to copy %s/lib%s.so to /system/lib: %s' |
| % (app_folder, app.lower(), output)) |
| |
| def delete_ndk_cache(self): |
| '''Deletes NDK cached scripts from the device. |
| |
| The NDK caches compiled scripts as shared libraries in |
| the folder specified when calling `rs->init()`. |
| |
| For all out tests this is set to '/data/rscache'. |
| ''' |
| self._android.shell('rm -r /data/rscache') |
| |
| def get_package(self, app_name): |
| '''From a given apk name get the name of its package. |
| |
| Args: |
| app_name: The string that is the name of the apk. |
| |
| Returns: |
| A string representing the name of the package of the app. |
| |
| Raises: |
| TestSuiteException: The app name is not in the list of apks. |
| ''' |
| if app_name in self._tests_apk: |
| return self._tests_apk[app_name] |
| elif app_name in self._tests_jni: |
| return self._tests_jni[app_name] |
| else: |
| msg = ('unknown app %s. (Do you need to add an ' |
| 'entry to bundle.py :: test_apps_?)' % app_name) |
| raise TestSuiteException(msg) |
| return self._tests_apk[app_name] |
| |
| def launch(self, app_name): |
| '''Launch an apk/ndk app on a remote device. |
| |
| Args: |
| app_name: The string that is the name of the APK or NDK executable. |
| |
| Returns: |
| The Process ID of the launched executable, otherwise None |
| |
| Raises: |
| TestSuiteException: Previous processes of this apk could not be |
| killed. |
| ''' |
| process_name = '' |
| success = False |
| if app_name in self._tests_apk: |
| process_name = self._tests_apk[app_name] |
| |
| self._android.kill_all_processes(process_name) |
| |
| success = self._android.launch_app(process_name, 'MainActivity') |
| elif app_name in self._tests_ndk: |
| process_name = app_name |
| self._android.kill_all_processes(process_name) |
| success = self._android.launch_elf(process_name) |
| elif app_name in self._tests_jni: |
| package = self._tests_jni[app_name] |
| |
| self._android.kill_process(package) |
| |
| success = self._android.launch_app(package, 'MainActivity') |
| if not success: |
| self._log.log_and_print(app_name + |
| ' is not installed. Try removing the --no-install option?') |
| return None |
| |
| return self._android.find_app_pid(package) |
| else: |
| self._log.error('Executable {0} neither Java nor NDK.' |
| .format(app_name)) |
| |
| self._log.fatal('Failed to launch test executable {0}' |
| .format(app_name)) |
| return None |
| |
| if not success: |
| self._log.log_and_print(app_name + |
| ' is not installed. Try removing the --no-install option?') |
| return None |
| |
| return self._android.find_app_pid(process_name) |
| |
| def check_apps_installed(self, java_only): |
| ''' Check whether all Java/JNI/NDK apps are installed on the device. |
| |
| Args: |
| java_only: Boolean to specify whether only the Java apks should be |
| checked (in case of --wimpy mode for example). |
| |
| Raises: |
| TestSuiteException: Not all apps are installed. |
| ''' |
| java_and_jni_apks = self._tests_apk.copy() |
| |
| if not java_only: |
| java_and_jni_apks.update(self._tests_jni) |
| |
| installed = self._android.shell('pm list packages -f') |
| |
| for app, package in java_and_jni_apks.items(): |
| if package not in installed: |
| raise TestSuiteException('apk %s is not installed.' % app) |
| |
| if not java_only: |
| ls_data = self._android.shell('ls /data') |
| for app in self._tests_ndk: |
| if app not in ls_data: |
| raise TestSuiteException('app %s is not installed.' % app) |