From 6d9a03ad8f4dde4a70748c2d0493c042e82c4773 Mon Sep 17 00:00:00 2001 From: Waleed El-Geresy Date: Mon, 7 Aug 2017 18:35:20 +0100 Subject: [PATCH 1/3] ReventWorkload: Move class to linux and add features The ReventWorkload class has been moved to the linux directory and two new features have been added: the option to run an idle workload and the option to specify a .teardown revent file as well as a .setup file which runs in the eponymous stage. --- wlauto/common/android/workload.py | 125 +++++---------------- wlauto/common/linux/workload.py | 165 ++++++++++++++++++++++++++++ wlauto/resource_getters/standard.py | 2 + 3 files changed, 192 insertions(+), 100 deletions(-) create mode 100644 wlauto/common/linux/workload.py diff --git a/wlauto/common/android/workload.py b/wlauto/common/android/workload.py index b83541d2..541b8b24 100755 --- a/wlauto/common/android/workload.py +++ b/wlauto/common/android/workload.py @@ -16,22 +16,19 @@ import os import sys import time -from math import ceil from distutils.version import LooseVersion from wlauto.core.extension import Parameter, ExtensionMeta, ListCollection from wlauto.core.workload import Workload -from wlauto.core.resource import NO_ONE -from wlauto.common.android.resources import ApkFile, ReventFile -from wlauto.common.resources import ExtensionAsset, Executable, File +from wlauto.common.android.resources import ApkFile +from wlauto.common.resources import ExtensionAsset, File from wlauto.exceptions import WorkloadError, ResourceError, DeviceError from wlauto.utils.android import (ApkInfo, ANDROID_NORMAL_PERMISSIONS, ANDROID_UNCHANGEABLE_PERMISSIONS, UNSUPPORTED_PACKAGES) from wlauto.utils.types import boolean, ParameterDict -from wlauto.utils.revent import ReventRecording import wlauto.utils.statedetect as state_detector -import wlauto.common.android.resources +from wlauto.common.linux.workload import ReventWorkload DELAY = 5 @@ -481,102 +478,8 @@ class ApkWorkload(Workload): if self.uninstall_apk: self.device.uninstall(self.package) - AndroidBenchmark = ApkWorkload # backward compatibility - -class ReventWorkload(Workload): - # pylint: disable=attribute-defined-outside-init - - def __init__(self, device, _call_super=True, **kwargs): - if _call_super: - Workload.__init__(self, device, **kwargs) - devpath = self.device.path - self.on_device_revent_binary = devpath.join(self.device.binaries_directory, 'revent') - self.setup_timeout = kwargs.get('setup_timeout', None) - self.run_timeout = kwargs.get('run_timeout', None) - self.revent_setup_file = None - self.revent_run_file = None - self.on_device_setup_revent = None - self.on_device_run_revent = None - self.statedefs_dir = None - - if self.check_states: - state_detector.check_match_state_dependencies() - - def setup(self, context): - self.revent_setup_file = context.resolver.get(ReventFile(self, 'setup')) - self.revent_run_file = context.resolver.get(ReventFile(self, 'run')) - devpath = self.device.path - self.on_device_setup_revent = devpath.join(self.device.working_directory, - os.path.split(self.revent_setup_file)[-1]) - self.on_device_run_revent = devpath.join(self.device.working_directory, - os.path.split(self.revent_run_file)[-1]) - self._check_revent_files(context) - default_setup_timeout = ceil(ReventRecording(self.revent_setup_file).duration) + 30 - default_run_timeout = ceil(ReventRecording(self.revent_run_file).duration) + 30 - self.setup_timeout = self.setup_timeout or default_setup_timeout - self.run_timeout = self.run_timeout or default_run_timeout - - Workload.setup(self, 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.killall('revent') - 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) - - def _check_statedetection_files(self, context): - try: - self.statedefs_dir = context.resolver.get(File(self, 'state_definitions')) - except ResourceError: - self.logger.warning("State definitions directory not found. Disabling state detection.") - self.check_states = False - - def check_state(self, context, phase): - try: - self.logger.info("\tChecking workload state...") - screenshotPath = os.path.join(context.output_directory, "screen.png") - self.device.capture_screen(screenshotPath) - stateCheck = state_detector.verify_state(screenshotPath, self.statedefs_dir, phase) - if not stateCheck: - raise WorkloadError("Unexpected state after setup") - except state_detector.StateDefinitionError as e: - msg = "State definitions or template files missing or invalid ({}). Skipping state detection." - self.logger.warning(msg.format(e.message)) - - class AndroidUiAutoBenchmark(UiAutomatorWorkload, AndroidBenchmark): supported_platforms = ['android'] @@ -731,6 +634,7 @@ class GameWorkload(ApkWorkload, ReventWorkload): view = 'SurfaceView' loading_time = 10 supported_platforms = ['android'] + setup_required = True parameters = [ Parameter('install_timeout', default=500, override=True), @@ -749,6 +653,8 @@ class GameWorkload(ApkWorkload, ReventWorkload): def __init__(self, device, **kwargs): # pylint: disable=W0613 ApkWorkload.__init__(self, device, **kwargs) ReventWorkload.__init__(self, device, _call_super=False, **kwargs) + if self.check_states: + state_detector.check_match_state_dependencies() 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') @@ -823,3 +729,22 @@ class GameWorkload(ApkWorkload, ReventWorkload): self.device.busybox, ondevice_cache) self.device.execute(deploy_command, timeout=timeout, as_root=True) + + def _check_statedetection_files(self, context): + try: + self.statedefs_dir = context.resolver.get(File(self, 'state_definitions')) + except ResourceError: + self.logger.warning("State definitions directory not found. Disabling state detection.") + self.check_states = False # pylint: disable=W0201 + + def check_state(self, context, phase): + try: + self.logger.info("\tChecking workload state...") + screenshotPath = os.path.join(context.output_directory, "screen.png") + self.device.capture_screen(screenshotPath) + stateCheck = state_detector.verify_state(screenshotPath, self.statedefs_dir, phase) + if not stateCheck: + raise WorkloadError("Unexpected state after setup") + except state_detector.StateDefinitionError as e: + msg = "State definitions or template files missing or invalid ({}). Skipping state detection." + self.logger.warning(msg.format(e.message)) diff --git a/wlauto/common/linux/workload.py b/wlauto/common/linux/workload.py new file mode 100644 index 00000000..1d25ce6e --- /dev/null +++ b/wlauto/common/linux/workload.py @@ -0,0 +1,165 @@ +# Copyright 2017 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 math import ceil + +from wlauto.core.extension import Parameter +from wlauto.core.workload import Workload +from wlauto.core.resource import NO_ONE +from wlauto.common.resources import Executable +from wlauto.common.android.resources import ReventFile +from wlauto.exceptions import WorkloadError +from wlauto.utils.revent import ReventRecording + +class ReventWorkload(Workload): + # pylint: disable=attribute-defined-outside-init + + description = """ + A workload for playing back revent recordings. You can supply three + different files: + + 1. {device_model}.setup.revent + 2. {device_model}.run.revent + 3. {device_model}.teardown.revent + + You may generate these files using the wa record command using the -s flag + to specify the stage (``setup``, ``run``, ``teardown``) + + You may also supply an 'idle_time' in seconds in place of the run file. + The ``run`` file may only be omitted if you choose to run this way, but + while running idle may supply ``setup`` and ``teardown`` files. + + To use a ``setup`` or ``teardown`` file set the setup_required and/or + teardown_required class attributes to True (default: False). + + N.B. This is the default description. You may overwrite this for your + workload to include more specific information. + + """ + + setup_required = False + teardown_required = False + + parameters = [ + Parameter( + 'idle_time', kind=int, default=None, + description=''' + The time you wish the device to remain idle for (if a value is + given then this overrides any .run revent file). + '''), + ] + + def __init__(self, device, _call_super=True, **kwargs): + if _call_super: + Workload.__init__(self, device, **kwargs) + self.setup_timeout = kwargs.get('setup_timeout', None) + self.run_timeout = kwargs.get('run_timeout', None) + self.teardown_timeout = kwargs.get('teardown_timeout', None) + self.revent_setup_file = None + self.revent_run_file = None + self.revent_teardown_file = None + self.on_device_setup_revent = None + self.on_device_run_revent = None + self.on_device_teardown_revent = None + self.statedefs_dir = None + + def initialize(self, context): + devpath = self.device.path + self.on_device_revent_binary = devpath.join(self.device.binaries_directory, 'revent') + + def setup(self, context): + devpath = self.device.path + if self.setup_required: + self.revent_setup_file = context.resolver.get(ReventFile(self, 'setup')) + if self.revent_setup_file: + self.on_device_setup_revent = devpath.join(self.device.working_directory, + os.path.split(self.revent_setup_file)[-1]) + duration = ReventRecording(self.revent_setup_file).duration + self.default_setup_timeout = ceil(duration) + 30 + if not self.idle_time: + self.revent_run_file = context.resolver.get(ReventFile(self, 'run')) + if self.revent_run_file: + self.on_device_run_revent = devpath.join(self.device.working_directory, + os.path.split(self.revent_run_file)[-1]) + self.default_run_timeout = ceil(ReventRecording(self.revent_run_file).duration) + 30 + if self.teardown_required: + self.revent_teardown_file = context.resolver.get(ReventFile(self, 'teardown')) + if self.revent_teardown_file: + self.on_device_teardown_revent = devpath.join(self.device.working_directory, + os.path.split(self.revent_teardown_file)[-1]) + duration = ReventRecording(self.revent_teardown_file).duration + self.default_teardown_timeout = ceil(duration) + 30 + self._check_revent_files(context) + + Workload.setup(self, context) + + if self.revent_setup_file is not None: + self.setup_timeout = self.setup_timeout or self.default_setup_timeout + 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) + self.logger.debug('Revent setup completed.') + + def run(self, context): + if not self.idle_time: + self.run_timeout = self.run_timeout or self.default_run_timeout + 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.') + else: + self.logger.info('Idling for ' + str(self.idle_time) + ' seconds.') + self.device.sleep(self.idle_time) + self.logger.info('Successfully did nothing for ' + str(self.idle_time) + ' seconds!') + + def update_result(self, context): + pass + + def teardown(self, context): + if self.revent_teardown_file is not None: + self.teardown_timeout = self.teardown_timeout or self.default_teardown_timeout + command = '{} replay {}'.format(self.on_device_revent_binary, + self.on_device_teardown_revent) + self.device.execute(command, timeout=self.teardown_timeout) + self.logger.debug('Replay completed.') + self.device.killall('revent') + if self.revent_setup_file is not None: + self.device.delete_file(self.on_device_setup_revent) + if not self.idle_time: + self.device.delete_file(self.on_device_run_revent) + if self.revent_teardown_file is not None: + self.device.delete_file(self.on_device_teardown_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_run_file and not self.idle_time: + # pylint: disable=too-few-format-args + message = 'It seems a {0}.run.revent file does not exist, '\ + 'Please provide one for your device: {0}.' + raise WorkloadError(message.format(self.device.name)) + + self.on_device_revent_binary = self.device.install_executable(revent_binary) + if self.revent_setup_file is not None: + self.device.push_file(self.revent_setup_file, self.on_device_setup_revent) + if not self.idle_time: + self.device.push_file(self.revent_run_file, self.on_device_run_revent) + if self.revent_teardown_file is not None: + self.device.push_file(self.revent_teardown_file, self.on_device_teardown_revent) diff --git a/wlauto/resource_getters/standard.py b/wlauto/resource_getters/standard.py index 0f948137..837d7ad7 100644 --- a/wlauto/resource_getters/standard.py +++ b/wlauto/resource_getters/standard.py @@ -90,12 +90,14 @@ class ReventGetter(ResourceGetter): self.resolver.register(self, 'revent', GetterPriority.package) def get(self, resource, **kwargs): + # name format: [model/device_name.stage.revent] device_model = resource.owner.device.get_device_model() wa_device_name = resource.owner.device.name for name in [device_model, wa_device_name]: if not name: continue filename = '.'.join([name, resource.stage, 'revent']).lower() + self.logger.debug('Trying to get {0}.'.format(str(filename))) location = _d(os.path.join(self.get_base_location(resource), 'revent_files')) for candidate in os.listdir(location): if candidate.lower() == filename.lower(): From e35d0f2959b818f91590709749e1ff637c3d453a Mon Sep 17 00:00:00 2001 From: Waleed El-Geresy Date: Mon, 7 Aug 2017 18:38:45 +0100 Subject: [PATCH 2/3] wa record: Update -h description for ReventWorkload Added information pertaining to the new .teardown file and several rewordings. --- wlauto/commands/record.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wlauto/commands/record.py b/wlauto/commands/record.py index 25c4fdb3..84449a4e 100644 --- a/wlauto/commands/record.py +++ b/wlauto/commands/record.py @@ -74,7 +74,7 @@ class RecordCommand(ReventCommand): name = 'record' description = '''Performs a revent recording - This command helps making revent recordings. It will automatically + This command helps create revent recordings. It will automatically deploy revent and even has the option of automatically opening apps. Revent allows you to record raw inputs such as screen swipes or button presses. @@ -82,14 +82,14 @@ class RecordCommand(ReventCommand): have XML UI layouts that can be used with UIAutomator. As a drawback from this, revent recordings are specific to the device type they were recorded on. - WA uses two parts to the names of revent recordings in the format, - {device_name}.{suffix}.revent. + WA uses two parts to form the names of revent recordings in the format, + {device_name}.{suffix}.revent - device_name can either be specified manually with the ``-d`` argument or - it can be automatically determined. On Android device it will be obtained + else the name of the device will be automatically determined. On an Android device it is obtained from ``build.prop``, on Linux devices it is obtained from ``/proc/device-tree/model``. - suffix is used by WA to determine which part of the app execution the - recording is for, currently these are either ``setup`` or ``run``. This + recording is for, currently either ``setup``, ``run`` or ``teardown``. This should be specified with the ``-s`` argument. From f35c444ddb8e1afcef2dd9ee6dfb1ad581df1078 Mon Sep 17 00:00:00 2001 From: Waleed El-Geresy Date: Mon, 7 Aug 2017 18:42:07 +0100 Subject: [PATCH 3/3] generic_linux: Remove forbidden chars from device_model Fix for Chromebook Plus and possibly other devices - removes forbidden characters from the device_model such as the null character which was causing a problem with getters getting file names including device_model. --- wlauto/common/linux/device.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wlauto/common/linux/device.py b/wlauto/common/linux/device.py index 34968fb2..9f90b947 100644 --- a/wlauto/common/linux/device.py +++ b/wlauto/common/linux/device.py @@ -590,7 +590,8 @@ class BaseLinuxDevice(Device): # pylint: disable=abstract-method def get_device_model(self): if self.file_exists("/proc/device-tree/model"): raw_model = self.execute("cat /proc/device-tree/model") - return '_'.join(raw_model.split()[:2]) + device_model_to_return = '_'.join(raw_model.split()[:2]) + return device_model_to_return.rstrip(' \t\r\n\0') # Right now we don't know any other way to get device model # info in linux on arm platforms return None