mirror of
				https://github.com/ARM-software/workload-automation.git
				synced 2025-10-30 22:54:18 +00:00 
			
		
		
		
	Initial commit of open source Workload Automation.
This commit is contained in:
		
							
								
								
									
										16
									
								
								wlauto/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								wlauto/common/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #    Copyright 2013-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								wlauto/common/android/BaseUiAutomation.class
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								wlauto/common/android/BaseUiAutomation.class
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										16
									
								
								wlauto/common/android/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								wlauto/common/android/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #    Copyright 2014-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
							
								
								
									
										678
									
								
								wlauto/common/android/device.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										678
									
								
								wlauto/common/android/device.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,678 @@ | ||||
| #    Copyright 2013-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
| # pylint: disable=E1101 | ||||
| import os | ||||
| import sys | ||||
| import re | ||||
| import time | ||||
| import tempfile | ||||
| import shutil | ||||
| import threading | ||||
| from subprocess import CalledProcessError | ||||
|  | ||||
| from wlauto.core.extension import Parameter | ||||
| from wlauto.common.linux.device import BaseLinuxDevice | ||||
| from wlauto.exceptions import DeviceError, WorkerThreadError, TimeoutError, DeviceNotRespondingError | ||||
| from wlauto.utils.misc import convert_new_lines | ||||
| from wlauto.utils.types import boolean, regex | ||||
| from wlauto.utils.android import (adb_shell, adb_background_shell, adb_list_devices, | ||||
|                                   adb_command, AndroidProperties, ANDROID_VERSION_MAP) | ||||
|  | ||||
|  | ||||
| SCREEN_STATE_REGEX = re.compile('(?:mPowerState|mScreenOn)=([0-9]+|true|false)', re.I) | ||||
|  | ||||
|  | ||||
| class AndroidDevice(BaseLinuxDevice):  # pylint: disable=W0223 | ||||
|     """ | ||||
|     Device running Android OS. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     platform = 'android' | ||||
|  | ||||
|     parameters = [ | ||||
|         Parameter('adb_name', | ||||
|                   description='The unique ID of the device as output by "adb devices".'), | ||||
|         Parameter('android_prompt', kind=regex, default=re.compile('^.*(shell|root)@.*:/ [#$] ', re.MULTILINE), | ||||
|                   description='The format  of matching the shell prompt in Android.'), | ||||
|         Parameter('working_directory', default='/sdcard/wa-working', | ||||
|                   description='Directory that will be used WA on the device for output files etc.'), | ||||
|         Parameter('binaries_directory', default='/system/bin', | ||||
|                   description='Location of binaries on the device.'), | ||||
|         Parameter('package_data_directory', default='/data/data', | ||||
|                   description='Location of of data for an installed package (APK).'), | ||||
|         Parameter('external_storage_directory', default='/sdcard', | ||||
|                   description='Mount point for external storage.'), | ||||
|         Parameter('connection', default='usb', allowed_values=['usb', 'ethernet'], | ||||
|                   description='Specified the nature of adb connection.'), | ||||
|         Parameter('logcat_poll_period', kind=int, | ||||
|                   description=""" | ||||
|                   If specified and is not ``0``, logcat will be polled every | ||||
|                   ``logcat_poll_period`` seconds, and buffered on the host. This | ||||
|                   can be used if a lot of output is expected in logcat and the fixed | ||||
|                   logcat buffer on the device is not big enough. The trade off is that | ||||
|                   this introduces some minor runtime overhead. Not set by default. | ||||
|                   """), | ||||
|         Parameter('enable_screen_check', kind=boolean, default=False, | ||||
|                   description=""" | ||||
|                   Specified whether the device should make sure that the screen is on | ||||
|                   during initialization. | ||||
|                   """), | ||||
|     ] | ||||
|  | ||||
|     default_timeout = 30 | ||||
|     delay = 2 | ||||
|     long_delay = 3 * delay | ||||
|     ready_timeout = 60 | ||||
|  | ||||
|     # Overwritten from Device. For documentation, see corresponding method in | ||||
|     # Device. | ||||
|  | ||||
|     @property | ||||
|     def is_rooted(self): | ||||
|         if self._is_rooted is None: | ||||
|             try: | ||||
|                 result = adb_shell(self.adb_name, 'su', timeout=1) | ||||
|                 if 'not found' in result: | ||||
|                     self._is_rooted = False | ||||
|                 else: | ||||
|                     self._is_rooted = True | ||||
|             except TimeoutError: | ||||
|                 self._is_rooted = True | ||||
|             except DeviceError: | ||||
|                 self._is_rooted = False | ||||
|         return self._is_rooted | ||||
|  | ||||
|     @property | ||||
|     def abi(self): | ||||
|         return self.getprop()['ro.product.cpu.abi'].split('-')[0] | ||||
|  | ||||
|     @property | ||||
|     def supported_eabi(self): | ||||
|         props = self.getprop() | ||||
|         result = [props['ro.product.cpu.abi']] | ||||
|         if 'ro.product.cpu.abi2' in props: | ||||
|             result.append(props['ro.product.cpu.abi2']) | ||||
|         if 'ro.product.cpu.abilist' in props: | ||||
|             for eabi in props['ro.product.cpu.abilist'].split(','): | ||||
|                 if eabi not in result: | ||||
|                     result.append(eabi) | ||||
|         return result | ||||
|  | ||||
|     def __init__(self, **kwargs): | ||||
|         super(AndroidDevice, self).__init__(**kwargs) | ||||
|         self._logcat_poller = None | ||||
|  | ||||
|     def reset(self): | ||||
|         self._is_ready = False | ||||
|         self._just_rebooted = True | ||||
|         adb_command(self.adb_name, 'reboot', timeout=self.default_timeout) | ||||
|  | ||||
|     def hard_reset(self): | ||||
|         super(AndroidDevice, self).hard_reset() | ||||
|         self._is_ready = False | ||||
|         self._just_rebooted = True | ||||
|  | ||||
|     def boot(self, **kwargs): | ||||
|         self.reset() | ||||
|  | ||||
|     def connect(self):  # NOQA pylint: disable=R0912 | ||||
|         iteration_number = 0 | ||||
|         max_iterations = self.ready_timeout / self.delay | ||||
|         available = False | ||||
|         self.logger.debug('Polling for device {}...'.format(self.adb_name)) | ||||
|         while iteration_number < max_iterations: | ||||
|             devices = adb_list_devices() | ||||
|             if self.adb_name: | ||||
|                 for device in devices: | ||||
|                     if device.name == self.adb_name and device.status != 'offline': | ||||
|                         available = True | ||||
|             else:  # adb_name not set | ||||
|                 if len(devices) == 1: | ||||
|                     available = True | ||||
|                 elif len(devices) > 1: | ||||
|                     raise DeviceError('More than one device is connected and adb_name is not set.') | ||||
|  | ||||
|             if available: | ||||
|                 break | ||||
|             else: | ||||
|                 time.sleep(self.delay) | ||||
|                 iteration_number += 1 | ||||
|         else: | ||||
|             raise DeviceError('Could not boot {} ({}).'.format(self.name, self.adb_name)) | ||||
|  | ||||
|         while iteration_number < max_iterations: | ||||
|             available = (1 == int('0' + adb_shell(self.adb_name, 'getprop sys.boot_completed', timeout=self.default_timeout))) | ||||
|             if available: | ||||
|                 break | ||||
|             else: | ||||
|                 time.sleep(self.delay) | ||||
|                 iteration_number += 1 | ||||
|         else: | ||||
|             raise DeviceError('Could not boot {} ({}).'.format(self.name, self.adb_name)) | ||||
|  | ||||
|         if self._just_rebooted: | ||||
|             self.logger.debug('Waiting for boot to complete...') | ||||
|             # On some devices, adb connection gets reset some time after booting. | ||||
|             # This  causes errors during execution. To prevent this, open a shell | ||||
|             # session and wait for it to be killed. Once its killed, give adb | ||||
|             # enough time to restart, and then the device should be ready. | ||||
|             # TODO: This is more of a work-around rather than an actual solution. | ||||
|             #       Need to figure out what is going on the "proper" way of handling it. | ||||
|             try: | ||||
|                 adb_shell(self.adb_name, '', timeout=20) | ||||
|                 time.sleep(5)  # give adb time to re-initialize | ||||
|             except TimeoutError: | ||||
|                 pass  # timed out waiting for the session to be killed -- assume not going to be. | ||||
|  | ||||
|             self.logger.debug('Boot completed.') | ||||
|             self._just_rebooted = False | ||||
|         self._is_ready = True | ||||
|  | ||||
|     def initialize(self, context, *args, **kwargs): | ||||
|         self.execute('mkdir -p {}'.format(self.working_directory)) | ||||
|         if self.is_rooted: | ||||
|             if not self.executable_is_installed('busybox'): | ||||
|                 self.busybox = self.deploy_busybox(context) | ||||
|             else: | ||||
|                 self.busybox = 'busybox' | ||||
|             self.disable_screen_lock() | ||||
|             self.disable_selinux() | ||||
|         if self.enable_screen_check: | ||||
|             self.ensure_screen_is_on() | ||||
|         self.init(context, *args, **kwargs) | ||||
|  | ||||
|     def disconnect(self): | ||||
|         if self._logcat_poller: | ||||
|             self._logcat_poller.close() | ||||
|  | ||||
|     def ping(self): | ||||
|         try: | ||||
|             # May be triggered inside initialize() | ||||
|             adb_shell(self.adb_name, 'ls /', timeout=10) | ||||
|         except (TimeoutError, CalledProcessError): | ||||
|             raise DeviceNotRespondingError(self.adb_name or self.name) | ||||
|  | ||||
|     def start(self): | ||||
|         if self.logcat_poll_period: | ||||
|             if self._logcat_poller: | ||||
|                 self._logcat_poller.close() | ||||
|             self._logcat_poller = _LogcatPoller(self, self.logcat_poll_period, timeout=self.default_timeout) | ||||
|             self._logcat_poller.start() | ||||
|  | ||||
|     def stop(self): | ||||
|         if self._logcat_poller: | ||||
|             self._logcat_poller.stop() | ||||
|  | ||||
|     def get_android_version(self): | ||||
|         return ANDROID_VERSION_MAP.get(self.get_sdk_version(), None) | ||||
|  | ||||
|     def get_android_id(self): | ||||
|         """ | ||||
|         Get the device's ANDROID_ID. Which is | ||||
|  | ||||
|             "A 64-bit number (as a hex string) that is randomly generated when the user | ||||
|             first sets up the device and should remain constant for the lifetime of the | ||||
|             user's device." | ||||
|  | ||||
|         .. note:: This will get reset on userdata erasure. | ||||
|  | ||||
|         """ | ||||
|         return self.execute('settings get secure android_id').strip() | ||||
|  | ||||
|     def get_sdk_version(self): | ||||
|         try: | ||||
|             return int(self.getprop('ro.build.version.sdk')) | ||||
|         except (ValueError, TypeError): | ||||
|             return None | ||||
|  | ||||
|     def get_installed_package_version(self, package): | ||||
|         """ | ||||
|         Returns the version (versionName) of the specified package if it is installed | ||||
|         on the device, or ``None`` otherwise. | ||||
|  | ||||
|         Added in version 2.1.4 | ||||
|  | ||||
|         """ | ||||
|         output = self.execute('dumpsys package {}'.format(package)) | ||||
|         for line in convert_new_lines(output).split('\n'): | ||||
|             if 'versionName' in line: | ||||
|                 return line.split('=', 1)[1] | ||||
|         return None | ||||
|  | ||||
|     def list_packages(self): | ||||
|         """ | ||||
|         List packages installed on the device. | ||||
|  | ||||
|         Added in version 2.1.4 | ||||
|  | ||||
|         """ | ||||
|         output = self.execute('pm list packages') | ||||
|         output = output.replace('package:', '') | ||||
|         return output.split() | ||||
|  | ||||
|     def package_is_installed(self, package_name): | ||||
|         """ | ||||
|         Returns ``True`` the if a package with the specified name is installed on | ||||
|         the device, and ``False`` otherwise. | ||||
|  | ||||
|         Added in version 2.1.4 | ||||
|  | ||||
|         """ | ||||
|         return package_name in self.list_packages() | ||||
|  | ||||
|     def executable_is_installed(self, executable_name): | ||||
|         return executable_name in self.listdir(self.binaries_directory) | ||||
|  | ||||
|     def is_installed(self, name): | ||||
|         return self.executable_is_installed(name) or self.package_is_installed(name) | ||||
|  | ||||
|     def listdir(self, path, as_root=False, **kwargs): | ||||
|         contents = self.execute('ls {}'.format(path), as_root=as_root) | ||||
|         return [x.strip() for x in contents.split()] | ||||
|  | ||||
|     def push_file(self, source, dest, as_root=False, timeout=default_timeout):  # pylint: disable=W0221 | ||||
|         """ | ||||
|         Modified in version 2.1.4: added  ``as_root`` parameter. | ||||
|  | ||||
|         """ | ||||
|         self._check_ready() | ||||
|         if not as_root: | ||||
|             adb_command(self.adb_name, "push '{}' '{}'".format(source, dest), timeout=timeout) | ||||
|         else: | ||||
|             device_tempfile = self.path.join(self.file_transfer_cache, source.lstrip(self.path.sep)) | ||||
|             self.execute('mkdir -p {}'.format(self.path.dirname(device_tempfile))) | ||||
|             adb_command(self.adb_name, "push '{}' '{}'".format(source, device_tempfile), timeout=timeout) | ||||
|             self.execute('cp {} {}'.format(device_tempfile, dest), as_root=True) | ||||
|  | ||||
|     def pull_file(self, source, dest, as_root=False, timeout=default_timeout):  # pylint: disable=W0221 | ||||
|         """ | ||||
|         Modified in version 2.1.4: added  ``as_root`` parameter. | ||||
|  | ||||
|         """ | ||||
|         self._check_ready() | ||||
|         if not as_root: | ||||
|             adb_command(self.adb_name, "pull '{}' '{}'".format(source, dest), timeout=timeout) | ||||
|         else: | ||||
|             device_tempfile = self.path.join(self.file_transfer_cache, source.lstrip(self.path.sep)) | ||||
|             self.execute('mkdir -p {}'.format(self.path.dirname(device_tempfile))) | ||||
|             self.execute('cp {} {}'.format(source, device_tempfile), as_root=True) | ||||
|             adb_command(self.adb_name, "pull '{}' '{}'".format(device_tempfile, dest), timeout=timeout) | ||||
|  | ||||
|     def delete_file(self, filepath, as_root=False):  # pylint: disable=W0221 | ||||
|         self._check_ready() | ||||
|         adb_shell(self.adb_name, "rm '{}'".format(filepath), as_root=as_root, timeout=self.default_timeout) | ||||
|  | ||||
|     def file_exists(self, filepath): | ||||
|         self._check_ready() | ||||
|         output = adb_shell(self.adb_name, 'if [ -e \'{}\' ]; then echo 1; else echo 0; fi'.format(filepath), | ||||
|                            timeout=self.default_timeout) | ||||
|         if int(output): | ||||
|             return True | ||||
|         else: | ||||
|             return False | ||||
|  | ||||
|     def install(self, filepath, timeout=default_timeout, with_name=None):  # pylint: disable=W0221 | ||||
|         ext = os.path.splitext(filepath)[1].lower() | ||||
|         if ext == '.apk': | ||||
|             return self.install_apk(filepath, timeout) | ||||
|         else: | ||||
|             return self.install_executable(filepath, with_name) | ||||
|  | ||||
|     def install_apk(self, filepath, timeout=default_timeout):  # pylint: disable=W0221 | ||||
|         self._check_ready() | ||||
|         ext = os.path.splitext(filepath)[1].lower() | ||||
|         if ext == '.apk': | ||||
|             return adb_command(self.adb_name, "install {}".format(filepath), timeout=timeout) | ||||
|         else: | ||||
|             raise DeviceError('Can\'t install {}: unsupported format.'.format(filepath)) | ||||
|  | ||||
|     def install_executable(self, filepath, with_name=None): | ||||
|         """ | ||||
|         Installs a binary executable on device. Requires root access. Returns | ||||
|         the path to the installed binary, or ``None`` if the installation has failed. | ||||
|         Optionally, ``with_name`` parameter may be used to specify a different name under | ||||
|         which the executable will be installed. | ||||
|  | ||||
|         Added in version 2.1.3. | ||||
|         Updated in version 2.1.5 with ``with_name`` parameter. | ||||
|  | ||||
|         """ | ||||
|         executable_name = with_name or os.path.basename(filepath) | ||||
|         on_device_file = self.path.join(self.working_directory, executable_name) | ||||
|         on_device_executable = self.path.join(self.binaries_directory, executable_name) | ||||
|         self.push_file(filepath, on_device_file) | ||||
|         matched = [] | ||||
|         for entry in self.list_file_systems(): | ||||
|             if self.binaries_directory.rstrip('/').startswith(entry.mount_point): | ||||
|                 matched.append(entry) | ||||
|  | ||||
|         if matched: | ||||
|             entry = sorted(matched, key=lambda x: len(x.mount_point))[-1] | ||||
|             if 'rw' not in entry.options: | ||||
|                 self.execute('mount -o rw,remount {} {}'.format(entry.device, entry.mount_point), as_root=True) | ||||
|             self.execute('cp {} {}'.format(on_device_file, on_device_executable), as_root=True) | ||||
|             self.execute('chmod 0777 {}'.format(on_device_executable), as_root=True) | ||||
|             return on_device_executable | ||||
|         else: | ||||
|             raise DeviceError('Could not find mount point for binaries directory {}'.format(self.binaries_directory)) | ||||
|  | ||||
|     def uninstall(self, package): | ||||
|         self._check_ready() | ||||
|         adb_command(self.adb_name, "uninstall {}".format(package), timeout=self.default_timeout) | ||||
|  | ||||
|     def uninstall_executable(self, executable_name): | ||||
|         """ | ||||
|         Requires root access. | ||||
|  | ||||
|         Added in version 2.1.3. | ||||
|  | ||||
|         """ | ||||
|         on_device_executable = self.path.join(self.binaries_directory, executable_name) | ||||
|         for entry in self.list_file_systems(): | ||||
|             if entry.mount_point == '/system': | ||||
|                 if 'rw' not in entry.options: | ||||
|                     self.execute('mount -o rw,remount {} /system'.format(entry.device), as_root=True) | ||||
|         self.delete_file(on_device_executable) | ||||
|  | ||||
|     def execute(self, command, timeout=default_timeout, check_exit_code=True, background=False, | ||||
|                 as_root=False, busybox=False, **kwargs): | ||||
|         """ | ||||
|         Execute the specified command on the device using adb. | ||||
|  | ||||
|         Parameters: | ||||
|  | ||||
|             :param command: The command to be executed. It should appear exactly | ||||
|                             as if you were typing it into a shell. | ||||
|             :param timeout: Time, in seconds, to wait for adb to return before aborting | ||||
|                             and raising an error. Defaults to ``AndroidDevice.default_timeout``. | ||||
|             :param check_exit_code: If ``True``, the return code of the command on the Device will | ||||
|                                     be check and exception will be raised if it is not 0. | ||||
|                                     Defaults to ``True``. | ||||
|             :param background: If ``True``, will execute adb in a subprocess, and will return | ||||
|                                immediately, not waiting for adb to return. Defaults to ``False`` | ||||
|             :param busybox: If ``True``, will use busybox to execute the command. Defaults to ``False``. | ||||
|  | ||||
|                             Added in version 2.1.3 | ||||
|  | ||||
|                             .. note:: The device must be rooted to be able to use busybox. | ||||
|  | ||||
|             :param as_root: If ``True``, will attempt to execute command in privileged mode. The device | ||||
|                             must be rooted, otherwise an error will be raised. Defaults to ``False``. | ||||
|  | ||||
|                             Added in version 2.1.3 | ||||
|  | ||||
|         :returns: If ``background`` parameter is set to ``True``, the subprocess object will | ||||
|                   be returned; otherwise, the contents of STDOUT from the device will be returned. | ||||
|  | ||||
|         :raises: DeviceError if adb timed out  or if the command returned non-zero exit | ||||
|                  code on the device, or if attempting to execute a command in privileged mode on an | ||||
|                  unrooted device. | ||||
|  | ||||
|         """ | ||||
|         self._check_ready() | ||||
|         if as_root and not self.is_rooted: | ||||
|             raise DeviceError('Attempting to execute "{}" as root on unrooted device.'.format(command)) | ||||
|         if busybox: | ||||
|             if not self.is_rooted: | ||||
|                 DeviceError('Attempting to execute "{}" with busybox. '.format(command) + | ||||
|                             'Busybox can only be deployed to rooted devices.') | ||||
|             command = ' '.join([self.busybox, command]) | ||||
|         if background: | ||||
|             return adb_background_shell(self.adb_name, command, as_root=as_root) | ||||
|         else: | ||||
|             return adb_shell(self.adb_name, command, timeout, check_exit_code, as_root) | ||||
|  | ||||
|     def kick_off(self, command): | ||||
|         """ | ||||
|         Like execute but closes adb session and returns immediately, leaving the command running on the | ||||
|         device (this is different from execute(background=True) which keeps adb connection open and returns | ||||
|         a subprocess object). | ||||
|  | ||||
|         .. note:: This relies on busybox's nohup applet and so won't work on unrooted devices. | ||||
|  | ||||
|         Added in version 2.1.4 | ||||
|  | ||||
|         """ | ||||
|         if not self.is_rooted: | ||||
|             raise DeviceError('kick_off uses busybox\'s nohup applet and so can only be run a rooted device.') | ||||
|         try: | ||||
|             command = 'cd {} && busybox nohup {}'.format(self.working_directory, command) | ||||
|             output = self.execute(command, timeout=1, as_root=True) | ||||
|         except TimeoutError: | ||||
|             pass | ||||
|         else: | ||||
|             raise ValueError('Background command exited before timeout; got "{}"'.format(output)) | ||||
|  | ||||
|     def get_properties(self, context): | ||||
|         """Captures and saves the information from /system/build.prop and /proc/version""" | ||||
|         props = {} | ||||
|         props['android_id'] = self.get_android_id() | ||||
|         buildprop_file = os.path.join(context.host_working_directory, 'build.prop') | ||||
|         if not os.path.isfile(buildprop_file): | ||||
|             self.pull_file('/system/build.prop', context.host_working_directory) | ||||
|         self._update_build_properties(buildprop_file, props) | ||||
|         context.add_run_artifact('build_properties', buildprop_file, 'export') | ||||
|  | ||||
|         version_file = os.path.join(context.host_working_directory, 'version') | ||||
|         if not os.path.isfile(version_file): | ||||
|             self.pull_file('/proc/version', context.host_working_directory) | ||||
|         self._update_versions(version_file, props) | ||||
|         context.add_run_artifact('device_version', version_file, 'export') | ||||
|         return props | ||||
|  | ||||
|     def getprop(self, prop=None): | ||||
|         """Returns parsed output of Android getprop command. If a property is | ||||
|         specified, only the value for that property will be returned (with | ||||
|         ``None`` returned if the property doesn't exist. Otherwise, | ||||
|         ``wlauto.utils.android.AndroidProperties`` will be returned, which is | ||||
|         a dict-like object.""" | ||||
|         props = AndroidProperties(self.execute('getprop')) | ||||
|         if prop: | ||||
|             return props[prop] | ||||
|         return props | ||||
|  | ||||
|     # Android-specific methods. These either rely on specifics of adb or other | ||||
|     # Android-only concepts in their interface and/or implementation. | ||||
|  | ||||
|     def forward_port(self, from_port, to_port): | ||||
|         """ | ||||
|         Forward a port on the device to a port on localhost. | ||||
|  | ||||
|         :param from_port: Port on the device which to forward. | ||||
|         :param to_port: Port on the localhost to which the device port will be forwarded. | ||||
|  | ||||
|         Ports should be specified using adb spec. See the "adb forward" section in "adb help". | ||||
|  | ||||
|         """ | ||||
|         adb_command(self.adb_name, 'forward {} {}'.format(from_port, to_port), timeout=self.default_timeout) | ||||
|  | ||||
|     def dump_logcat(self, outfile, filter_spec=None): | ||||
|         """ | ||||
|         Dump the contents of logcat, for the specified filter spec to the | ||||
|         specified output file. | ||||
|         See http://developer.android.com/tools/help/logcat.html | ||||
|  | ||||
|         :param outfile: Output file on the host into which the contents of the | ||||
|                         log will be written. | ||||
|         :param filter_spec: Logcat filter specification. | ||||
|                             see http://developer.android.com/tools/debugging/debugging-log.html#filteringOutput | ||||
|  | ||||
|         """ | ||||
|         if self._logcat_poller: | ||||
|             return self._logcat_poller.write_log(outfile) | ||||
|         else: | ||||
|             if filter_spec: | ||||
|                 command = 'logcat -d -s {} > {}'.format(filter_spec, outfile) | ||||
|             else: | ||||
|                 command = 'logcat -d > {}'.format(outfile) | ||||
|             return adb_command(self.adb_name, command, timeout=self.default_timeout) | ||||
|  | ||||
|     def clear_logcat(self): | ||||
|         """Clear (flush) logcat log.""" | ||||
|         if self._logcat_poller: | ||||
|             return self._logcat_poller.clear_buffer() | ||||
|         else: | ||||
|             return adb_shell(self.adb_name, 'logcat -c', timeout=self.default_timeout) | ||||
|  | ||||
|     def capture_screen(self, filepath): | ||||
|         """Caputers the current device screen into the specified file in a PNG format.""" | ||||
|         on_device_file = self.path.join(self.working_directory, 'screen_capture.png') | ||||
|         self.execute('screencap -p  {}'.format(on_device_file)) | ||||
|         self.pull_file(on_device_file, filepath) | ||||
|         self.delete_file(on_device_file) | ||||
|  | ||||
|     def is_screen_on(self): | ||||
|         """Returns ``True`` if the device screen is currently on, ``False`` otherwise.""" | ||||
|         output = self.execute('dumpsys power') | ||||
|         match = SCREEN_STATE_REGEX.search(output) | ||||
|         if match: | ||||
|             return boolean(match.group(1)) | ||||
|         else: | ||||
|             raise DeviceError('Could not establish screen state.') | ||||
|  | ||||
|     def ensure_screen_is_on(self): | ||||
|         if not self.is_screen_on(): | ||||
|             self.execute('input keyevent 26') | ||||
|  | ||||
|     def disable_screen_lock(self): | ||||
|         """ | ||||
|         Attempts to disable he screen lock on the device. | ||||
|  | ||||
|         .. note:: This does not always work... | ||||
|  | ||||
|         Added inversion 2.1.4 | ||||
|  | ||||
|         """ | ||||
|         lockdb = '/data/system/locksettings.db' | ||||
|         sqlcommand = "update locksettings set value=\\'0\\' where name=\\'screenlock.disabled\\';" | ||||
|         self.execute('sqlite3 {} "{}"'.format(lockdb, sqlcommand), as_root=True) | ||||
|  | ||||
|     def disable_selinux(self): | ||||
|         # This may be invoked from intialize() so we can't use execute() or the | ||||
|         # standard API for doing this. | ||||
|         api_level = int(adb_shell(self.adb_name, 'getprop ro.build.version.sdk', | ||||
|                                   timeout=self.default_timeout).strip()) | ||||
|         # SELinux was added in Android 4.3 (API level 18). Trying to | ||||
|         # 'getenforce' in earlier versions will produce an error. | ||||
|         if api_level >= 18: | ||||
|             se_status = self.execute('getenforce', as_root=True).strip() | ||||
|             if se_status == 'Enforcing': | ||||
|                 self.execute('setenforce 0', as_root=True) | ||||
|  | ||||
|     # Internal methods: do not use outside of the class. | ||||
|  | ||||
|     def _update_build_properties(self, filepath, props): | ||||
|         try: | ||||
|             with open(filepath) as fh: | ||||
|                 for line in fh: | ||||
|                     line = re.sub(r'#.*', '', line).strip() | ||||
|                     if not line: | ||||
|                         continue | ||||
|                     key, value = line.split('=', 1) | ||||
|                     props[key] = value | ||||
|         except ValueError: | ||||
|             self.logger.warning('Could not parse build.prop.') | ||||
|  | ||||
|     def _update_versions(self, filepath, props): | ||||
|         with open(filepath) as fh: | ||||
|             text = fh.read() | ||||
|             props['version'] = text | ||||
|             text = re.sub(r'#.*', '', text).strip() | ||||
|             match = re.search(r'^(Linux version .*?)\s*\((gcc version .*)\)$', text) | ||||
|             if match: | ||||
|                 props['linux_version'] = match.group(1).strip() | ||||
|                 props['gcc_version'] = match.group(2).strip() | ||||
|             else: | ||||
|                 self.logger.warning('Could not parse version string.') | ||||
|  | ||||
|  | ||||
| class _LogcatPoller(threading.Thread): | ||||
|  | ||||
|     join_timeout = 5 | ||||
|  | ||||
|     def __init__(self, device, period, timeout=None): | ||||
|         super(_LogcatPoller, self).__init__() | ||||
|         self.adb_device = device.adb_name | ||||
|         self.logger = device.logger | ||||
|         self.period = period | ||||
|         self.timeout = timeout | ||||
|         self.stop_signal = threading.Event() | ||||
|         self.lock = threading.RLock() | ||||
|         self.buffer_file = tempfile.mktemp() | ||||
|         self.last_poll = 0 | ||||
|         self.daemon = True | ||||
|         self.exc = None | ||||
|  | ||||
|     def run(self): | ||||
|         self.logger.debug('Starting logcat polling.') | ||||
|         try: | ||||
|             while True: | ||||
|                 if self.stop_signal.is_set(): | ||||
|                     break | ||||
|                 with self.lock: | ||||
|                     current_time = time.time() | ||||
|                     if (current_time - self.last_poll) >= self.period: | ||||
|                         self._poll() | ||||
|                 time.sleep(0.5) | ||||
|         except Exception:  # pylint: disable=W0703 | ||||
|             self.exc = WorkerThreadError(self.name, sys.exc_info()) | ||||
|         self.logger.debug('Logcat polling stopped.') | ||||
|  | ||||
|     def stop(self): | ||||
|         self.logger.debug('Stopping logcat polling.') | ||||
|         self.stop_signal.set() | ||||
|         self.join(self.join_timeout) | ||||
|         if self.is_alive(): | ||||
|             self.logger.error('Could not join logcat poller thread.') | ||||
|         if self.exc: | ||||
|             raise self.exc  # pylint: disable=E0702 | ||||
|  | ||||
|     def clear_buffer(self): | ||||
|         self.logger.debug('Clearing logcat buffer.') | ||||
|         with self.lock: | ||||
|             adb_shell(self.adb_device, 'logcat -c', timeout=self.timeout) | ||||
|             with open(self.buffer_file, 'w') as _:  # NOQA | ||||
|                 pass | ||||
|  | ||||
|     def write_log(self, outfile): | ||||
|         self.logger.debug('Writing logbuffer to {}.'.format(outfile)) | ||||
|         with self.lock: | ||||
|             self._poll() | ||||
|             if os.path.isfile(self.buffer_file): | ||||
|                 shutil.copy(self.buffer_file, outfile) | ||||
|             else:  # there was no logcat trace at this time | ||||
|                 with open(outfile, 'w') as _:  # NOQA | ||||
|                     pass | ||||
|  | ||||
|     def close(self): | ||||
|         self.logger.debug('Closing logcat poller.') | ||||
|         if os.path.isfile(self.buffer_file): | ||||
|             os.remove(self.buffer_file) | ||||
|  | ||||
|     def _poll(self): | ||||
|         with self.lock: | ||||
|             self.last_poll = time.time() | ||||
|             adb_command(self.adb_device, 'logcat -d >> {}'.format(self.buffer_file), timeout=self.timeout) | ||||
|             adb_command(self.adb_device, 'logcat -c', timeout=self.timeout) | ||||
|  | ||||
|  | ||||
| class BigLittleDevice(AndroidDevice):  # pylint: disable=W0223 | ||||
|  | ||||
|     parameters = [ | ||||
|         Parameter('scheduler', default='hmp', override=True), | ||||
|     ] | ||||
|  | ||||
							
								
								
									
										36
									
								
								wlauto/common/android/resources.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								wlauto/common/android/resources.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| #    Copyright 2014-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
| from wlauto.common.resources import FileResource | ||||
|  | ||||
|  | ||||
| class ReventFile(FileResource): | ||||
|  | ||||
|     name = 'revent' | ||||
|  | ||||
|     def __init__(self, owner, stage): | ||||
|         super(ReventFile, self).__init__(owner) | ||||
|         self.stage = stage | ||||
|  | ||||
|  | ||||
| class JarFile(FileResource): | ||||
|  | ||||
|     name = 'jar' | ||||
|  | ||||
|  | ||||
| class ApkFile(FileResource): | ||||
|  | ||||
|     name = 'apk' | ||||
							
								
								
									
										425
									
								
								wlauto/common/android/workload.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										425
									
								
								wlauto/common/android/workload.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,425 @@ | ||||
| #    Copyright 2013-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
| import os | ||||
| import sys | ||||
| import time | ||||
|  | ||||
| from wlauto.core.extension import Parameter | ||||
| from wlauto.core.workload import Workload | ||||
| from wlauto.core.resource import NO_ONE | ||||
| from wlauto.common.resources import ExtensionAsset, Executable | ||||
| from wlauto.exceptions import WorkloadError, ResourceError | ||||
| from wlauto.utils.android import ApkInfo | ||||
| from wlauto.utils.types import boolean | ||||
| import wlauto.common.android.resources | ||||
|  | ||||
|  | ||||
| DELAY = 5 | ||||
|  | ||||
|  | ||||
| class UiAutomatorWorkload(Workload): | ||||
|     """ | ||||
|     Base class for all workloads that rely on a UI Automator JAR file. | ||||
|  | ||||
|     This class should be subclassed by workloads that rely on android UiAutomator | ||||
|     to work. This class handles transferring the UI Automator JAR file to the device | ||||
|     and invoking it to run the workload. By default, it will look for the JAR file in | ||||
|     the same directory as the .py file for the workload (this can be changed by overriding | ||||
|     the ``uiauto_file`` property in the subclassing workload). | ||||
|  | ||||
|     To inintiate UI Automation, the fully-qualified name of the Java class and the | ||||
|     corresponding method name are needed. By default, the package part of the class name | ||||
|     is derived from the class file, and class and method names are ``UiAutomation`` | ||||
|     and ``runUiAutomaton`` respectively. If you have generated the boilder plate for the | ||||
|     UiAutomatior code using ``create_workloads`` utility, then everything should be named | ||||
|     correctly. If you're creating the Java project manually, you need to make sure the names | ||||
|     match what is expected, or you could override ``uiauto_package``, ``uiauto_class`` and | ||||
|     ``uiauto_method`` class attributes with the value that match your Java code. | ||||
|  | ||||
|     You can also pass parameters to the JAR file. To do this add the parameters to | ||||
|     ``self.uiauto_params`` dict inside your class's ``__init__`` or ``setup`` methods. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     supported_platforms = ['android'] | ||||
|  | ||||
|     uiauto_package = '' | ||||
|     uiauto_class = 'UiAutomation' | ||||
|     uiauto_method = 'runUiAutomation' | ||||
|  | ||||
|     # Can be overidden by subclasses to adjust to run time of specific | ||||
|     # benchmarks. | ||||
|     run_timeout = 4 * 60  # seconds | ||||
|  | ||||
|     def __init__(self, device, _call_super=True, **kwargs):  # pylint: disable=W0613 | ||||
|         if _call_super: | ||||
|             super(UiAutomatorWorkload, self).__init__(device, **kwargs) | ||||
|         self.uiauto_file = None | ||||
|         self.device_uiauto_file = None | ||||
|         self.command = None | ||||
|         self.uiauto_params = {} | ||||
|  | ||||
|     def init_resources(self, context): | ||||
|         self.uiauto_file = context.resolver.get(wlauto.common.android.resources.JarFile(self)) | ||||
|         if not self.uiauto_file: | ||||
|             raise ResourceError('No UI automation JAR file found for workload {}.'.format(self.name)) | ||||
|         self.device_uiauto_file = self.device.path.join(self.device.working_directory, | ||||
|                                                         os.path.basename(self.uiauto_file)) | ||||
|         if not self.uiauto_package: | ||||
|             self.uiauto_package = os.path.splitext(os.path.basename(self.uiauto_file))[0] | ||||
|  | ||||
|     def setup(self, context): | ||||
|         method_string = '{}.{}#{}'.format(self.uiauto_package, self.uiauto_class, self.uiauto_method) | ||||
|         params_dict = self.uiauto_params | ||||
|         params_dict['workdir'] = self.device.working_directory | ||||
|         params = '' | ||||
|         for k, v in self.uiauto_params.iteritems(): | ||||
|             params += ' -e {} {}'.format(k, v) | ||||
|         self.command = 'uiautomator runtest {}{} -c {}'.format(self.device_uiauto_file, params, method_string) | ||||
|         self.device.push_file(self.uiauto_file, self.device_uiauto_file) | ||||
|         self.device.killall('uiautomator') | ||||
|  | ||||
|     def run(self, context): | ||||
|         result = self.device.execute(self.command, self.run_timeout) | ||||
|         if 'FAILURE' in result: | ||||
|             raise WorkloadError(result) | ||||
|         else: | ||||
|             self.logger.debug(result) | ||||
|         time.sleep(DELAY) | ||||
|  | ||||
|     def update_result(self, context): | ||||
|         pass | ||||
|  | ||||
|     def teardown(self, context): | ||||
|         self.device.delete_file(self.device_uiauto_file) | ||||
|  | ||||
|     def validate(self): | ||||
|         if not self.uiauto_file: | ||||
|             raise WorkloadError('No UI automation JAR file found for workload {}.'.format(self.name)) | ||||
|         if not self.uiauto_package: | ||||
|             raise WorkloadError('No UI automation package specified for workload {}.'.format(self.name)) | ||||
|  | ||||
|  | ||||
| class ApkWorkload(Workload): | ||||
|     """ | ||||
|     A workload based on an APK file. | ||||
|  | ||||
|     Defines the following attributes: | ||||
|  | ||||
|     :package: The package name of the app. This is usually a Java-style name of the form | ||||
|               ``com.companyname.appname``. | ||||
|     :activity: This is the initial activity of the app. This will be used to launch the | ||||
|                app during the setup. | ||||
|     :view: The class of the main view pane of the app. This needs to be defined in order | ||||
|            to collect SurfaceFlinger-derived statistics (such as FPS) for the app, but | ||||
|            may otherwise be left as ``None``. | ||||
|     :install_timeout: Timeout for the installation of the APK. This may vary wildly based on | ||||
|                       the size and nature of a specific APK, and so should be defined on | ||||
|                       per-workload basis. | ||||
|  | ||||
|                       .. note:: To a lesser extent, this will also vary based on the the | ||||
|                                 device and the nature of adb connection (USB vs Ethernet), | ||||
|                                 so, as with all timeouts, so leeway must be included in | ||||
|                                 the specified value. | ||||
|  | ||||
|     .. note:: Both package and activity for a workload may be obtained from the APK using | ||||
|               the ``aapt`` tool that comes with the ADT  (Android Developemnt Tools) bundle. | ||||
|  | ||||
|     """ | ||||
|     package = None | ||||
|     activity = None | ||||
|     view = None | ||||
|     install_timeout = None | ||||
|     default_install_timeout = 300 | ||||
|  | ||||
|     parameters = [ | ||||
|         Parameter('uninstall_apk', kind=boolean, default=False, | ||||
|                   description="If ``True``, will uninstall workload's APK as part of teardown."), | ||||
|     ] | ||||
|  | ||||
|     def __init__(self, device, _call_super=True, **kwargs): | ||||
|         if _call_super: | ||||
|             super(ApkWorkload, self).__init__(device, **kwargs) | ||||
|         self.apk_file = None | ||||
|         self.apk_version = None | ||||
|         self.logcat_log = None | ||||
|         self.force_reinstall = kwargs.get('force_reinstall', False) | ||||
|         if not self.install_timeout: | ||||
|             self.install_timeout = self.default_install_timeout | ||||
|  | ||||
|     def init_resources(self, context): | ||||
|         self.apk_file = context.resolver.get(wlauto.common.android.resources.ApkFile(self), version=getattr(self, 'version', None)) | ||||
|  | ||||
|     def setup(self, context): | ||||
|         self.initialize_package(context) | ||||
|         self.start_activity() | ||||
|         self.device.execute('am kill-all')  # kill all *background* activities | ||||
|         self.device.clear_logcat() | ||||
|  | ||||
|     def initialize_package(self, context): | ||||
|         installed_version = self.device.get_installed_package_version(self.package) | ||||
|         host_version = ApkInfo(self.apk_file).version_name | ||||
|         if installed_version != host_version: | ||||
|             if installed_version: | ||||
|                 message = '{} host version: {}, device version: {}; re-installing...' | ||||
|                 self.logger.debug(message.format(os.path.basename(self.apk_file), host_version, installed_version)) | ||||
|             else: | ||||
|                 message = '{} host version: {}, not found on device; installing...' | ||||
|                 self.logger.debug(message.format(os.path.basename(self.apk_file), host_version)) | ||||
|             self.force_reinstall = True | ||||
|         else: | ||||
|             message = '{} version {} found on both device and host.' | ||||
|             self.logger.debug(message.format(os.path.basename(self.apk_file), host_version)) | ||||
|         if self.force_reinstall: | ||||
|             if installed_version: | ||||
|                 self.device.uninstall(self.package) | ||||
|             self.install_apk(context) | ||||
|         else: | ||||
|             self.reset(context) | ||||
|         self.apk_version = host_version | ||||
|  | ||||
|     def start_activity(self): | ||||
|         output = self.device.execute('am start -W -n {}/{}'.format(self.package, self.activity)) | ||||
|         if 'Error:' in output: | ||||
|             self.device.execute('am force-stop {}'.format(self.package))  # this will dismiss any erro dialogs | ||||
|             raise WorkloadError(output) | ||||
|         self.logger.debug(output) | ||||
|  | ||||
|     def reset(self, context):  # pylint: disable=W0613 | ||||
|         self.device.execute('am force-stop {}'.format(self.package)) | ||||
|         self.device.execute('pm clear {}'.format(self.package)) | ||||
|  | ||||
|     def install_apk(self, context): | ||||
|         output = self.device.install(self.apk_file, self.install_timeout) | ||||
|         if 'Failure' in output: | ||||
|             if 'ALREADY_EXISTS' in output: | ||||
|                 self.logger.warn('Using already installed APK (did not unistall properly?)') | ||||
|             else: | ||||
|                 raise WorkloadError(output) | ||||
|         else: | ||||
|             self.logger.debug(output) | ||||
|         self.do_post_install(context) | ||||
|  | ||||
|     def do_post_install(self, context): | ||||
|         """ May be overwritten by dervied classes.""" | ||||
|         pass | ||||
|  | ||||
|     def run(self, context): | ||||
|         pass | ||||
|  | ||||
|     def update_result(self, context): | ||||
|         self.logcat_log = os.path.join(context.output_directory, 'logcat.log') | ||||
|         self.device.dump_logcat(self.logcat_log) | ||||
|         context.add_iteration_artifact(name='logcat', | ||||
|                                        path='logcat.log', | ||||
|                                        kind='log', | ||||
|                                        description='Logact dump for the run.') | ||||
|  | ||||
|     def teardown(self, context): | ||||
|         self.device.execute('am force-stop {}'.format(self.package)) | ||||
|         if self.uninstall_apk: | ||||
|             self.device.uninstall(self.package) | ||||
|  | ||||
|     def validate(self): | ||||
|         if not self.apk_file: | ||||
|             raise WorkloadError('No APK file found for workload {}.'.format(self.name)) | ||||
|  | ||||
|  | ||||
| AndroidBenchmark = ApkWorkload  # backward compatibility | ||||
|  | ||||
|  | ||||
| class ReventWorkload(Workload): | ||||
|  | ||||
|     default_setup_timeout = 5 * 60  # in seconds | ||||
|     default_run_timeout = 10 * 60  # in seconds | ||||
|  | ||||
|     def __init__(self, device, _call_super=True, **kwargs): | ||||
|         if _call_super: | ||||
|             super(ReventWorkload, self).__init__(device, **kwargs) | ||||
|         devpath = self.device.path | ||||
|         self.on_device_revent_binary = devpath.join(self.device.working_directory, 'revent') | ||||
|         self.on_device_setup_revent = devpath.join(self.device.working_directory, '{}.setup.revent'.format(self.device.name)) | ||||
|         self.on_device_run_revent = devpath.join(self.device.working_directory, '{}.run.revent'.format(self.device.name)) | ||||
|         self.setup_timeout = kwargs.get('setup_timeout', self.default_setup_timeout) | ||||
|         self.run_timeout = kwargs.get('run_timeout', self.default_run_timeout) | ||||
|         self.revent_setup_file = None | ||||
|         self.revent_run_file = None | ||||
|  | ||||
|     def init_resources(self, context): | ||||
|         self.revent_setup_file = context.resolver.get(wlauto.common.android.resources.ReventFile(self, 'setup')) | ||||
|         self.revent_run_file = context.resolver.get(wlauto.common.android.resources.ReventFile(self, 'run')) | ||||
|  | ||||
|     def setup(self, context): | ||||
|         self._check_revent_files(context) | ||||
|         self.device.killall('revent') | ||||
|         command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_setup_revent) | ||||
|         self.device.execute(command, timeout=self.setup_timeout) | ||||
|  | ||||
|     def run(self, context): | ||||
|         command = '{} replay {}'.format(self.on_device_revent_binary, self.on_device_run_revent) | ||||
|         self.logger.debug('Replaying {}'.format(os.path.basename(self.on_device_run_revent))) | ||||
|         self.device.execute(command, timeout=self.run_timeout) | ||||
|         self.logger.debug('Replay completed.') | ||||
|  | ||||
|     def update_result(self, context): | ||||
|         pass | ||||
|  | ||||
|     def teardown(self, context): | ||||
|         self.device.delete_file(self.on_device_setup_revent) | ||||
|         self.device.delete_file(self.on_device_run_revent) | ||||
|  | ||||
|     def _check_revent_files(self, context): | ||||
|         # check the revent binary | ||||
|         revent_binary = context.resolver.get(Executable(NO_ONE, self.device.abi, 'revent')) | ||||
|         if not os.path.isfile(revent_binary): | ||||
|             message = '{} does not exist. '.format(revent_binary) | ||||
|             message += 'Please build revent for your system and place it in that location' | ||||
|             raise WorkloadError(message) | ||||
|         if not self.revent_setup_file: | ||||
|             # pylint: disable=too-few-format-args | ||||
|             message = '{0}.setup.revent file does not exist, Please provide one for your device, {0}'.format(self.device.name) | ||||
|             raise WorkloadError(message) | ||||
|         if not self.revent_run_file: | ||||
|             # pylint: disable=too-few-format-args | ||||
|             message = '{0}.run.revent file does not exist, Please provide one for your device, {0}'.format(self.device.name) | ||||
|             raise WorkloadError(message) | ||||
|  | ||||
|         self.on_device_revent_binary = self.device.install_executable(revent_binary) | ||||
|         self.device.push_file(self.revent_run_file, self.on_device_run_revent) | ||||
|         self.device.push_file(self.revent_setup_file, self.on_device_setup_revent) | ||||
|  | ||||
|  | ||||
| class AndroidUiAutoBenchmark(UiAutomatorWorkload, AndroidBenchmark): | ||||
|  | ||||
|     def __init__(self, device, **kwargs): | ||||
|         UiAutomatorWorkload.__init__(self, device, **kwargs) | ||||
|         AndroidBenchmark.__init__(self, device, _call_super=False, **kwargs) | ||||
|  | ||||
|     def init_resources(self, context): | ||||
|         UiAutomatorWorkload.init_resources(self, context) | ||||
|         AndroidBenchmark.init_resources(self, context) | ||||
|  | ||||
|     def setup(self, context): | ||||
|         UiAutomatorWorkload.setup(self, context) | ||||
|         AndroidBenchmark.setup(self, context) | ||||
|  | ||||
|     def update_result(self, context): | ||||
|         UiAutomatorWorkload.update_result(self, context) | ||||
|         AndroidBenchmark.update_result(self, context) | ||||
|  | ||||
|     def teardown(self, context): | ||||
|         UiAutomatorWorkload.teardown(self, context) | ||||
|         AndroidBenchmark.teardown(self, context) | ||||
|  | ||||
|  | ||||
| class GameWorkload(ApkWorkload, ReventWorkload): | ||||
|     """ | ||||
|     GameWorkload is the base class for all the workload that use revent files to | ||||
|     run. | ||||
|  | ||||
|     For more in depth details on how to record revent files, please see | ||||
|     :ref:`revent_files_creation`. To subclass this class, please refer to | ||||
|     :ref:`GameWorkload`. | ||||
|  | ||||
|     Additionally, this class defines the following attributes: | ||||
|  | ||||
|     :asset_file: A tarball containing additional assets for the workload. These are the assets | ||||
|                  that are not part of the APK but would need to be downloaded by the workload | ||||
|                  (usually, on first run of the app). Since the presence of a network connection | ||||
|                  cannot be assumed on some devices, this provides an alternative means of obtaining | ||||
|                  the assets. | ||||
|     :saved_state_file: A tarball containing the saved state for a workload. This tarball gets | ||||
|                        deployed in the same way as the asset file. The only difference being that | ||||
|                        it is usually much slower and re-deploying the tarball should alone be | ||||
|                        enough to reset the workload to a known state (without having to reinstall | ||||
|                        the app or re-deploy the other assets). | ||||
|     :loading_time: Time it takes for the workload to load after the initial activity has been | ||||
|                    started. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     # May be optionally overwritten by subclasses | ||||
|     asset_file = None | ||||
|     saved_state_file = None | ||||
|     view = 'SurfaceView' | ||||
|     install_timeout = 500 | ||||
|     loading_time = 10 | ||||
|  | ||||
|     def __init__(self, device, **kwargs):  # pylint: disable=W0613 | ||||
|         ApkWorkload.__init__(self, device, **kwargs) | ||||
|         ReventWorkload.__init__(self, device, _call_super=False, **kwargs) | ||||
|         self.logcat_process = None | ||||
|         self.module_dir = os.path.dirname(sys.modules[self.__module__].__file__) | ||||
|         self.revent_dir = os.path.join(self.module_dir, 'revent_files') | ||||
|  | ||||
|     def init_resources(self, context): | ||||
|         ApkWorkload.init_resources(self, context) | ||||
|         ReventWorkload.init_resources(self, context) | ||||
|  | ||||
|     def setup(self, context): | ||||
|         ApkWorkload.setup(self, context) | ||||
|         self.logger.debug('Waiting for the game to load...') | ||||
|         time.sleep(self.loading_time) | ||||
|         ReventWorkload.setup(self, context) | ||||
|  | ||||
|     def do_post_install(self, context): | ||||
|         ApkWorkload.do_post_install(self, context) | ||||
|         self._deploy_assets(context) | ||||
|  | ||||
|     def reset(self, context): | ||||
|         # If saved state exists, restore it; if not, do full | ||||
|         # uninstall/install cycle. | ||||
|         if self.saved_state_file: | ||||
|             self._deploy_resource_tarball(context, self.saved_state_file) | ||||
|         else: | ||||
|             ApkWorkload.reset(self, context) | ||||
|             self._deploy_assets(context) | ||||
|  | ||||
|     def run(self, context): | ||||
|         ReventWorkload.run(self, context) | ||||
|  | ||||
|     def teardown(self, context): | ||||
|         if not self.saved_state_file: | ||||
|             ApkWorkload.teardown(self, context) | ||||
|         else: | ||||
|             self.device.execute('am force-stop {}'.format(self.package)) | ||||
|         ReventWorkload.teardown(self, context) | ||||
|  | ||||
|     def _deploy_assets(self, context, timeout=300): | ||||
|         if self.asset_file: | ||||
|             self._deploy_resource_tarball(context, self.asset_file, timeout) | ||||
|         if self.saved_state_file:  # must be deployed *after* asset tarball! | ||||
|             self._deploy_resource_tarball(context, self.saved_state_file, timeout) | ||||
|  | ||||
|     def _deploy_resource_tarball(self, context, resource_file, timeout=300): | ||||
|         kind = 'data' | ||||
|         if ':' in resource_file: | ||||
|             kind, resource_file = resource_file.split(':', 1) | ||||
|         ondevice_cache = self.device.path.join(self.device.resource_cache, self.name, resource_file) | ||||
|         if not self.device.file_exists(ondevice_cache): | ||||
|             asset_tarball = context.resolver.get(ExtensionAsset(self, resource_file)) | ||||
|             if not asset_tarball: | ||||
|                 message = 'Could not find resource {} for workload {}.' | ||||
|                 raise WorkloadError(message.format(resource_file, self.name)) | ||||
|             # adb push will create intermediate directories if they don't | ||||
|             # exist. | ||||
|             self.device.push_file(asset_tarball, ondevice_cache) | ||||
|  | ||||
|         device_asset_directory = self.device.path.join(self.device.external_storage_directory, 'Android', kind) | ||||
|         deploy_command = 'cd {} && {} tar -xzf {}'.format(device_asset_directory, | ||||
|                                                           self.device.busybox, | ||||
|                                                           ondevice_cache) | ||||
|         self.device.execute(deploy_command, timeout=timeout, as_root=True) | ||||
							
								
								
									
										
											BIN
										
									
								
								wlauto/common/bin/arm64/busybox
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								wlauto/common/bin/arm64/busybox
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								wlauto/common/bin/arm64/revent
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								wlauto/common/bin/arm64/revent
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								wlauto/common/bin/armeabi/busybox
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								wlauto/common/bin/armeabi/busybox
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								wlauto/common/bin/armeabi/revent
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								wlauto/common/bin/armeabi/revent
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										16
									
								
								wlauto/common/linux/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								wlauto/common/linux/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #    Copyright 2014-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
							
								
								
									
										966
									
								
								wlauto/common/linux/device.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										966
									
								
								wlauto/common/linux/device.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,966 @@ | ||||
| #    Copyright 2014-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
| # pylint: disable=E1101 | ||||
| import os | ||||
| import re | ||||
| from collections import namedtuple | ||||
| from subprocess import CalledProcessError | ||||
|  | ||||
| from wlauto.core.extension import Parameter | ||||
| from wlauto.core.device import Device, RuntimeParameter, CoreParameter | ||||
| from wlauto.core.resource import NO_ONE | ||||
| from wlauto.exceptions import ConfigError, DeviceError, TimeoutError, DeviceNotRespondingError | ||||
| from wlauto.common.resources import Executable | ||||
| from wlauto.utils.cpuinfo import Cpuinfo | ||||
| from wlauto.utils.misc import convert_new_lines, escape_double_quotes | ||||
| from wlauto.utils.ssh import SshShell | ||||
| from wlauto.utils.types import boolean, list_of_strings | ||||
|  | ||||
|  | ||||
| # a dict of governor name and a list of it tunables that can't be read | ||||
| WRITE_ONLY_TUNABLES = { | ||||
|     'interactive': ['boostpulse'] | ||||
| } | ||||
|  | ||||
| FstabEntry = namedtuple('FstabEntry', ['device', 'mount_point', 'fs_type', 'options', 'dump_freq', 'pass_num']) | ||||
| PsEntry = namedtuple('PsEntry', 'user pid ppid vsize rss wchan pc state name') | ||||
|  | ||||
|  | ||||
| class BaseLinuxDevice(Device):  # pylint: disable=abstract-method | ||||
|  | ||||
|     path_module = 'posixpath' | ||||
|     has_gpu = True | ||||
|  | ||||
|     parameters = [ | ||||
|         Parameter('scheduler', kind=str, default='unknown', | ||||
|                   allowed_values=['unknown', 'smp', 'hmp', 'iks', 'ea', 'other'], | ||||
|                   description=""" | ||||
|                   Specifies the type of multi-core scheduling model utilized in the device. The value | ||||
|                   must be one of the following: | ||||
|  | ||||
|                   :unknown: A generic Device interface is used to interact with the underlying device | ||||
|                             and the underlying scheduling model is unkown. | ||||
|                   :smp: A standard single-core or Symmetric Multi-Processing system. | ||||
|                   :hmp: ARM Heterogeneous Multi-Processing system. | ||||
|                   :iks: Linaro In-Kernel Switcher. | ||||
|                   :ea: ARM Energy-Aware scheduler. | ||||
|                   :other: Any other system not covered by the above. | ||||
|  | ||||
|                           .. note:: most currently-available systems would fall under ``smp`` rather than | ||||
|                                     this value. ``other`` is there to future-proof against new schemes | ||||
|                                     not yet covered by WA. | ||||
|  | ||||
|                   """), | ||||
|         Parameter('iks_switch_frequency', kind=int, default=None, | ||||
|                   description=""" | ||||
|                  This is the switching frequency, in kilohertz, of IKS devices. This parameter *MUST NOT* | ||||
|                  be set for non-IKS device (i.e. ``scheduler != 'iks'``). If left unset for IKS devices, | ||||
|                  it will default to ``800000``, i.e. 800MHz. | ||||
|                  """), | ||||
|  | ||||
|     ] | ||||
|  | ||||
|     runtime_parameters = [ | ||||
|         RuntimeParameter('sysfile_values', 'get_sysfile_values', 'set_sysfile_values', value_name='params'), | ||||
|         CoreParameter('${core}_cores', 'get_number_of_active_cores', 'set_number_of_active_cores', | ||||
|                       value_name='number'), | ||||
|         CoreParameter('${core}_min_frequency', 'get_core_min_frequency', 'set_core_min_frequency', | ||||
|                       value_name='freq'), | ||||
|         CoreParameter('${core}_max_frequency', 'get_core_max_frequency', 'set_core_max_frequency', | ||||
|                       value_name='freq'), | ||||
|         CoreParameter('${core}_governor', 'get_core_governor', 'set_core_governor', | ||||
|                       value_name='governor'), | ||||
|         CoreParameter('${core}_governor_tunables', 'get_core_governor_tunables', 'set_core_governor_tunables', | ||||
|                       value_name='tunables'), | ||||
|     ] | ||||
|  | ||||
|     @property | ||||
|     def active_cpus(self): | ||||
|         val = self.get_sysfile_value('/sys/devices/system/cpu/online') | ||||
|         cpus = re.findall(r"([\d]\-[\d]|[\d])", val) | ||||
|         active_cpus = [] | ||||
|         for cpu in cpus: | ||||
|             if '-' in cpu: | ||||
|                 lo, hi = cpu.split('-') | ||||
|                 active_cpus.extend(range(int(lo), int(hi) + 1)) | ||||
|             else: | ||||
|                 active_cpus.append(int(cpu)) | ||||
|         return active_cpus | ||||
|  | ||||
|     @property | ||||
|     def number_of_cores(self): | ||||
|         """ | ||||
|         Added in version 2.1.4. | ||||
|  | ||||
|         """ | ||||
|         if self._number_of_cores is None: | ||||
|             corere = re.compile('^\s*cpu\d+\s*$') | ||||
|             output = self.execute('ls /sys/devices/system/cpu') | ||||
|             self._number_of_cores = 0 | ||||
|             for entry in output.split(): | ||||
|                 if corere.match(entry): | ||||
|                     self._number_of_cores += 1 | ||||
|         return self._number_of_cores | ||||
|  | ||||
|     @property | ||||
|     def resource_cache(self): | ||||
|         return self.path.join(self.working_directory, '.cache') | ||||
|  | ||||
|     @property | ||||
|     def file_transfer_cache(self): | ||||
|         return self.path.join(self.working_directory, '.transfer') | ||||
|  | ||||
|     @property | ||||
|     def cpuinfo(self): | ||||
|         if not self._cpuinfo: | ||||
|             self._cpuinfo = Cpuinfo(self.execute('cat /proc/cpuinfo')) | ||||
|         return self._cpuinfo | ||||
|  | ||||
|     def __init__(self, **kwargs): | ||||
|         super(BaseLinuxDevice, self).__init__(**kwargs) | ||||
|         self.busybox = None | ||||
|         self._is_initialized = False | ||||
|         self._is_ready = False | ||||
|         self._just_rebooted = False | ||||
|         self._is_rooted = None | ||||
|         self._available_frequencies = {} | ||||
|         self._available_governors = {} | ||||
|         self._available_governor_tunables = {} | ||||
|         self._number_of_cores = None | ||||
|         self._written_sysfiles = [] | ||||
|         self._cpuinfo = None | ||||
|  | ||||
|     def validate(self): | ||||
|         if len(self.core_names) != len(self.core_clusters): | ||||
|             raise ConfigError('core_names and core_clusters are of different lengths.') | ||||
|         if self.iks_switch_frequency is not None and self.scheduler != 'iks':  # pylint: disable=E0203 | ||||
|             raise ConfigError('iks_switch_frequency must NOT be set for non-IKS devices.') | ||||
|         if self.iks_switch_frequency is None and self.scheduler == 'iks':  # pylint: disable=E0203 | ||||
|             self.iks_switch_frequency = 800000  # pylint: disable=W0201 | ||||
|  | ||||
|     def initialize(self, context, *args, **kwargs): | ||||
|         self.execute('mkdir -p {}'.format(self.working_directory)) | ||||
|         if self.is_rooted: | ||||
|             if not self.is_installed('busybox'): | ||||
|                 self.busybox = self.deploy_busybox(context) | ||||
|             else: | ||||
|                 self.busybox = 'busybox' | ||||
|         self.init(context, *args, **kwargs) | ||||
|  | ||||
|     def get_sysfile_value(self, sysfile, kind=None): | ||||
|         """ | ||||
|         Get the contents of the specified sysfile. | ||||
|  | ||||
|         :param sysfile: The file who's contents will be returned. | ||||
|  | ||||
|         :param kind: The type of value to be expected in the sysfile. This can | ||||
|                      be any Python callable that takes a single str argument. | ||||
|                      If not specified or is None, the contents will be returned | ||||
|                      as a string. | ||||
|  | ||||
|         """ | ||||
|         output = self.execute('cat \'{}\''.format(sysfile), as_root=True).strip()  # pylint: disable=E1103 | ||||
|         if kind: | ||||
|             return kind(output) | ||||
|         else: | ||||
|             return output | ||||
|  | ||||
|     def set_sysfile_value(self, sysfile, value, verify=True): | ||||
|         """ | ||||
|         Set the value of the specified sysfile. By default, the value will be checked afterwards. | ||||
|         Can be overridden by setting ``verify`` parameter to ``False``. | ||||
|  | ||||
|         """ | ||||
|         value = str(value) | ||||
|         self.execute('echo {} > \'{}\''.format(value, sysfile), check_exit_code=False, as_root=True) | ||||
|         if verify: | ||||
|             output = self.get_sysfile_value(sysfile) | ||||
|             if not output.strip() == value:  # pylint: disable=E1103 | ||||
|                 message = 'Could not set the value of {} to {}'.format(sysfile, value) | ||||
|                 raise DeviceError(message) | ||||
|         self._written_sysfiles.append(sysfile) | ||||
|  | ||||
|     def get_sysfile_values(self): | ||||
|         """ | ||||
|         Returns a dict mapping paths of sysfiles that were previously set to their | ||||
|         current values. | ||||
|  | ||||
|         """ | ||||
|         values = {} | ||||
|         for sysfile in self._written_sysfiles: | ||||
|             values[sysfile] = self.get_sysfile_value(sysfile) | ||||
|         return values | ||||
|  | ||||
|     def set_sysfile_values(self, params): | ||||
|         """ | ||||
|         The plural version of ``set_sysfile_value``. Takes a single parameter which is a mapping of | ||||
|         file paths to values to be set. By default, every value written will be verified. The can | ||||
|         be disabled for individual paths by appending ``'!'`` to them. | ||||
|  | ||||
|         """ | ||||
|         for sysfile, value in params.iteritems(): | ||||
|             verify = not sysfile.endswith('!') | ||||
|             sysfile = sysfile.rstrip('!') | ||||
|             self.set_sysfile_value(sysfile, value, verify=verify) | ||||
|  | ||||
|     def deploy_busybox(self, context, force=False): | ||||
|         """ | ||||
|         Deploys the busybox Android binary (hence in android module) to the | ||||
|         specified device, and returns the path to the binary on the device. | ||||
|  | ||||
|         :param device: device to deploy the binary to. | ||||
|         :param context: an instance of ExecutionContext | ||||
|         :param force: by default, if the binary is already present on the | ||||
|                     device, it will not be deployed again. Setting force | ||||
|                     to ``True`` overrides that behavior and ensures that the | ||||
|                     binary is always copied. Defaults to ``False``. | ||||
|  | ||||
|         :returns: The on-device path to the busybox binary. | ||||
|  | ||||
|         """ | ||||
|         on_device_executable = self.path.join(self.binaries_directory, 'busybox') | ||||
|         if not force and self.file_exists(on_device_executable): | ||||
|             return on_device_executable | ||||
|         host_file = context.resolver.get(Executable(NO_ONE, self.abi, 'busybox')) | ||||
|         return self.install(host_file) | ||||
|  | ||||
|     def list_file_systems(self): | ||||
|         output = self.execute('mount') | ||||
|         fstab = [] | ||||
|         for line in output.split('\n'): | ||||
|             fstab.append(FstabEntry(*line.split())) | ||||
|         return fstab | ||||
|  | ||||
|     # Process query and control | ||||
|  | ||||
|     def get_pids_of(self, process_name): | ||||
|         """Returns a list of PIDs of all processes with the specified name.""" | ||||
|         result = self.execute('ps {}'.format(process_name[-15:]), check_exit_code=False).strip() | ||||
|         if result and 'not found' not in result: | ||||
|             return [int(x.split()[1]) for x in result.split('\n')[1:]] | ||||
|         else: | ||||
|             return [] | ||||
|  | ||||
|     def ps(self, **kwargs): | ||||
|         """ | ||||
|         Returns the list of running processes on the device. Keyword arguments may | ||||
|         be used to specify simple filters for columns. | ||||
|  | ||||
|         Added in version 2.1.4 | ||||
|  | ||||
|         """ | ||||
|         lines = iter(convert_new_lines(self.execute('ps')).split('\n')) | ||||
|         lines.next()  # header | ||||
|         result = [] | ||||
|         for line in lines: | ||||
|             parts = line.split() | ||||
|             if parts: | ||||
|                 result.append(PsEntry(*(parts[0:1] + map(int, parts[1:5]) + parts[5:]))) | ||||
|         if not kwargs: | ||||
|             return result | ||||
|         else: | ||||
|             filtered_result = [] | ||||
|             for entry in result: | ||||
|                 if all(getattr(entry, k) == v for k, v in kwargs.iteritems()): | ||||
|                     filtered_result.append(entry) | ||||
|             return filtered_result | ||||
|  | ||||
|     def kill(self, pid, signal=None, as_root=False):  # pylint: disable=W0221 | ||||
|         """ | ||||
|         Kill the specified process. | ||||
|  | ||||
|             :param pid: PID of the process to kill. | ||||
|             :param signal: Specify which singal to send to the process. This must | ||||
|                            be a valid value for -s option of kill. Defaults to ``None``. | ||||
|  | ||||
|         Modified in version 2.1.4: added ``signal`` parameter. | ||||
|  | ||||
|         """ | ||||
|         signal_string = '-s {}'.format(signal) if signal else '' | ||||
|         self.execute('kill {} {}'.format(signal_string, pid), as_root=as_root) | ||||
|  | ||||
|     def killall(self, process_name, signal=None, as_root=False):  # pylint: disable=W0221 | ||||
|         """ | ||||
|         Kill all processes with the specified name. | ||||
|  | ||||
|             :param process_name: The name of the process(es) to kill. | ||||
|             :param signal: Specify which singal to send to the process. This must | ||||
|                            be a valid value for -s option of kill. Defaults to ``None``. | ||||
|  | ||||
|         Modified in version 2.1.5: added ``as_root`` parameter. | ||||
|  | ||||
|         """ | ||||
|         for pid in self.get_pids_of(process_name): | ||||
|             self.kill(pid, signal=signal, as_root=as_root) | ||||
|  | ||||
|     # cpufreq | ||||
|  | ||||
|     def list_available_cpu_governors(self, cpu): | ||||
|         """Returns a list of governors supported by the cpu.""" | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         if cpu not in self._available_governors: | ||||
|             cmd = 'cat /sys/devices/system/cpu/{}/cpufreq/scaling_available_governors'.format(cpu) | ||||
|             output = self.execute(cmd, check_exit_code=True) | ||||
|             self._available_governors[cpu] = output.strip().split()  # pylint: disable=E1103 | ||||
|         return self._available_governors[cpu] | ||||
|  | ||||
|     def get_cpu_governor(self, cpu): | ||||
|         """Returns the governor currently set for the specified CPU.""" | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor'.format(cpu) | ||||
|         return self.get_sysfile_value(sysfile) | ||||
|  | ||||
|     def set_cpu_governor(self, cpu, governor, **kwargs): | ||||
|         """ | ||||
|         Set the governor for the specified CPU. | ||||
|         See https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt | ||||
|  | ||||
|         :param cpu: The CPU for which the governor is to be set. This must be | ||||
|                     the full name as it appears in sysfs, e.g. "cpu0". | ||||
|         :param governor: The name of the governor to be used. This must be | ||||
|                          supported by the specific device. | ||||
|  | ||||
|         Additional keyword arguments can be used to specify governor tunables for | ||||
|         governors that support them. | ||||
|  | ||||
|         :note: On big.LITTLE all cores in a cluster must be using the same governor. | ||||
|                Setting the governor on any core in a cluster will also set it on all | ||||
|                other cores in that cluster. | ||||
|  | ||||
|         :raises: ConfigError if governor is not supported by the CPU. | ||||
|         :raises: DeviceError if, for some reason, the governor could not be set. | ||||
|  | ||||
|         """ | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         supported = self.list_available_cpu_governors(cpu) | ||||
|         if governor not in supported: | ||||
|             raise ConfigError('Governor {} not supported for cpu {}'.format(governor, cpu)) | ||||
|         sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor'.format(cpu) | ||||
|         self.set_sysfile_value(sysfile, governor) | ||||
|         self.set_cpu_governor_tunables(cpu, governor, **kwargs) | ||||
|  | ||||
|     def list_available_cpu_governor_tunables(self, cpu): | ||||
|         """Returns a list of tunables available for the governor on the specified CPU.""" | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         governor = self.get_cpu_governor(cpu) | ||||
|         if governor not in self._available_governor_tunables: | ||||
|             try: | ||||
|                 tunables_path = '/sys/devices/system/cpu/{}/cpufreq/{}'.format(cpu, governor) | ||||
|                 self._available_governor_tunables[governor] = self.listdir(tunables_path) | ||||
|             except DeviceError:  # probably an older kernel | ||||
|                 try: | ||||
|                     tunables_path = '/sys/devices/system/cpu/cpufreq/{}'.format(governor) | ||||
|                     self._available_governor_tunables[governor] = self.listdir(tunables_path) | ||||
|                 except DeviceError:  # governor does not support tunables | ||||
|                     self._available_governor_tunables[governor] = [] | ||||
|         return self._available_governor_tunables[governor] | ||||
|  | ||||
|     def get_cpu_governor_tunables(self, cpu): | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         governor = self.get_cpu_governor(cpu) | ||||
|         tunables = {} | ||||
|         for tunable in self.list_available_cpu_governor_tunables(cpu): | ||||
|             if tunable not in WRITE_ONLY_TUNABLES.get(governor, []): | ||||
|                 try: | ||||
|                     path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable) | ||||
|                     tunables[tunable] = self.get_sysfile_value(path) | ||||
|                 except DeviceError:  # May be an older kernel | ||||
|                     path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable) | ||||
|                     tunables[tunable] = self.get_sysfile_value(path) | ||||
|         return tunables | ||||
|  | ||||
|     def set_cpu_governor_tunables(self, cpu, governor, **kwargs): | ||||
|         """ | ||||
|         Set tunables for the specified governor. Tunables should be specified as | ||||
|         keyword arguments. Which tunables and values are valid depends on the | ||||
|         governor. | ||||
|  | ||||
|         :param cpu: The cpu for which the governor will be set. This must be the | ||||
|                     full cpu name as it appears in sysfs, e.g. ``cpu0``. | ||||
|         :param governor: The name of the governor. Must be all lower case. | ||||
|  | ||||
|         The rest should be keyword parameters mapping tunable name onto the value to | ||||
|         be set for it. | ||||
|  | ||||
|         :raises: ConfigError if governor specified is not a valid governor name, or if | ||||
|                  a tunable specified is not valid for the governor. | ||||
|         :raises: DeviceError if could not set tunable. | ||||
|  | ||||
|  | ||||
|         """ | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         valid_tunables = self.list_available_cpu_governor_tunables(cpu) | ||||
|         for tunable, value in kwargs.iteritems(): | ||||
|             if tunable in valid_tunables: | ||||
|                 try: | ||||
|                     path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable) | ||||
|                     self.set_sysfile_value(path, value) | ||||
|                 except DeviceError:  # May be an older kernel | ||||
|                     path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable) | ||||
|                     self.set_sysfile_value(path, value) | ||||
|             else: | ||||
|                 message = 'Unexpected tunable {} for governor {} on {}.\n'.format(tunable, governor, cpu) | ||||
|                 message += 'Available tunables are: {}'.format(valid_tunables) | ||||
|                 raise ConfigError(message) | ||||
|  | ||||
|     def enable_cpu(self, cpu): | ||||
|         """ | ||||
|         Enable the specified core. | ||||
|  | ||||
|         :param cpu: CPU core to enable. This must be the full name as it | ||||
|                     appears in sysfs, e.g. "cpu0". | ||||
|  | ||||
|         """ | ||||
|         self.hotplug_cpu(cpu, online=True) | ||||
|  | ||||
|     def disable_cpu(self, cpu): | ||||
|         """ | ||||
|         Disable the specified core. | ||||
|  | ||||
|         :param cpu: CPU core to disable. This must be the full name as it | ||||
|                     appears in sysfs, e.g. "cpu0". | ||||
|         """ | ||||
|         self.hotplug_cpu(cpu, online=False) | ||||
|  | ||||
|     def hotplug_cpu(self, cpu, online): | ||||
|         """ | ||||
|         Hotplug the specified CPU either on or off. | ||||
|         See https://www.kernel.org/doc/Documentation/cpu-hotplug.txt | ||||
|  | ||||
|         :param cpu: The CPU for which the governor is to be set. This must be | ||||
|                     the full name as it appears in sysfs, e.g. "cpu0". | ||||
|         :param online: CPU will be enabled if this value bool()'s to True, and | ||||
|                        will be disabled otherwise. | ||||
|  | ||||
|         """ | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         status = 1 if online else 0 | ||||
|         sysfile = '/sys/devices/system/cpu/{}/online'.format(cpu) | ||||
|         self.set_sysfile_value(sysfile, status) | ||||
|  | ||||
|     def list_available_cpu_frequencies(self, cpu): | ||||
|         """Returns a list of frequencies supported by the cpu or an empty list | ||||
|         if not could be found.""" | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         if cpu not in self._available_frequencies: | ||||
|             try: | ||||
|                 cmd = 'cat /sys/devices/system/cpu/{}/cpufreq/scaling_available_frequencies'.format(cpu) | ||||
|                 output = self.execute(cmd) | ||||
|                 self._available_frequencies[cpu] = map(int, output.strip().split())  # pylint: disable=E1103 | ||||
|             except DeviceError: | ||||
|                 # we return an empty list because on some devices scaling_available_frequencies | ||||
|                 # is not generated. So we are returing an empty list as an indication | ||||
|                 # http://adrynalyne-teachtofish.blogspot.co.uk/2011/11/how-to-enable-scalingavailablefrequenci.html | ||||
|                 self._available_frequencies[cpu] = [] | ||||
|         return self._available_frequencies[cpu] | ||||
|  | ||||
|     def get_cpu_min_frequency(self, cpu): | ||||
|         """ | ||||
|         Returns the min frequency currently set for the specified CPU. | ||||
|  | ||||
|         Warning, this method does not check if the cpu is online or not. It will | ||||
|         try to read the minimum frequency and the following exception will be | ||||
|         raised :: | ||||
|  | ||||
|         :raises: DeviceError if for some reason the frequency could not be read. | ||||
|  | ||||
|         """ | ||||
|         sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_min_freq'.format(cpu) | ||||
|         return self.get_sysfile_value(sysfile) | ||||
|  | ||||
|     def set_cpu_min_frequency(self, cpu, frequency): | ||||
|         """ | ||||
|         Set's the minimum value for CPU frequency. Actual frequency will | ||||
|         depend on the Governor used and may vary during execution. The value should be | ||||
|         either an int or a string representing an integer. The Value must also be | ||||
|         supported by the device. The available frequencies can be obtained by calling | ||||
|         get_available_frequencies() or examining | ||||
|  | ||||
|         /sys/devices/system/cpu/cpuX/cpufreq/scaling_available_frequencies | ||||
|  | ||||
|         on the device. | ||||
|  | ||||
|         :raises: ConfigError if the frequency is not supported by the CPU. | ||||
|         :raises: DeviceError if, for some reason, frequency could not be set. | ||||
|  | ||||
|         """ | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         available_frequencies = self.list_available_cpu_frequencies(cpu) | ||||
|         try: | ||||
|             value = int(frequency) | ||||
|             if available_frequencies and value not in available_frequencies: | ||||
|                 raise ConfigError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu, | ||||
|                                                                                         value, | ||||
|                                                                                         available_frequencies)) | ||||
|             sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_min_freq'.format(cpu) | ||||
|             self.set_sysfile_value(sysfile, value) | ||||
|         except ValueError: | ||||
|             raise ValueError('value must be an integer; got: "{}"'.format(value)) | ||||
|  | ||||
|     def get_cpu_max_frequency(self, cpu): | ||||
|         """ | ||||
|         Returns the max frequency currently set for the specified CPU. | ||||
|  | ||||
|         Warning, this method does not check if the cpu is online or not. It will | ||||
|         try to read the maximum frequency and the following exception will be | ||||
|         raised :: | ||||
|  | ||||
|         :raises: DeviceError if for some reason the frequency could not be read. | ||||
|         """ | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_max_freq'.format(cpu) | ||||
|         return self.get_sysfile_value(sysfile) | ||||
|  | ||||
|     def set_cpu_max_frequency(self, cpu, frequency): | ||||
|         """ | ||||
|         Set's the minimum value for CPU frequency. Actual frequency will | ||||
|         depend on the Governor used and may vary during execution. The value should be | ||||
|         either an int or a string representing an integer. The Value must also be | ||||
|         supported by the device. The available frequencies can be obtained by calling | ||||
|         get_available_frequencies() or examining | ||||
|  | ||||
|         /sys/devices/system/cpu/cpuX/cpufreq/scaling_available_frequencies | ||||
|  | ||||
|         on the device. | ||||
|  | ||||
|         :raises: ConfigError if the frequency is not supported by the CPU. | ||||
|         :raises: DeviceError if, for some reason, frequency could not be set. | ||||
|  | ||||
|         """ | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         available_frequencies = self.list_available_cpu_frequencies(cpu) | ||||
|         try: | ||||
|             value = int(frequency) | ||||
|             if available_frequencies and value not in available_frequencies: | ||||
|                 raise DeviceError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu, | ||||
|                                                                                         value, | ||||
|                                                                                         available_frequencies)) | ||||
|             sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_max_freq'.format(cpu) | ||||
|             self.set_sysfile_value(sysfile, value) | ||||
|         except ValueError: | ||||
|             raise ValueError('value must be an integer; got: "{}"'.format(value)) | ||||
|  | ||||
|     def get_cpuidle_states(self, cpu=0): | ||||
|         """ | ||||
|         Return map of cpuidle states with their descriptive names. | ||||
|         """ | ||||
|         if isinstance(cpu, int): | ||||
|             cpu = 'cpu{}'.format(cpu) | ||||
|         cpuidle_states = {} | ||||
|         statere = re.compile('^\s*state\d+\s*$') | ||||
|         output = self.execute("ls /sys/devices/system/cpu/{}/cpuidle".format(cpu)) | ||||
|         for entry in output.split(): | ||||
|             if statere.match(entry): | ||||
|                 cpuidle_states[entry] = self.get_sysfile_value("/sys/devices/system/cpu/{}/cpuidle/{}/desc".format(cpu, entry)) | ||||
|         return cpuidle_states | ||||
|  | ||||
|     # Core- and cluster-level mapping for the above cpu-level APIs above. The | ||||
|     # APIs make the following assumptions, which were True for all devices that | ||||
|     # existed at the time of writing: | ||||
|     #   1. A cluster can only contain cores of one type. | ||||
|     #   2. All cores in a cluster are tied to the same DVFS domain, therefore | ||||
|     #      changes to cpufreq for a core will affect all other cores on the | ||||
|     #      same cluster. | ||||
|  | ||||
|     def get_core_clusters(self, core, strict=True): | ||||
|         """Returns the list of clusters  that contain the specified core. if ``strict`` | ||||
|         is ``True``, raises ValueError if no clusters has been found (returns empty list | ||||
|         if ``strict`` is ``False``).""" | ||||
|         core_indexes = [i for i, c in enumerate(self.core_names) if c == core] | ||||
|         clusters = sorted(list(set(self.core_clusters[i] for i in core_indexes))) | ||||
|         if strict and not clusters: | ||||
|             raise ValueError('No active clusters for core {}'.format(core)) | ||||
|         return clusters | ||||
|  | ||||
|     def get_cluster_cpu(self, cluster): | ||||
|         """Returns the first *active* cpu for the cluster. If the entire cluster | ||||
|         has been hotplugged, this will raise a ``ValueError``.""" | ||||
|         cpu_indexes = set([i for i, c in enumerate(self.core_clusters) if c == cluster]) | ||||
|         active_cpus = sorted(list(cpu_indexes.intersection(self.active_cpus))) | ||||
|         if not active_cpus: | ||||
|             raise ValueError('All cpus for cluster {} are offline'.format(cluster)) | ||||
|         return active_cpus[0] | ||||
|  | ||||
|     def list_available_cluster_governors(self, cluster): | ||||
|         return self.list_available_cpu_governors(self.get_cluster_cpu(cluster)) | ||||
|  | ||||
|     def get_cluster_governor(self, cluster): | ||||
|         return self.get_cpu_governor(self.get_cluster_cpu(cluster)) | ||||
|  | ||||
|     def set_cluster_governor(self, cluster, governor, **tunables): | ||||
|         return self.set_cpu_governor(self.get_cluster_cpu(cluster), governor, **tunables) | ||||
|  | ||||
|     def list_available_cluster_governor_tunables(self, cluster): | ||||
|         return self.list_available_cpu_governor_tunables(self.get_cluster_cpu(cluster)) | ||||
|  | ||||
|     def get_cluster_governor_tunables(self, cluster): | ||||
|         return self.get_cpu_governor_tunables(self.get_cluster_cpu(cluster)) | ||||
|  | ||||
|     def set_cluster_governor_tunables(self, cluster, governor, **tunables): | ||||
|         return self.set_cpu_governor_tunables(self.get_cluster_cpu(cluster), governor, **tunables) | ||||
|  | ||||
|     def get_cluster_min_frequency(self, cluster): | ||||
|         return self.get_cpu_min_frequency(self.get_cluster_cpu(cluster)) | ||||
|  | ||||
|     def set_cluster_min_frequency(self, cluster, freq): | ||||
|         return self.set_cpu_min_frequency(self.get_cluster_cpu(cluster), freq) | ||||
|  | ||||
|     def get_cluster_max_frequency(self, cluster): | ||||
|         return self.get_cpu_max_frequency(self.get_cluster_cpu(cluster)) | ||||
|  | ||||
|     def set_cluster_max_frequency(self, cluster, freq): | ||||
|         return self.set_cpu_max_frequency(self.get_cluster_cpu(cluster), freq) | ||||
|  | ||||
|     def get_core_cpu(self, core): | ||||
|         for cluster in self.get_core_clusters(core): | ||||
|             try: | ||||
|                 return self.get_cluster_cpu(cluster) | ||||
|             except ValueError: | ||||
|                 pass | ||||
|         raise ValueError('No active CPUs found for core {}'.format(core)) | ||||
|  | ||||
|     def list_available_core_governors(self, core): | ||||
|         return self.list_available_cpu_governors(self.get_core_cpu(core)) | ||||
|  | ||||
|     def get_core_governor(self, core): | ||||
|         return self.get_cpu_governor(self.get_core_cpu(core)) | ||||
|  | ||||
|     def set_core_governor(self, core, governor, **tunables): | ||||
|         for cluster in self.get_core_clusters(core): | ||||
|             self.set_cluster_governor(cluster, governor, **tunables) | ||||
|  | ||||
|     def list_available_core_governor_tunables(self, core): | ||||
|         return self.list_available_cpu_governor_tunables(self.get_core_cpu(core)) | ||||
|  | ||||
|     def get_core_governor_tunables(self, core): | ||||
|         return self.get_cpu_governor_tunables(self.get_core_cpu(core)) | ||||
|  | ||||
|     def set_core_governor_tunables(self, core, tunables): | ||||
|         for cluster in self.get_core_clusters(core): | ||||
|             governor = self.get_cluster_governor(cluster) | ||||
|             self.set_cluster_governor_tunables(cluster, governor, **tunables) | ||||
|  | ||||
|     def get_core_min_frequency(self, core): | ||||
|         return self.get_cpu_min_frequency(self.get_core_cpu(core)) | ||||
|  | ||||
|     def set_core_min_frequency(self, core, freq): | ||||
|         for cluster in self.get_core_clusters(core): | ||||
|             self.set_cluster_min_frequency(cluster, freq) | ||||
|  | ||||
|     def get_core_max_frequency(self, core): | ||||
|         return self.get_cpu_max_frequency(self.get_core_cpu(core)) | ||||
|  | ||||
|     def set_core_max_frequency(self, core, freq): | ||||
|         for cluster in self.get_core_clusters(core): | ||||
|             self.set_cluster_max_frequency(cluster, freq) | ||||
|  | ||||
|     def get_number_of_active_cores(self, core): | ||||
|         if core not in self.core_names: | ||||
|             raise ValueError('Unexpected core: {}; must be in {}'.format(core, list(set(self.core_names)))) | ||||
|         active_cpus = self.active_cpus | ||||
|         num_active_cores = 0 | ||||
|         for i, c in enumerate(self.core_names): | ||||
|             if c == core and i in active_cpus: | ||||
|                 num_active_cores += 1 | ||||
|         return num_active_cores | ||||
|  | ||||
|     def set_number_of_active_cores(self, core, number): | ||||
|         if core not in self.core_names: | ||||
|             raise ValueError('Unexpected core: {}; must be in {}'.format(core, list(set(self.core_names)))) | ||||
|         core_ids = [i for i, c in enumerate(self.core_names) if c == core] | ||||
|         max_cores = len(core_ids) | ||||
|         if number > max_cores: | ||||
|             message = 'Attempting to set the number of active {} to {}; maximum is {}' | ||||
|             raise ValueError(message.format(core, number, max_cores)) | ||||
|         for i in xrange(0, number): | ||||
|             self.enable_cpu(core_ids[i]) | ||||
|         for i in xrange(number, max_cores): | ||||
|             self.disable_cpu(core_ids[i]) | ||||
|  | ||||
|     # internal methods | ||||
|  | ||||
|     def _check_ready(self): | ||||
|         if not self._is_ready: | ||||
|             raise AttributeError('Device not ready.') | ||||
|  | ||||
|     def _get_core_cluster(self, core): | ||||
|         """Returns the first cluster that has cores of the specified type. Raises | ||||
|         value error if no cluster for the specified type has been found""" | ||||
|         core_indexes = [i for i, c in enumerate(self.core_names) if c == core] | ||||
|         core_clusters = set(self.core_clusters[i] for i in core_indexes) | ||||
|         if not core_clusters: | ||||
|             raise ValueError('No cluster found for core {}'.format(core)) | ||||
|         return sorted(list(core_clusters))[0] | ||||
|  | ||||
|  | ||||
| class LinuxDevice(BaseLinuxDevice): | ||||
|  | ||||
|     platform = 'linux' | ||||
|  | ||||
|     default_timeout = 30 | ||||
|     delay = 2 | ||||
|     long_delay = 3 * delay | ||||
|     ready_timeout = 60 | ||||
|  | ||||
|     parameters = [ | ||||
|         Parameter('host', mandatory=True, description='Host name or IP address for the device.'), | ||||
|         Parameter('username', mandatory=True, description='User name for the account on the device.'), | ||||
|         Parameter('password', description='Password for the account on the device (for password-based auth).'), | ||||
|         Parameter('keyfile', description='Keyfile to be used for key-based authentication.'), | ||||
|         Parameter('port', kind=int, description='SSH port number on the device.'), | ||||
|  | ||||
|         Parameter('use_telnet', kind=boolean, default=False, | ||||
|                   description='Optionally, telnet may be used instead of ssh, though this is discouraged.'), | ||||
|  | ||||
|         Parameter('working_directory', default=None, | ||||
|                   description=''' | ||||
|                   Working directory to be used by WA. This must be in a location where the specified user | ||||
|                   has write permissions. This will default to /home/<username>/wa (or to /root/wa, if | ||||
|                   username is 'root'). | ||||
|                   '''), | ||||
|         Parameter('binaries_directory', default='/usr/local/bin', | ||||
|                   description='Location of executable binaries on this device (must be in PATH).'), | ||||
|         Parameter('property_files', kind=list_of_strings, | ||||
|                   default=['/proc/version', '/etc/debian_version', '/etc/lsb-release', '/etc/arch-release'], | ||||
|                   description=''' | ||||
|                   A list of paths to files containing static OS properties. These will be pulled into the | ||||
|                   __meta directory in output for each run in order to provide information about the platfrom. | ||||
|                   These paths do not have to exist and will be ignored if the path is not present on a | ||||
|                   particular device. | ||||
|                   '''), | ||||
|     ] | ||||
|  | ||||
|     @property | ||||
|     def is_rooted(self): | ||||
|         if self._is_rooted is None: | ||||
|             try: | ||||
|                 self.execute('ls /', as_root=True) | ||||
|                 self._is_rooted = True | ||||
|             except DeviceError: | ||||
|                 self._is_rooted = False | ||||
|         return self._is_rooted | ||||
|  | ||||
|     def __init__(self, *args, **kwargs): | ||||
|         super(LinuxDevice, self).__init__(*args, **kwargs) | ||||
|         self.shell = None | ||||
|         self.local_binaries_directory = None | ||||
|         self._is_rooted = None | ||||
|  | ||||
|     def validate(self): | ||||
|         if not self.password and not self.keyfile: | ||||
|             raise ConfigError('Either a password or a keyfile must be provided.') | ||||
|         if self.working_directory is None:  # pylint: disable=access-member-before-definition | ||||
|             if self.username == 'root': | ||||
|                 self.working_directory = '/root/wa'  # pylint: disable=attribute-defined-outside-init | ||||
|             else: | ||||
|                 self.working_directory = '/home/{}/wa'.format(self.username)  # pylint: disable=attribute-defined-outside-init | ||||
|         self.local_binaries_directory = self.path.join(self.working_directory, 'bin') | ||||
|  | ||||
|     def initialize(self, context, *args, **kwargs): | ||||
|         self.execute('mkdir -p {}'.format(self.local_binaries_directory)) | ||||
|         self.execute('export PATH={}:$PATH'.format(self.local_binaries_directory)) | ||||
|         super(LinuxDevice, self).initialize(context, *args, **kwargs) | ||||
|  | ||||
|     # Power control | ||||
|  | ||||
|     def reset(self): | ||||
|         self._is_ready = False | ||||
|         self.execute('reboot', as_root=True) | ||||
|  | ||||
|     def hard_reset(self): | ||||
|         super(LinuxDevice, self).hard_reset() | ||||
|         self._is_ready = False | ||||
|  | ||||
|     def boot(self, **kwargs): | ||||
|         self.reset() | ||||
|  | ||||
|     def connect(self):  # NOQA pylint: disable=R0912 | ||||
|         self.shell = SshShell(timeout=self.default_timeout) | ||||
|         self.shell.login(self.host, self.username, self.password, self.keyfile, self.port, telnet=self.use_telnet) | ||||
|         self._is_ready = True | ||||
|  | ||||
|     def disconnect(self):  # NOQA pylint: disable=R0912 | ||||
|         self.shell.logout() | ||||
|         self._is_ready = False | ||||
|  | ||||
|     # Execution | ||||
|  | ||||
|     def has_root(self): | ||||
|         try: | ||||
|             self.execute('ls /', as_root=True) | ||||
|             return True | ||||
|         except DeviceError as e: | ||||
|             if 'not in the sudoers file' not in e.message: | ||||
|                 raise e | ||||
|             return False | ||||
|  | ||||
|     def execute(self, command, timeout=default_timeout, check_exit_code=True, background=False, | ||||
|                 as_root=False, strip_colors=True, **kwargs): | ||||
|         """ | ||||
|         Execute the specified command on the device using adb. | ||||
|  | ||||
|         Parameters: | ||||
|  | ||||
|             :param command: The command to be executed. It should appear exactly | ||||
|                             as if you were typing it into a shell. | ||||
|             :param timeout: Time, in seconds, to wait for adb to return before aborting | ||||
|                             and raising an error. Defaults to ``AndroidDevice.default_timeout``. | ||||
|             :param check_exit_code: If ``True``, the return code of the command on the Device will | ||||
|                                     be check and exception will be raised if it is not 0. | ||||
|                                     Defaults to ``True``. | ||||
|             :param background: If ``True``, will execute create a new ssh shell rather than using | ||||
|                                the default session and will return it immediately. If this is ``True``, | ||||
|                                ``timeout``, ``strip_colors`` and (obvisously) ``check_exit_code`` will | ||||
|                                be ignored; also, with this, ``as_root=True``  is only valid if ``username`` | ||||
|                                for the device was set to ``root``. | ||||
|             :param as_root: If ``True``, will attempt to execute command in privileged mode. The device | ||||
|                             must be rooted, otherwise an error will be raised. Defaults to ``False``. | ||||
|  | ||||
|                             Added in version 2.1.3 | ||||
|  | ||||
|         :returns: If ``background`` parameter is set to ``True``, the subprocess object will | ||||
|                   be returned; otherwise, the contents of STDOUT from the device will be returned. | ||||
|  | ||||
|         """ | ||||
|         self._check_ready() | ||||
|         if background: | ||||
|             if as_root and self.username != 'root': | ||||
|                 raise DeviceError('Cannot execute in background with as_root=True unless user is root.') | ||||
|             return self.shell.background(command) | ||||
|         else: | ||||
|             return self.shell.execute(command, timeout, check_exit_code, as_root, strip_colors) | ||||
|  | ||||
|     def kick_off(self, command): | ||||
|         """ | ||||
|         Like execute but closes adb session and returns immediately, leaving the command running on the | ||||
|         device (this is different from execute(background=True) which keeps adb connection open and returns | ||||
|         a subprocess object). | ||||
|  | ||||
|         """ | ||||
|         self._check_ready() | ||||
|         command = 'sh -c "{}" 1>/dev/null 2>/dev/null &'.format(escape_double_quotes(command)) | ||||
|         return self.shell.execute(command) | ||||
|  | ||||
|     # File management | ||||
|  | ||||
|     def push_file(self, source, dest, as_root=False, timeout=default_timeout):  # pylint: disable=W0221 | ||||
|         self._check_ready() | ||||
|         if not as_root or self.username == 'root': | ||||
|             self.shell.push_file(source, dest, timeout=timeout) | ||||
|         else: | ||||
|             tempfile = self.path.join(self.working_directory, self.path.basename(dest)) | ||||
|             self.shell.push_file(source, tempfile, timeout=timeout) | ||||
|             self.shell.execute('cp -r {} {}'.format(tempfile, dest), timeout=timeout, as_root=True) | ||||
|  | ||||
|     def pull_file(self, source, dest, as_root=False, timeout=default_timeout):  # pylint: disable=W0221 | ||||
|         self._check_ready() | ||||
|         if not as_root or self.username == 'root': | ||||
|             self.shell.pull_file(source, dest, timeout=timeout) | ||||
|         else: | ||||
|             tempfile = self.path.join(self.working_directory, self.path.basename(source)) | ||||
|             self.shell.execute('cp -r {} {}'.format(source, tempfile), timeout=timeout, as_root=True) | ||||
|             self.shell.execute('chown -R {} {}'.format(self.username, tempfile), timeout=timeout, as_root=True) | ||||
|             self.shell.pull_file(tempfile, dest, timeout=timeout) | ||||
|  | ||||
|     def delete_file(self, filepath, as_root=False):  # pylint: disable=W0221 | ||||
|         self.execute('rm -rf {}'.format(filepath), as_root=as_root) | ||||
|  | ||||
|     def file_exists(self, filepath): | ||||
|         output = self.execute('if [ -e \'{}\' ]; then echo 1; else echo 0; fi'.format(filepath)) | ||||
|         return boolean(output.strip())  # pylint: disable=maybe-no-member | ||||
|  | ||||
|     def listdir(self, path, as_root=False, **kwargs): | ||||
|         contents = self.execute('ls -1 {}'.format(path), as_root=as_root) | ||||
|         return [x.strip() for x in contents.split('\n')]  # pylint: disable=maybe-no-member | ||||
|  | ||||
|     def install(self, filepath, timeout=default_timeout, with_name=None):  # pylint: disable=W0221 | ||||
|         if self.is_rooted: | ||||
|             destpath = self.path.join(self.binaries_directory, | ||||
|                                       with_name and with_name or self.path.basename(filepath)) | ||||
|             self.push_file(filepath, destpath, as_root=True) | ||||
|             self.execute('chmod a+x {}'.format(destpath), timeout=timeout, as_root=True) | ||||
|         else: | ||||
|             destpath = self.path.join(self.local_binaries_directory, | ||||
|                                       with_name and with_name or self.path.basename(filepath)) | ||||
|             self.push_file(filepath, destpath) | ||||
|             self.execute('chmod a+x {}'.format(destpath), timeout=timeout) | ||||
|         return destpath | ||||
|  | ||||
|     install_executable = install  # compatibility | ||||
|  | ||||
|     def uninstall(self, name): | ||||
|         path = self.path.join(self.local_binaries_directory, name) | ||||
|         self.delete_file(path) | ||||
|  | ||||
|     uninstall_executable = uninstall  # compatibility | ||||
|  | ||||
|     def is_installed(self, name): | ||||
|         try: | ||||
|             self.execute('which {}'.format(name)) | ||||
|             return True | ||||
|         except DeviceError: | ||||
|             return False | ||||
|  | ||||
|     # misc | ||||
|  | ||||
|     def ping(self): | ||||
|         try: | ||||
|             # May be triggered inside initialize() | ||||
|             self.shell.execute('ls /', timeout=5) | ||||
|         except (TimeoutError, CalledProcessError): | ||||
|             raise DeviceNotRespondingError(self.host) | ||||
|  | ||||
|     def capture_screen(self, filepath): | ||||
|         if not self.is_installed('scrot'): | ||||
|             self.logger.debug('Could not take screenshot as scrot is not installed.') | ||||
|             return | ||||
|         try: | ||||
|             tempfile = self.path.join(self.working_directory, os.path.basename(filepath)) | ||||
|             self.execute('DISPLAY=:0.0 scrot {}'.format(tempfile)) | ||||
|             self.pull_file(tempfile, filepath) | ||||
|             self.delete_file(tempfile) | ||||
|         except DeviceError as e: | ||||
|             if "Can't open X dispay." not in e.message: | ||||
|                 raise e | ||||
|             message = e.message.split('OUTPUT:', 1)[1].strip() | ||||
|             self.logger.debug('Could not take screenshot: {}'.format(message)) | ||||
|  | ||||
|     def is_screen_on(self): | ||||
|         pass  # TODO | ||||
|  | ||||
|     def ensure_screen_is_on(self): | ||||
|         pass  # TODO | ||||
|  | ||||
|     def get_properties(self, context): | ||||
|         for propfile in self.property_files: | ||||
|             if not self.file_exists(propfile): | ||||
|                 continue | ||||
|             normname = propfile.lstrip(self.path.sep).replace(self.path.sep, '.') | ||||
|             outfile = os.path.join(context.host_working_directory, normname) | ||||
|             self.pull_file(propfile, outfile) | ||||
|         return {} | ||||
|  | ||||
							
								
								
									
										64
									
								
								wlauto/common/resources.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								wlauto/common/resources.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | ||||
| #    Copyright 2013-2015 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
|  | ||||
| import os | ||||
|  | ||||
| from wlauto.core.resource import Resource | ||||
|  | ||||
|  | ||||
| class FileResource(Resource): | ||||
|     """ | ||||
|     Base class for all resources that are a regular file in the | ||||
|     file system. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     def delete(self, instance): | ||||
|         os.remove(instance) | ||||
|  | ||||
|  | ||||
| class File(FileResource): | ||||
|  | ||||
|     name = 'file' | ||||
|  | ||||
|     def __init__(self, owner, path, url=None): | ||||
|         super(File, self).__init__(owner) | ||||
|         self.path = path | ||||
|         self.url = url | ||||
|  | ||||
|     def __str__(self): | ||||
|         return '<{}\'s {} {}>'.format(self.owner, self.name, self.path or self.url) | ||||
|  | ||||
|  | ||||
| class ExtensionAsset(File): | ||||
|  | ||||
|     name = 'extension_asset' | ||||
|  | ||||
|     def __init__(self, owner, path): | ||||
|         super(ExtensionAsset, self).__init__(owner, os.path.join(owner.name, path)) | ||||
|  | ||||
|  | ||||
| class Executable(FileResource): | ||||
|  | ||||
|     name = 'executable' | ||||
|  | ||||
|     def __init__(self, owner, platform, filename): | ||||
|         super(Executable, self).__init__(owner) | ||||
|         self.platform = platform | ||||
|         self.filename = filename | ||||
|  | ||||
|     def __str__(self): | ||||
|         return '<{}\'s {} {}>'.format(self.owner, self.platform, self.filename) | ||||
		Reference in New Issue
	
	Block a user