diff --git a/external/devlib/LICENSE b/external/devlib/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..28f59a0ea2d061bf74869161b158a09bd4c96ae6
--- /dev/null
+++ b/external/devlib/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2024 Arm Ltd.
+
+ 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.
diff --git a/external/devlib/devlib/__init__.py b/external/devlib/devlib/__init__.py
index dceda0d6258146cc644d0b41c5d84855a67e17a7..e496299b15453a47ceef03577a24314159775b62 100644
--- a/external/devlib/devlib/__init__.py
+++ b/external/devlib/devlib/__init__.py
@@ -22,8 +22,6 @@ from devlib.target import (
ChromeOsTarget,
)
-from devlib.target_runner import QEMUTargetRunner
-
from devlib.host import (
PACKAGE_BIN_DIRECTORY,
LocalConnection,
diff --git a/external/devlib/devlib/target_runner.py b/external/devlib/devlib/_target_runner.py
similarity index 61%
rename from external/devlib/devlib/target_runner.py
rename to external/devlib/devlib/_target_runner.py
index c08c3a1abb351f51e726b091bbcc6927668fb87a..a45612354560bcc64a706ec2404234b7b71ced6c 100644
--- a/external/devlib/devlib/target_runner.py
+++ b/external/devlib/devlib/_target_runner.py
@@ -19,8 +19,6 @@ Target runner and related classes are implemented here.
import logging
import os
-import signal
-import subprocess
import time
from platform import machine
@@ -37,20 +35,41 @@ class TargetRunner:
It mainly aims to provide framework support for QEMU like target runners
(e.g., :class:`QEMUTargetRunner`).
+ :param target: Specifies type of target per :class:`Target` based classes.
+ :type target: Target
+ """
+
+ def __init__(self,
+ target):
+ self.target = target
+
+ self.logger = logging.getLogger(self.__class__.__name__)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *_):
+ pass
+
+
+class SubprocessTargetRunner(TargetRunner):
+ """
+ Class for providing subprocess support to the target runners.
+
:param runner_cmd: The command to start runner process (e.g.,
``qemu-system-aarch64 -kernel Image -append "console=ttyAMA0" ...``).
- :type runner_cmd: str
+ :type runner_cmd: list(str)
:param target: Specifies type of target per :class:`Target` based classes.
:type target: Target
:param connect: Specifies if :class:`TargetRunner` should try to connect
target after launching it, defaults to True.
- :type connect: bool, optional
+ :type connect: bool or None
:param boot_timeout: Timeout for target's being ready for SSH access in
seconds, defaults to 60.
- :type boot_timeout: int, optional
+ :type boot_timeout: int or None
:raises HostError: if it cannot execute runner command successfully.
@@ -62,15 +81,14 @@ class TargetRunner:
target,
connect=True,
boot_timeout=60):
- self.boot_timeout = boot_timeout
- self.target = target
+ super().__init__(target=target)
- self.logger = logging.getLogger(self.__class__.__name__)
+ self.boot_timeout = boot_timeout
self.logger.info('runner_cmd: %s', runner_cmd)
try:
- self.runner_process = get_subprocess(list(runner_cmd.split()))
+ self.runner_process = get_subprocess(runner_cmd)
except Exception as ex:
raise HostError(f'Error while running "{runner_cmd}": {ex}') from ex
@@ -78,20 +96,13 @@ class TargetRunner:
self.wait_boot_complete()
def __enter__(self):
- """
- Complementary method for contextmanager.
-
- :return: Self object.
- :rtype: TargetRunner
- """
-
return self
def __exit__(self, *_):
"""
Exit routine for contextmanager.
- Ensure :attr:`TargetRunner.runner_process` is terminated on exit.
+ Ensure ``SubprocessTargetRunner.runner_process`` is terminated on exit.
"""
self.terminate()
@@ -99,7 +110,7 @@ class TargetRunner:
def wait_boot_complete(self):
"""
Wait for target OS to finish boot up and become accessible over SSH in at most
- :attr:`TargetRunner.boot_timeout` seconds.
+ ``SubprocessTargetRunner.boot_timeout`` seconds.
:raises TargetStableError: In case of timeout.
"""
@@ -109,10 +120,10 @@ class TargetRunner:
while self.boot_timeout >= elapsed:
try:
self.target.connect(timeout=self.boot_timeout - elapsed)
- self.logger.info('Target is ready.')
+ self.logger.debug('Target is ready.')
return
# pylint: disable=broad-except
- except BaseException as ex:
+ except Exception as ex:
self.logger.info('Cannot connect target: %s', ex)
time.sleep(1)
@@ -123,33 +134,41 @@ class TargetRunner:
def terminate(self):
"""
- Terminate :attr:`TargetRunner.runner_process`.
+ Terminate ``SubprocessTargetRunner.runner_process``.
"""
- if self.runner_process is None:
- return
-
- try:
- self.runner_process.stdin.close()
- self.runner_process.stdout.close()
- self.runner_process.stderr.close()
+ self.logger.debug('Killing target runner...')
+ self.runner_process.kill()
+ self.runner_process.__exit__(None, None, None)
- if self.runner_process.poll() is None:
- self.logger.debug('Terminating target runner...')
- os.killpg(self.runner_process.pid, signal.SIGTERM)
- # Wait 3 seconds before killing the runner.
- self.runner_process.wait(timeout=3)
- except subprocess.TimeoutExpired:
- self.logger.info('Killing target runner...')
- os.killpg(self.runner_process.pid, signal.SIGKILL)
+class NOPTargetRunner(TargetRunner):
+ """
+ Class for implementing a target runner which does nothing except providing .target attribute.
-class QEMUTargetRunner(TargetRunner):
+ :param target: Specifies type of target per :class:`Target` based classes.
+ :type target: Target
"""
- Class for interacting with QEMU runners.
- :class:`QEMUTargetRunner` is a subclass of :class:`TargetRunner` which performs necessary
- groundwork for launching a guest OS on QEMU.
+ def __init__(self, target):
+ super().__init__(target=target)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *_):
+ pass
+
+ def terminate(self):
+ """
+ Nothing to terminate for NOP target runners.
+ Defined to be compliant with other runners (e.g., ``SubprocessTargetRunner``).
+ """
+
+
+class QEMUTargetRunner(SubprocessTargetRunner):
+ """
+ Class for preparing necessary groundwork for launching a guest OS on QEMU.
:param qemu_settings: A dictionary which has QEMU related parameters. The full list
of QEMU parameters is below:
@@ -181,12 +200,11 @@ class QEMUTargetRunner(TargetRunner):
:type qemu_settings: Dict
:param connection_settings: the dictionary to store connection settings
- of :attr:`Target.connection_settings`, defaults to None.
- :type connection_settings: Dict, optional
+ of ``Target.connection_settings``, defaults to None.
+ :type connection_settings: Dict or None
- :param make_target: Lambda function for creating :class:`Target` based
- object, defaults to :func:`lambda **kwargs: LinuxTarget(**kwargs)`.
- :type make_target: func, optional
+ :param make_target: Lambda function for creating :class:`Target` based object.
+ :type make_target: func or None
:Variable positional arguments: Forwarded to :class:`TargetRunner`.
@@ -196,9 +214,9 @@ class QEMUTargetRunner(TargetRunner):
def __init__(self,
qemu_settings,
connection_settings=None,
- # pylint: disable=unnecessary-lambda
- make_target=lambda **kwargs: LinuxTarget(**kwargs),
+ make_target=LinuxTarget,
**args):
+
self.connection_settings = {
'host': '127.0.0.1',
'port': 8022,
@@ -206,62 +224,61 @@ class QEMUTargetRunner(TargetRunner):
'password': 'root',
'strict_host_check': False,
}
-
- if connection_settings is not None:
- self.connection_settings = self.connection_settings | connection_settings
+ self.connection_settings = {**self.connection_settings, **(connection_settings or {})}
qemu_args = {
- 'kernel_image': '',
'arch': 'aarch64',
'cpu_type': 'cortex-a72',
- 'initrd_image': '',
'mem_size': 512,
'num_cores': 2,
'num_threads': 2,
'cmdline': 'console=ttyAMA0',
'enable_kvm': True,
}
-
- qemu_args = qemu_args | qemu_settings
+ qemu_args = {**qemu_args, **qemu_settings}
qemu_executable = f'qemu-system-{qemu_args["arch"]}'
qemu_path = which(qemu_executable)
if qemu_path is None:
raise FileNotFoundError(f'Cannot find {qemu_executable} executable!')
- if not os.path.exists(qemu_args["kernel_image"]):
- raise FileNotFoundError(f'{qemu_args["kernel_image"]} does not exist!')
-
- # pylint: disable=consider-using-f-string
- qemu_cmd = '''\
-{} -kernel {} -append "{}" -m {} -smp cores={},threads={} -netdev user,id=net0,hostfwd=tcp::{}-:22 \
--device virtio-net-pci,netdev=net0 --nographic\
-'''.format(
- qemu_path,
- qemu_args["kernel_image"],
- qemu_args["cmdline"],
- qemu_args["mem_size"],
- qemu_args["num_cores"],
- qemu_args["num_threads"],
- self.connection_settings["port"],
- )
-
- if qemu_args["initrd_image"]:
+ if qemu_args.get("kernel_image"):
+ if not os.path.exists(qemu_args["kernel_image"]):
+ raise FileNotFoundError(f'{qemu_args["kernel_image"]} does not exist!')
+ else:
+ raise KeyError('qemu_settings must have kernel_image!')
+
+ qemu_cmd = [qemu_path,
+ '-kernel', qemu_args["kernel_image"],
+ '-append', f"'{qemu_args['cmdline']}'",
+ '-m', str(qemu_args["mem_size"]),
+ '-smp', f'cores={qemu_args["num_cores"]},threads={qemu_args["num_threads"]}',
+ '-netdev', f'user,id=net0,hostfwd=tcp::{self.connection_settings["port"]}-:22',
+ '-device', 'virtio-net-pci,netdev=net0',
+ '--nographic',
+ ]
+
+ if qemu_args.get("initrd_image"):
if not os.path.exists(qemu_args["initrd_image"]):
raise FileNotFoundError(f'{qemu_args["initrd_image"]} does not exist!')
- qemu_cmd += f' -initrd {qemu_args["initrd_image"]}'
+ qemu_cmd.extend(['-initrd', qemu_args["initrd_image"]])
- if qemu_args["arch"] == machine():
- if qemu_args["enable_kvm"]:
- qemu_cmd += ' --enable-kvm'
- else:
- qemu_cmd += f' -machine virt -cpu {qemu_args["cpu_type"]}'
+ if qemu_args["enable_kvm"]:
+ # Enable KVM accelerator if host and guest architectures match.
+ # Comparison is done based on x86 for the sake of simplicity.
+ if (qemu_args['arch'].startswith('x86') and machine().startswith('x86')) or (
+ qemu_args['arch'].startswith('x86') and machine().startswith('x86')):
+ qemu_cmd.append('--enable-kvm')
+
+ # qemu-system-x86_64 does not support -machine virt as of now.
+ if not qemu_args['arch'].startswith('x86'):
+ qemu_cmd.extend(['-machine', 'virt', '-cpu', qemu_args["cpu_type"]])
- self.target = make_target(connect=False,
- conn_cls=SshConnection,
- connection_settings=self.connection_settings)
+ target = make_target(connect=False,
+ conn_cls=SshConnection,
+ connection_settings=self.connection_settings)
super().__init__(runner_cmd=qemu_cmd,
- target=self.target,
+ target=target,
**args)
diff --git a/external/devlib/devlib/bin/scripts/shutils.in b/external/devlib/devlib/bin/scripts/shutils.in
index 6607504e9bdf8d5b430128dd7bd506f0484f9e4f..2e72362e4d65f0e78343167bc226bb3a57a5891b 100755
--- a/external/devlib/devlib/bin/scripts/shutils.in
+++ b/external/devlib/devlib/bin/scripts/shutils.in
@@ -154,7 +154,16 @@ cgroups_run_into() {
# Move this shell into that control group
echo $$ > $CGPATH/cgroup.procs
echo "Moving task into root CGroup ($CGPATH)"
+ # Check the move actually worked
+ $GREP -E "$$" $CGPATH/cgroup.procs >/dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ echo "ERROR: Process was not moved into $CGP"
+ exit 1
+ fi
done
+ if [ $? -ne 0 ]; then
+ exit 1
+ fi
# Execution under specified CGroup
else
@@ -173,8 +182,16 @@ cgroups_run_into() {
# Move this shell into that control group
echo $$ > $CGPATH/cgroup.procs
echo "Moving task into $CGPATH"
+ # Check the move actually worked
+ $GREP -E "$$" $CGPATH/cgroup.procs >/dev/null 2>&1
+ if [ $? -ne 0 ]; then
+ echo "ERROR: Process was not moved into $CGP"
+ exit 1
+ fi
done
-
+ if [ $? -ne 0 ]; then
+ exit 1
+ fi
fi
# Execute the command
diff --git a/external/devlib/devlib/collector/dmesg.py b/external/devlib/devlib/collector/dmesg.py
index 4d4ee60c08639268d07ca8d2a5c8861450f7a90b..617258c405748fe9a2ca15824f1e1975bc459ef3 100644
--- a/external/devlib/devlib/collector/dmesg.py
+++ b/external/devlib/devlib/collector/dmesg.py
@@ -70,7 +70,7 @@ class KernelLogEntry(object):
def parse_raw_level(line):
match = cls._RAW_LEVEL_REGEX.match(line)
if not match:
- raise ValueError('dmesg entry format not recognized: {}'.format(line))
+ raise ValueError(f'dmesg entry format not recognized: {line}')
level, remainder = match.groups()
levels = DmesgCollector.LOG_LEVELS
# BusyBox dmesg can output numbers that need to wrap around
@@ -79,11 +79,15 @@ class KernelLogEntry(object):
def parse_pretty_level(line):
match = cls._PRETTY_LEVEL_REGEX.match(line)
+ if not match:
+ raise ValueError(f'dmesg entry pretty format not recognized: {line}')
facility, level, remainder = match.groups()
return facility, level, remainder
def parse_timestamp_msg(line):
match = cls._TIMESTAMP_MSG_REGEX.match(line)
+ if not match:
+ raise ValueError(f'dmesg entry timestamp format not recognized: {line}')
timestamp, msg = match.groups()
timestamp = timedelta(seconds=float(timestamp.strip()))
return timestamp, msg
diff --git a/external/devlib/devlib/target.py b/external/devlib/devlib/target.py
index 5f2c595a0904856ce8c6d88373d368d6969256fd..753fb96a3620dcee19d32b3dc3b31d8a1858dabf 100644
--- a/external/devlib/devlib/target.py
+++ b/external/devlib/devlib/target.py
@@ -13,6 +13,7 @@
# limitations under the License.
#
+import atexit
import asyncio
from contextlib import contextmanager
import io
@@ -40,6 +41,7 @@ from past.builtins import long
from past.types import basestring
from numbers import Number
from shlex import quote
+from weakref import WeakMethod
try:
from collections.abc import Mapping
except ImportError:
@@ -413,6 +415,10 @@ class Target(object):
))
self._modules = modules
+ atexit.register(
+ WeakMethod(self.disconnect, atexit.unregister)
+ )
+
self._update_modules('early')
if connect:
self.connect(max_async=max_async)
@@ -521,10 +527,32 @@ class Target(object):
def disconnect(self):
connections = self._conn.get_all_values()
+ # Now that we have all the connection objects, we simply reset the TLS
+ # property so that the connections we got will not be reused anywhere.
+ del self._conn
+
+ unused_conns = self._unused_conns
+ self._unused_conns.clear()
+
for conn in itertools.chain(connections, self._unused_conns):
conn.close()
- if self._async_pool is not None:
- self._async_pool.__exit__(None, None, None)
+
+ pool = self._async_pool
+ self._async_pool = None
+ if pool is not None:
+ pool.__exit__(None, None, None)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *args, **kwargs):
+ self.disconnect()
+
+ async def __aenter__(self):
+ return self.__enter__()
+
+ async def __aexit__(self, *args, **kwargs):
+ return self.__exit__(*args, **kwargs)
def get_connection(self, timeout=None):
if self.conn_cls is None:
@@ -1125,20 +1153,20 @@ fi
else:
raise
- @contextmanager
- def make_temp(self, is_directory=True, directory='', prefix='devlib-test'):
+ @asyn.asynccontextmanager
+ async def make_temp(self, is_directory=True, directory='', prefix='devlib-test'):
"""
Creates temporary file/folder on target and deletes it once it's done.
:param is_directory: Specifies if temporary object is a directory, defaults to True.
- :type is_directory: bool, optional
+ :type is_directory: bool or None
:param directory: Temp object will be created under this directory,
- defaults to :attr:`Target.working_directory`.
- :type directory: str, optional
+ defaults to ``Target.working_directory``.
+ :type directory: str or None
- :param prefix: Prefix of temp object's name, defaults to 'devlib-test'.
- :type prefix: str, optional
+ :param prefix: Prefix of temp object's name.
+ :type prefix: str or None
:yield: Full path of temp object.
:rtype: str
@@ -1147,15 +1175,15 @@ fi
directory = directory or self.working_directory
temp_obj = None
try:
- cmd = f'mktemp -p {directory} {prefix}-XXXXXX'
+ cmd = f'mktemp -p {quote(directory)} {quote(prefix)}-XXXXXX'
if is_directory:
cmd += ' -d'
- temp_obj = self.execute(cmd).strip()
+ temp_obj = (await self.execute.asyn(cmd)).strip()
yield temp_obj
finally:
if temp_obj is not None:
- self.remove(temp_obj)
+ await self.remove.asyn(temp_obj)
def reset(self):
try:
diff --git a/external/devlib/devlib/utils/android.py b/external/devlib/devlib/utils/android.py
index 6f2eb87ae60ad331b95287d0e7ada9320e6d6744..81d70c28c0b1b20fe18a8b06893d141d36a63a4f 100755
--- a/external/devlib/devlib/utils/android.py
+++ b/external/devlib/devlib/utils/android.py
@@ -562,7 +562,7 @@ def adb_disconnect(device, adb_server=None, adb_port=None):
adb_cmd = get_adb_command(None, 'disconnect', adb_server, adb_port)
command = "{} {}".format(adb_cmd, device)
logger.debug(command)
- retval = subprocess.call(command, stdout=open(os.devnull, 'wb'), shell=True)
+ retval = subprocess.call(command, stdout=subprocess.DEVNULL, shell=True)
if retval:
raise TargetTransientError('"{}" returned {}'.format(command, retval))
@@ -571,11 +571,13 @@ def _ping(device, adb_server=None, adb_port=None):
adb_cmd = get_adb_command(device, 'shell', adb_server, adb_port)
command = "{} {}".format(adb_cmd, quote('ls /data/local/tmp > /dev/null'))
logger.debug(command)
- result = subprocess.call(command, stderr=subprocess.PIPE, shell=True)
- if not result: # pylint: disable=simplifiable-if-statement
- return True
- else:
+ try:
+ subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
+ except subprocess.CalledProcessError as e:
+ logger.debug(f'ADB ping failed: {e.stdout}')
return False
+ else:
+ return True
# pylint: disable=too-many-locals
diff --git a/external/devlib/devlib/utils/ssh.py b/external/devlib/devlib/utils/ssh.py
index 0eb7db2ba81335a50ef00f79509ed7d0455c5713..bc130226e83a902c5aedf87f25644df4f55e8844 100644
--- a/external/devlib/devlib/utils/ssh.py
+++ b/external/devlib/devlib/utils/ssh.py
@@ -24,14 +24,12 @@ import tempfile
import socket
import sys
import time
-import atexit
import contextlib
import select
import copy
import functools
import shutil
from shlex import quote
-from weakref import WeakMethod
from paramiko.client import SSHClient, AutoAddPolicy, RejectPolicy
import paramiko.ssh_exception
@@ -372,8 +370,6 @@ class SshConnection(SshConnectionBase):
self.client = None
try:
self.client = self._make_client()
- weak_close = WeakMethod(self.close, atexit.unregister)
- atexit.register(weak_close)
# Use a marker in the output so that we will be able to differentiate
# target connection issues with "password needed".
@@ -392,10 +388,13 @@ class SshConnection(SshConnectionBase):
)
)
- except BaseException:
- if self.client is not None:
- self.client.close()
- raise
+ # pylint: disable=broad-except
+ except BaseException as e:
+ try:
+ if self.client is not None:
+ self.client.close()
+ finally:
+ raise e
def _make_client(self):
if self.strict_host_check:
@@ -815,9 +814,6 @@ class TelnetConnection(SshConnectionBase):
self.conn = telnet_get_shell(host, username, password, port, timeout, original_prompt)
- weak_close = WeakMethod(self.close, atexit.unregister)
- atexit.register(weak_close)
-
def fmt_remote_path(self, path):
return '{}@{}:{}'.format(self.username, self.host, path)
diff --git a/external/devlib/tests/target_configs.yaml b/external/devlib/tests/target_configs.yaml
deleted file mode 100644
index 47e00ec4ebf4ea126c3899b12e3ea415dbaf92c3..0000000000000000000000000000000000000000
--- a/external/devlib/tests/target_configs.yaml
+++ /dev/null
@@ -1,5 +0,0 @@
-LocalLinuxTarget:
- entry-0:
- connection_settings:
- unrooted: True
-
diff --git a/external/devlib/tests/target_configs.yaml.example b/external/devlib/tests/target_configs.yaml.example
deleted file mode 100644
index 1154ea8b99ec607825e71680e1ee493f0f184fdd..0000000000000000000000000000000000000000
--- a/external/devlib/tests/target_configs.yaml.example
+++ /dev/null
@@ -1,39 +0,0 @@
-AndroidTarget:
- entry-0:
- timeout: 60
- connection_settings:
- device: 'emulator-5554'
-
-ChromeOsTarget:
- entry-0:
- connection_settings:
- device: 'emulator-5556'
- host: 'example.com'
- username: 'username'
- password: 'password'
-
-LinuxTarget:
- entry-0:
- connection_settings:
- host: 'example.com'
- username: 'username'
- password: 'password'
-
-LocalLinuxTarget:
- entry-0:
- connection_settings:
- unrooted: True
-
-QEMUTargetRunner:
- entry-0:
- qemu_settings:
- kernel_image: '/path/to/devlib/tools/buildroot/buildroot-v2023.11.1-aarch64/output/images/Image'
-
- entry-1:
- connection_settings:
- port : 8023
-
- qemu_settings:
- kernel_image: '/path/to/devlib/tools/buildroot/buildroot-v2023.11.1-x86_64/output/images/bzImage'
- arch: 'x86_64'
- cmdline: 'console=ttyS0'
diff --git a/external/devlib/tests/test_config.yml b/external/devlib/tests/test_config.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6c5b53ba26fb3c2157f238d355d1bf22b87bd2a3
--- /dev/null
+++ b/external/devlib/tests/test_config.yml
@@ -0,0 +1,5 @@
+target-configs:
+ entry-0:
+ LocalLinuxTarget:
+ connection_settings:
+ unrooted: True
diff --git a/external/devlib/tests/test_config.yml.example b/external/devlib/tests/test_config.yml.example
new file mode 100644
index 0000000000000000000000000000000000000000..5e33a6c77216f07a01f5db50fb16adadc20a4de7
--- /dev/null
+++ b/external/devlib/tests/test_config.yml.example
@@ -0,0 +1,40 @@
+target-configs:
+ entry-0:
+ AndroidTarget:
+ timeout: 60
+ connection_settings:
+ device: 'emulator-5554'
+
+ entry-1:
+ ChromeOsTarget:
+ connection_settings:
+ device: 'emulator-5556'
+ host: 'example.com'
+ username: 'username'
+ password: 'password'
+
+ entry-2:
+ LinuxTarget:
+ connection_settings:
+ host: 'example.com'
+ username: 'username'
+ password: 'password'
+
+ entry-3:
+ LocalLinuxTarget:
+ connection_settings:
+ unrooted: True
+
+ entry-4:
+ QEMUTargetRunner:
+ qemu_settings:
+ kernel_image: '/path/to/devlib/tools/buildroot/buildroot-v2023.11.1-aarch64/output/images/Image'
+
+ entry-5:
+ QEMUTargetRunner:
+ connection_settings:
+ port: 8023
+ qemu_settings:
+ kernel_image: '/path/to/devlib/tools/buildroot/buildroot-v2023.11.1-x86_64/output/images/bzImage'
+ arch: 'x86_64'
+ cmdline: 'console=ttyS0'
diff --git a/external/devlib/tests/test_target.py b/external/devlib/tests/test_target.py
index 2d59a2374c6cc49ddeb1bf706c99a9be552d8389..2d811321fcce6c8b9f6d7fc904cb09189dc53782 100644
--- a/external/devlib/tests/test_target.py
+++ b/external/devlib/tests/test_target.py
@@ -14,136 +14,168 @@
# limitations under the License.
#
-"""Module for testing targets."""
+"""
+Module for testing targets.
+Sample run with log level is set to DEBUG (see
+https://docs.pytest.org/en/7.1.x/how-to/logging.html#live-logs for logging details):
+
+$ python -m pytest --log-cli-level DEBUG test_target.py
+"""
+
+import logging
import os
-from pprint import pp
import pytest
-from devlib import AndroidTarget, ChromeOsTarget, LinuxTarget, LocalLinuxTarget, QEMUTargetRunner
+from devlib import AndroidTarget, ChromeOsTarget, LinuxTarget, LocalLinuxTarget
+from devlib._target_runner import NOPTargetRunner, QEMUTargetRunner
from devlib.utils.android import AdbConnection
from devlib.utils.misc import load_struct_from_yaml
-def build_targets():
- """Read targets from a YAML formatted config file"""
+logger = logging.getLogger('test_target')
- config_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'target_configs.yaml')
- target_configs = load_struct_from_yaml(config_file)
- if target_configs is None:
+def get_class_object(name):
+ """
+ Get associated class object from string formatted class name
+
+ :param name: Class name
+ :type name: str
+ :return: Class object
+ :rtype: object or None
+ """
+ if globals().get(name) is None:
+ return None
+
+ return globals()[name] if issubclass(globals()[name], object) else None
+
+
+@pytest.fixture(scope="module")
+# pylint: disable=too-many-branches
+def build_target_runners():
+ """Read targets from a YAML formatted config file and create runners for them"""
+
+ logger.info("Initializing resources...")
+
+ config_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'test_config.yml')
+
+ test_config = load_struct_from_yaml(config_file)
+ if test_config is None:
raise ValueError(f'{config_file} looks empty!')
- targets = []
+ target_configs = test_config.get('target-configs')
+ if target_configs is None:
+ raise ValueError('No targets found!')
+
+ target_runners = []
- if target_configs.get('AndroidTarget') is not None:
- print('> Android targets:')
- for entry in target_configs['AndroidTarget'].values():
- pp(entry)
+ for entry in target_configs.values():
+ key, target_info = entry.popitem()
+
+ target_class = get_class_object(key)
+ if target_class is AndroidTarget:
+ logger.info('> Android target: %s', repr(target_info))
a_target = AndroidTarget(
connect=False,
- connection_settings=entry['connection_settings'],
+ connection_settings=target_info['connection_settings'],
conn_cls=lambda **kwargs: AdbConnection(adb_as_root=True, **kwargs),
)
- a_target.connect(timeout=entry.get('timeout', 60))
- targets.append((a_target, None))
-
- if target_configs.get('LinuxTarget') is not None:
- print('> Linux targets:')
- for entry in target_configs['LinuxTarget'].values():
- pp(entry)
- l_target = LinuxTarget(connection_settings=entry['connection_settings'])
- targets.append((l_target, None))
-
- if target_configs.get('ChromeOsTarget') is not None:
- print('> ChromeOS targets:')
- for entry in target_configs['ChromeOsTarget'].values():
- pp(entry)
+ a_target.connect(timeout=target_info.get('timeout', 60))
+ target_runners.append(NOPTargetRunner(a_target))
+
+ elif target_class is ChromeOsTarget:
+ logger.info('> ChromeOS target: %s', repr(target_info))
c_target = ChromeOsTarget(
- connection_settings=entry['connection_settings'],
+ connection_settings=target_info['connection_settings'],
working_directory='/tmp/devlib-target',
)
- targets.append((c_target, None))
-
- if target_configs.get('LocalLinuxTarget') is not None:
- print('> LocalLinux targets:')
- for entry in target_configs['LocalLinuxTarget'].values():
- pp(entry)
- ll_target = LocalLinuxTarget(connection_settings=entry['connection_settings'])
- targets.append((ll_target, None))
-
- if target_configs.get('QEMUTargetRunner') is not None:
- print('> QEMU target runners:')
- for entry in target_configs['QEMUTargetRunner'].values():
- pp(entry)
- qemu_settings = entry.get('qemu_settings') and entry['qemu_settings']
- connection_settings = entry.get(
- 'connection_settings') and entry['connection_settings']
+ target_runners.append(NOPTargetRunner(c_target))
+
+ elif target_class is LinuxTarget:
+ logger.info('> Linux target: %s', repr(target_info))
+ l_target = LinuxTarget(connection_settings=target_info['connection_settings'])
+ target_runners.append(NOPTargetRunner(l_target))
+
+ elif target_class is LocalLinuxTarget:
+ logger.info('> LocalLinux target: %s', repr(target_info))
+ ll_target = LocalLinuxTarget(connection_settings=target_info['connection_settings'])
+ target_runners.append(NOPTargetRunner(ll_target))
+
+ elif target_class is QEMUTargetRunner:
+ logger.info('> QEMU target runner: %s', repr(target_info))
qemu_runner = QEMUTargetRunner(
- qemu_settings=qemu_settings,
- connection_settings=connection_settings,
+ qemu_settings=target_info.get('qemu_settings'),
+ connection_settings=target_info.get('connection_settings'),
)
- if entry.get('ChromeOsTarget') is None:
- targets.append((qemu_runner.target, qemu_runner))
- continue
+ if target_info.get('ChromeOsTarget') is not None:
+ # Leave termination of QEMU runner to ChromeOS target.
+ target_runners.append(NOPTargetRunner(qemu_runner.target))
- # Leave termination of QEMU runner to ChromeOS target.
- targets.append((qemu_runner.target, None))
+ logger.info('>> ChromeOS target: %s', repr(target_info["ChromeOsTarget"]))
+ qemu_runner.target = ChromeOsTarget(
+ connection_settings={
+ **target_info['ChromeOsTarget']['connection_settings'],
+ **qemu_runner.target.connection_settings,
+ },
+ working_directory='/tmp/devlib-target',
+ )
- print('> ChromeOS targets:')
- pp(entry['ChromeOsTarget'])
- c_target = ChromeOsTarget(
- connection_settings={
- **entry['ChromeOsTarget']['connection_settings'],
- **qemu_runner.target.connection_settings,
- },
- working_directory='/tmp/devlib-target',
- )
- targets.append((c_target, qemu_runner))
+ target_runners.append(qemu_runner)
+
+ else:
+ raise ValueError(f'Unknown target type {key}!')
+
+ yield target_runners
- return targets
+ logger.info("Destroying resources...")
+ for target_runner in target_runners:
+ target = target_runner.target
-@pytest.mark.parametrize("target, target_runner", build_targets())
-def test_read_multiline_values(target, target_runner):
+ # TODO: Revisit per https://github.com/ARM-software/devlib/issues/680.
+ logger.debug('Removing %s...', target.working_directory)
+ target.remove(target.working_directory)
+
+ target_runner.terminate()
+
+
+# pylint: disable=redefined-outer-name
+def test_read_multiline_values(build_target_runners):
"""
Test Target.read_tree_values_flat()
- :param target: Type of target per :class:`Target` based classes.
- :type target: Target
-
- :param target_runner: Target runner object to terminate target (if necessary).
- :type target: TargetRunner
+ Runs tests around ``Target.read_tree_values_flat()`` for ``TargetRunner`` objects.
"""
+ logger.info('Running test_read_multiline_values test...')
+
data = {
'test1': '1',
'test2': '2\n\n',
'test3': '3\n\n4\n\n',
}
- print(f'target={target.__class__.__name__} os={target.os} hostname={target.hostname}')
+ target_runners = build_target_runners
+ for target_runner in target_runners:
+ target = target_runner.target
- with target.make_temp() as tempdir:
- print(f'Created {tempdir}.')
+ logger.info('target=%s os=%s hostname=%s',
+ target.__class__.__name__, target.os, target.hostname)
- for key, value in data.items():
- path = os.path.join(tempdir, key)
- print(f'Writing {value!r} to {path}...')
- target.write_value(path, value, verify=False,
- as_root=target.conn.connected_as_root)
+ with target.make_temp() as tempdir:
+ logger.debug('Created %s.', tempdir)
- print('Reading values from target...')
- raw_result = target.read_tree_values_flat(tempdir)
- result = {os.path.basename(k): v for k, v in raw_result.items()}
+ for key, value in data.items():
+ path = os.path.join(tempdir, key)
+ logger.debug('Writing %s to %s...', repr(value), path)
+ target.write_value(path, value, verify=False,
+ as_root=target.conn.connected_as_root)
- print(f'Removing {target.working_directory}...')
- target.remove(target.working_directory)
-
- if target_runner is not None:
- print('Terminating target runner...')
- target_runner.terminate()
+ logger.debug('Reading values from target...')
+ raw_result = target.read_tree_values_flat(tempdir)
+ result = {os.path.basename(k): v for k, v in raw_result.items()}
- assert {k: v.strip() for k, v in data.items()} == result
+ assert {k: v.strip() for k, v in data.items()} == result
diff --git a/external/devlib/tools/android/install_base.sh b/external/devlib/tools/android/install_base.sh
index 5335d5981aedb25e1be4565d89dee959b6ebd246..d1ea7612d944f0bd8d2fc3faa8e20cca0689d84e 100755
--- a/external/devlib/tools/android/install_base.sh
+++ b/external/devlib/tools/android/install_base.sh
@@ -37,21 +37,21 @@ test_os_release() {
if [[ "$(read_os_release "${field_name}")" == "${value}" ]]; then
return 0
- else
- return 1
fi
+ return 1
}
-function set_host_arch
-{
- # Google ABI type for Arm platforms
- HOST_ARCH="arm64-v8a"
+get_android_sdk_host_arch() {
+ # Default to Google ABI type for Arm platforms
+ local arch="arm64-v8a"
+ local machine
+
+ machine=$(uname -m)
+ if [[ "${machine}" == "x86"* ]]; then
+ arch=${machine}
+ fi
- local machine
- machine=$(uname -m)
- if [[ "${machine}" == "x86"* ]]; then
- HOST_ARCH=${machine}
- fi
+ echo "${arch}"
}
ANDROID_HOME="$(dirname "${0}")/android-sdk-linux"
@@ -60,7 +60,7 @@ export ANDROID_USER_HOME="${ANDROID_HOME}/.android"
mkdir -p "${ANDROID_HOME}/cmdline-tools"
-CMDLINE_VERSION=${CMDLINE_VERSION:-"11076708"}
+ANDROID_CMDLINE_VERSION=${ANDROID_CMDLINE_VERSION:-"11076708"}
cleanup_android_home() {
echo "Cleaning up Android SDK: ${ANDROID_HOME}"
@@ -72,7 +72,7 @@ install_android_sdk_manager() {
echo "Installing Android SDK manager ..."
# URL taken from "Command line tools only": https://developer.android.com/studio
- local url="https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_VERSION}_latest.zip"
+ local url="https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_CMDLINE_VERSION}_latest.zip"
echo "Downloading Android SDK manager from: $url"
wget -qO- "${url}" | bsdtar -xf- -C "${ANDROID_HOME}/cmdline-tools"
@@ -117,29 +117,34 @@ call_android_avdmanager() {
# Needs install_android_sdk_manager first
install_android_tools() {
+ local android_sdk_host_arch
+ android_sdk_host_arch=$(get_android_sdk_host_arch)
+
yes | call_android_sdkmanager --verbose --channel=0 --install "platform-tools"
yes | call_android_sdkmanager --verbose --channel=0 --install "platforms;android-31"
yes | call_android_sdkmanager --verbose --channel=0 --install "platforms;android-33"
yes | call_android_sdkmanager --verbose --channel=0 --install "platforms;android-34"
- yes | call_android_sdkmanager --verbose --channel=0 --install "system-images;android-31;google_apis;${HOST_ARCH}"
- yes | call_android_sdkmanager --verbose --channel=0 --install "system-images;android-33;android-desktop;${HOST_ARCH}"
- yes | call_android_sdkmanager --verbose --channel=0 --install "system-images;android-34;google_apis;${HOST_ARCH}"
+ yes | call_android_sdkmanager --verbose --channel=0 --install "system-images;android-31;google_apis;${android_sdk_host_arch}"
+ yes | call_android_sdkmanager --verbose --channel=0 --install "system-images;android-33;android-desktop;${android_sdk_host_arch}"
+ yes | call_android_sdkmanager --verbose --channel=0 --install "system-images;android-34;google_apis;${android_sdk_host_arch}"
}
create_android_vds() {
- local vd_name
+ local android_sdk_host_arch
+ android_sdk_host_arch=$(get_android_sdk_host_arch)
+ local vd_name
vd_name="devlib-p6-12"
echo "Creating virtual device \"${vd_name}\" (Pixel 6 - Android 12)..."
- echo no | call_android_avdmanager -s create avd -n "${vd_name}" -k "system-images;android-31;google_apis;${HOST_ARCH}" --skin pixel_6 -b "${HOST_ARCH}" -f
+ echo no | call_android_avdmanager -s create avd -n "${vd_name}" -k "system-images;android-31;google_apis;${android_sdk_host_arch}" --skin pixel_6 -b "${android_sdk_host_arch}" -f
vd_name="devlib-p6-14"
echo "Creating virtual device \"${vd_name}\" (Pixel 6 - Android 14)..."
- echo no | call_android_avdmanager -s create avd -n "${vd_name}" -k "system-images;android-34;google_apis;${HOST_ARCH}" --skin pixel_6 -b "${HOST_ARCH}" -f
+ echo no | call_android_avdmanager -s create avd -n "${vd_name}" -k "system-images;android-34;google_apis;${android_sdk_host_arch}" --skin pixel_6 -b "${android_sdk_host_arch}" -f
vd_name="devlib-chromeos"
echo "Creating virtual device \"${vd_name}\" (ChromeOS - Android 13, Pixel tablet)..."
- echo no | call_android_avdmanager -s create avd -n "${vd_name}" -k "system-images;android-33;android-desktop;${HOST_ARCH}" --skin pixel_tablet -b "${HOST_ARCH}" -f
+ echo no | call_android_avdmanager -s create avd -n "${vd_name}" -k "system-images;android-33;android-desktop;${android_sdk_host_arch}" --skin pixel_tablet -b "${android_sdk_host_arch}" -f
}
install_apt() {
@@ -183,7 +188,6 @@ if which apt-get &>/dev/null; then
install_functions+=(install_apt)
package_manager='apt-get'
expected_distro="Ubuntu"
-
elif which pacman &>/dev/null; then
install_functions+=(install_pacman)
package_manager="pacman"
@@ -223,8 +227,6 @@ else
args=("$@")
fi
-set_host_arch
-
# Use conditional fall-through ;;& to all matching all branches with
# --install-all
for arg in "${args[@]}"; do
diff --git a/external/devlib/tools/docker/Dockerfile b/external/devlib/tools/docker/Dockerfile
index e52a7d613c257d88388322f64c54951a1a3aec2a..b96a90352bce282b185efaf4cc05fe88948b4ce7 100644
--- a/external/devlib/tools/docker/Dockerfile
+++ b/external/devlib/tools/docker/Dockerfile
@@ -63,9 +63,9 @@ RUN cd /devlib && \
pip install --upgrade pip setuptools wheel && \
pip install .[full]
-# Set CMDLINE_VERSION environment variable if you want to use a specific
-# version of Android command line tools rather than default which is
-# ``11076708`` as of writing this comment.
+# Set ANDROID_CMDLINE_VERSION environment variable if you want to use a
+# specific version of Android command line tools rather than default
+# which is ``11076708`` as of writing this comment.
RUN cd /devlib/tools/android && ./install_base.sh
# Set BUILDROOT_VERSION environment variable if you want to use a specific
diff --git a/external/devlib/tools/docker/target_configs.yaml b/external/devlib/tools/docker/target_configs.yaml
deleted file mode 100644
index d616822c30d363b41d48fb8c5f4ad464a19f0f92..0000000000000000000000000000000000000000
--- a/external/devlib/tools/docker/target_configs.yaml
+++ /dev/null
@@ -1,43 +0,0 @@
-AndroidTarget:
- # Android-12, Pixel-6
- entry-0:
- timeout: 60
- connection_settings:
- device: 'emulator-5554'
-
- # Android-14, Pixel-6
- entry-1:
- connection_settings:
- device: 'emulator-5556'
-
- # Android-13, Pixel tablet
- entry-2:
- connection_settings:
- device: 'emulator-5558'
-
-LocalLinuxTarget:
- entry-0:
- connection_settings:
- unrooted: True
-
-QEMUTargetRunner:
- entry-0:
- qemu_settings:
- kernel_image: '/devlib/tools/buildroot/buildroot-v2023.11.1-aarch64/output/images/Image'
-
- ChromeOsTarget:
- connection_settings:
- device: 'emulator-5558'
-
- entry-1:
- connection_settings:
- port: 8023
-
- qemu_settings:
- kernel_image: '/devlib/tools/buildroot/buildroot-v2023.11.1-x86_64/output/images/bzImage'
- arch: 'x86_64'
- cmdline: 'console=ttyS0'
-
- ChromeOsTarget:
- connection_settings:
- device: 'emulator-5558'
diff --git a/external/devlib/tools/docker/target_configs.yml b/external/devlib/tools/docker/target_configs.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f6b74210f69373a48e88ed41a379f005374f580b
--- /dev/null
+++ b/external/devlib/tools/docker/target_configs.yml
@@ -0,0 +1,46 @@
+target-configs:
+ entry-0:
+ # Android-12, Pixel-6
+ AndroidTarget:
+ timeout: 60
+ connection_settings:
+ device: 'emulator-5554'
+
+ entry-1:
+ # Android-14, Pixel-6
+ AndroidTarget:
+ connection_settings:
+ device: 'emulator-5556'
+
+ entry-2:
+ # Android-13, Pixel tablet
+ AndroidTarget:
+ connection_settings:
+ device: 'emulator-5558'
+
+ entry-3:
+ LocalLinuxTarget:
+ connection_settings:
+ unrooted: True
+
+ entry-4:
+ # aarch64 target
+ QEMUTargetRunner:
+ qemu_settings:
+ kernel_image: '/devlib/tools/buildroot/buildroot-v2023.11.1-aarch64/output/images/Image'
+ ChromeOsTarget:
+ connection_settings:
+ device: 'emulator-5558'
+
+ entry-5:
+ # x86_64 target
+ QEMUTargetRunner:
+ connection_settings:
+ port: 8023
+ qemu_settings:
+ kernel_image: '/devlib/tools/buildroot/buildroot-v2023.11.1-x86_64/output/images/bzImage'
+ arch: 'x86_64'
+ cmdline: 'console=ttyS0'
+ ChromeOsTarget:
+ connection_settings:
+ device: 'emulator-5558'
diff --git a/external/workload-automation/requirements.txt b/external/workload-automation/requirements.txt
index aff5658588e3082393755ccbe91188984f9766cb..de0f3c5365a5f13aefc51e351bf0d7b3a900b0fb 100644
--- a/external/workload-automation/requirements.txt
+++ b/external/workload-automation/requirements.txt
@@ -6,7 +6,7 @@ colorama==0.4.6
cryptography==42.0.4
devlib==1.3.4
future==0.18.3
-idna==3.4
+idna==3.7
Louie-latest==1.3.1
lxml==4.9.2
nose==1.3.7
@@ -21,7 +21,7 @@ pyserial==3.5
python-dateutil==2.8.2
pytz==2023.3
PyYAML==6.0
-requests==2.31.0
+requests==2.32.0
scp==0.14.5
six==1.16.0
tzdata==2023.3
diff --git a/external/workload-automation/wa/workloads/speedometer/__init__.py b/external/workload-automation/wa/workloads/speedometer/__init__.py
index 202554dc5b86c67cd889935bf80d1478dee07bc9..c225feecc776d548a4100bcf4713cb12124de4df 100755
--- a/external/workload-automation/wa/workloads/speedometer/__init__.py
+++ b/external/workload-automation/wa/workloads/speedometer/__init__.py
@@ -204,6 +204,9 @@ class Speedometer(Workload):
'\n',
'\n',
'\n',
+ # Add a 'request count' value to dismiss the pop-up window on the screen.
+ # If the value is greater than 1, pop-up window will be dismissed.
+ '\n',
]:
lines.insert(len(lines) - 1, line)