From 34eef97ac84b751d973ad50cb709c3ebbaeb6cab Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Wed, 16 Jul 2025 15:39:10 +0100 Subject: [PATCH 1/2] Squashed 'external/devlib/' changes from f71b6a7d9..9c4f09b5f 9c4f09b5f utils/android: Fix AdbConnection.execute(check_exit_code=False) default git-subtree-dir: external/devlib git-subtree-split: 9c4f09b5f3c45e77c3f9fe760460732a0031a9ac --- devlib/utils/android.py | 5 ++--- doc/connection.rst | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/devlib/utils/android.py b/devlib/utils/android.py index 001cb93be..2be37cccd 100755 --- a/devlib/utils/android.py +++ b/devlib/utils/android.py @@ -359,7 +359,7 @@ class AdbConnection(ConnectionBase): popen.communicate() # pylint: disable=unused-argument - def execute(self, command, timeout=None, check_exit_code=False, + def execute(self, command, timeout=None, check_exit_code=True, as_root=False, strip_colors=True, will_succeed=False): if as_root and self.connected_as_root: as_root = False @@ -483,8 +483,7 @@ class AdbConnection(ConnectionBase): return try: # Try the new style of invoking `su` - self.execute('ls', timeout=self.timeout, as_root=True, - check_exit_code=True) + self.execute('ls', timeout=self.timeout, as_root=True) # If failure assume either old style or unrooted. Here we will assume # old style and root status will be verified later. except (TargetStableError, TargetTransientError, TimeoutError): diff --git a/doc/connection.rst b/doc/connection.rst index 71ce155d8..7f641d9c4 100644 --- a/doc/connection.rst +++ b/doc/connection.rst @@ -41,7 +41,7 @@ class that implements the following methods. transfer does not complete within this period, an exception will be raised. -.. method:: execute(self, command, timeout=None, check_exit_code=False, as_root=False, strip_colors=True, will_succeed=False) +.. method:: execute(self, command, timeout=None, check_exit_code=True, as_root=False, strip_colors=True, will_succeed=False) Execute the specified command on the connected device and return its output. -- GitLab From 51dc45f1d1e81f34ae107d9492ee299bfc24dddd Mon Sep 17 00:00:00 2001 From: Douglas Raillard Date: Wed, 16 Jul 2025 15:39:21 +0100 Subject: [PATCH 2/2] Squashed 'external/workload-automation/' changes from 2d14c82f9..82033b6d0 82033b6d0 Fix: Use `gman` on MacOS for `wa show` 218308284 workloads: Add support for Honor of Kings game c898bdc41 framework: utils: Improve logging in revent replay related methods git-subtree-dir: external/workload-automation git-subtree-split: 82033b6d04c0f02690dd9affb1a84a2aa476e34e --- wa/commands/show.py | 12 +++++- wa/framework/workload.py | 7 +-- wa/utils/revent.py | 18 +++++--- wa/workloads/honorofkings/__init__.py | 61 +++++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 wa/workloads/honorofkings/__init__.py diff --git a/wa/commands/show.py b/wa/commands/show.py index 071b627e1..4c3439def 100644 --- a/wa/commands/show.py +++ b/wa/commands/show.py @@ -19,6 +19,7 @@ # pylint: disable-all import sys +import platform from subprocess import call, Popen, PIPE from devlib.utils.misc import escape_double_quotes @@ -72,6 +73,15 @@ class ShowCommand(Command): raise NotFoundError('Could not find plugin or alias "{}"'.format(name)) if which('pandoc'): + if platform.system() == "Darwin": + # The version of `man` shipped with macOS does not support `-l`. You need to use GNU man from: + # https://formulae.brew.sh/formula/man-db + if which("gman") is None: + print(rst_output) + man = "gman" + else: + man = "man" + p = Popen(['pandoc', '-f', 'rst', '-t', 'man'], stdin=PIPE, stdout=PIPE, stderr=PIPE) output, _ = p.communicate(rst_output.encode(sys.stdin.encoding)) output = output.decode(sys.stdout.encoding) @@ -84,7 +94,7 @@ class ShowCommand(Command): title = '.TH {}{} 7'.format(kind, plugin_name) output = '\n'.join([title, body]) - call('echo "{}" | man -l -'.format(escape_double_quotes(output)), shell=True) + call('echo "{}" | {} -l -'.format(escape_double_quotes(output), man), shell=True) else: print(rst_output) # pylint: disable=superfluous-parens diff --git a/wa/framework/workload.py b/wa/framework/workload.py index 5abb90e3f..57befdfe1 100644 --- a/wa/framework/workload.py +++ b/wa/framework/workload.py @@ -584,6 +584,7 @@ class ReventGUI(object): def __init__(self, workload, target, setup_timeout, run_timeout, extract_results_timeout, teardown_timeout): + self.logger = logging.getLogger(self.__class__.__name__) self.workload = workload self.target = target self.setup_timeout = setup_timeout @@ -596,7 +597,6 @@ class ReventGUI(object): self.on_target_run_revent = self.target.get_workpath('{}.run.revent'.format(self.target.model)) self.on_target_extract_results_revent = self.target.get_workpath('{}.extract_results.revent'.format(self.target.model)) self.on_target_teardown_revent = self.target.get_workpath('{}.teardown.revent'.format(self.target.model)) - self.logger = logging.getLogger('revent') self.revent_setup_file = None self.revent_run_file = None self.revent_extract_results_file = None @@ -629,8 +629,9 @@ class ReventGUI(object): timeout=self.setup_timeout) def run(self): - msg = 'Replaying {}' - self.logger.debug(msg.format(os.path.basename(self.on_target_run_revent))) + self.logger.debug('Replaying "%s" with %d seconds timeout', + os.path.basename(self.on_target_run_revent), + self.run_timeout) self.revent_recorder.replay(self.on_target_run_revent, timeout=self.run_timeout) self.logger.debug('Replay completed.') diff --git a/wa/utils/revent.py b/wa/utils/revent.py index f858ab471..0464884da 100644 --- a/wa/utils/revent.py +++ b/wa/utils/revent.py @@ -14,6 +14,7 @@ # +import logging import os import struct import signal @@ -117,22 +118,22 @@ class ReventRecording(object): Represents a parsed revent recording. This contains input events and device descriptions recorded by revent. Two parsing modes are supported. By default, the recording will be parsed in the "streaming" mode. In this - mode, initial headers and device descritions are parsed on creation and an + mode, initial headers and device descriptions are parsed on creation and an open file handle to the recording is saved. Events will be read from the file as they are being iterated over. In this mode, the entire recording is never loaded into memory at once. The underlying file may be "released" by - calling ``close`` on the recroding, after which further iteration over the + calling ``close`` on the recording, after which further iteration over the events will not be possible (but would still be possible to access the file description and header information). The alternative is to load the entire recording on creation (in which case - the file handle will be closed once the recroding is loaded). This can be + the file handle will be closed once the recording is loaded). This can be enabled by specifying ``streaming=False``. This will make it faster to subsequently iterate over the events, and also will not "hold" the file open. .. note:: When starting a new iteration over the events in streaming mode, - the postion in the open file will be automatically reset to the + the position in the open file will be automatically reset to the beginning of the event stream. This means it's possible to iterate over the events multiple times without having to re-open the recording, however it is not possible to do so in parallel. If @@ -274,10 +275,11 @@ def get_revent_binary(abi): class ReventRecorder(object): - # Share location of target excutable across all instances + # Share location of target executable across all instances target_executable = None def __init__(self, target): + self.logger = logging.getLogger(self.__class__.__name__) self.target = target if not ReventRecorder.target_executable: ReventRecorder.target_executable = self._get_target_path(self.target) @@ -295,7 +297,8 @@ class ReventRecorder(object): self.target.uninstall('revent') def start_record(self, revent_file): - command = '{} record -s {}'.format(ReventRecorder.target_executable, revent_file) + command = f'{ReventRecorder.target_executable} record -s {revent_file}' + self.logger.debug('Executing record command "%s"...', command) self.target.kick_off(command, self.target.is_rooted) def stop_record(self): @@ -303,7 +306,8 @@ class ReventRecorder(object): def replay(self, revent_file, timeout=None): self.target.killall('revent') - command = "{} replay {}".format(ReventRecorder.target_executable, revent_file) + command = f'{ReventRecorder.target_executable} replay {revent_file}' + self.logger.debug('Executing replay command "%s" with %d seconds timeout...', command, timeout) self.target.execute(command, timeout=timeout) @memoized diff --git a/wa/workloads/honorofkings/__init__.py b/wa/workloads/honorofkings/__init__.py new file mode 100644 index 000000000..93d404b2d --- /dev/null +++ b/wa/workloads/honorofkings/__init__.py @@ -0,0 +1,61 @@ +# Copyright 2025 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 wa import ApkReventWorkload, Parameter + + +class HoK(ApkReventWorkload): + name = 'honorofkings' + uninstall = False + clear_data_on_reset = False # Don't clear assets on exit + requires_network = True # The game requires network connection + description = ( + 'Launch a match replay in Honor of Kings.\n\n' + 'The game must already have a user logged in and the plugins downloaded.' + ) + package_names = [ + 'com.levelinfinite.sgameGlobal', + 'com.tencent.tmgp.sgame', + ] + + parameters = [ + Parameter( + 'activity', + kind=str, + default='.SGameGlobalActivity', + description='Activity name of Honor of Kings game.', + ), + Parameter( + 'replay_file', + kind=str, + default='replay.abc', + description='Honor of Kings Replay file name.', + ), + ] + + def setup(self, context): + upload_dir = self.target.path.join( + self.target.external_storage_app_dir, + self.apk.apk_info.package, + 'files', + 'Replay' + ) + replay_file = os.path.join(self.dependencies_directory, self.replay_file) + self.logger.debug('Uploading "%s" to "%s"...', replay_file, upload_dir) + self.target.push(replay_file, upload_dir) + + super().setup(context) -- GitLab