mirror of
https://github.com/ARM-software/devlib.git
synced 2025-01-31 02:00:45 +00:00
target: tests: Address review comments on PR#667
PR#667: https://github.com/ARM-software/devlib/pull/667 - Implement a test module initializer with a tear down method in test/test_target.py - Make various cleanups in test/test_target.py - Improve structure of test/test_config.yml (previously target_configs.yaml) - Make docstrings Sphinx compatible - Make ``TargetRunner`` and its subclasses private - Cleanup tests/test_target.py - Replace print()'s with appropriate logging calls - Implement ``NOPTargetRunner`` class for simplifying tests - Improve Python v3.7 compatibility - Relax host machine type checking - Escape user input strings and more.. Signed-off-by: Metin Kaya <metin.kaya@arm.com>
This commit is contained in:
parent
7276097d4e
commit
492d42dddb
@ -22,8 +22,6 @@ from devlib.target import (
|
|||||||
ChromeOsTarget,
|
ChromeOsTarget,
|
||||||
)
|
)
|
||||||
|
|
||||||
from devlib.target_runner import QEMUTargetRunner
|
|
||||||
|
|
||||||
from devlib.host import (
|
from devlib.host import (
|
||||||
PACKAGE_BIN_DIRECTORY,
|
PACKAGE_BIN_DIRECTORY,
|
||||||
LocalConnection,
|
LocalConnection,
|
||||||
|
@ -19,8 +19,6 @@ Target runner and related classes are implemented here.
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import signal
|
|
||||||
import subprocess
|
|
||||||
import time
|
import time
|
||||||
from platform import machine
|
from platform import machine
|
||||||
|
|
||||||
@ -37,20 +35,41 @@ class TargetRunner:
|
|||||||
It mainly aims to provide framework support for QEMU like target runners
|
It mainly aims to provide framework support for QEMU like target runners
|
||||||
(e.g., :class:`QEMUTargetRunner`).
|
(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.,
|
:param runner_cmd: The command to start runner process (e.g.,
|
||||||
``qemu-system-aarch64 -kernel Image -append "console=ttyAMA0" ...``).
|
``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.
|
:param target: Specifies type of target per :class:`Target` based classes.
|
||||||
:type target: Target
|
:type target: Target
|
||||||
|
|
||||||
:param connect: Specifies if :class:`TargetRunner` should try to connect
|
:param connect: Specifies if :class:`TargetRunner` should try to connect
|
||||||
target after launching it, defaults to True.
|
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
|
:param boot_timeout: Timeout for target's being ready for SSH access in
|
||||||
seconds, defaults to 60.
|
seconds, defaults to 60.
|
||||||
:type boot_timeout: int, optional
|
:type boot_timeout: int or None
|
||||||
|
|
||||||
:raises HostError: if it cannot execute runner command successfully.
|
:raises HostError: if it cannot execute runner command successfully.
|
||||||
|
|
||||||
@ -62,15 +81,14 @@ class TargetRunner:
|
|||||||
target,
|
target,
|
||||||
connect=True,
|
connect=True,
|
||||||
boot_timeout=60):
|
boot_timeout=60):
|
||||||
self.boot_timeout = boot_timeout
|
super().__init__(target=target)
|
||||||
self.target = target
|
|
||||||
|
|
||||||
self.logger = logging.getLogger(self.__class__.__name__)
|
self.boot_timeout = boot_timeout
|
||||||
|
|
||||||
self.logger.info('runner_cmd: %s', runner_cmd)
|
self.logger.info('runner_cmd: %s', runner_cmd)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.runner_process = get_subprocess(list(runner_cmd.split()))
|
self.runner_process = get_subprocess(runner_cmd)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise HostError(f'Error while running "{runner_cmd}": {ex}') from ex
|
raise HostError(f'Error while running "{runner_cmd}": {ex}') from ex
|
||||||
|
|
||||||
@ -78,20 +96,13 @@ class TargetRunner:
|
|||||||
self.wait_boot_complete()
|
self.wait_boot_complete()
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
"""
|
|
||||||
Complementary method for contextmanager.
|
|
||||||
|
|
||||||
:return: Self object.
|
|
||||||
:rtype: TargetRunner
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *_):
|
def __exit__(self, *_):
|
||||||
"""
|
"""
|
||||||
Exit routine for contextmanager.
|
Exit routine for contextmanager.
|
||||||
|
|
||||||
Ensure :attr:`TargetRunner.runner_process` is terminated on exit.
|
Ensure ``SubprocessTargetRunner.runner_process`` is terminated on exit.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.terminate()
|
self.terminate()
|
||||||
@ -99,7 +110,7 @@ class TargetRunner:
|
|||||||
def wait_boot_complete(self):
|
def wait_boot_complete(self):
|
||||||
"""
|
"""
|
||||||
Wait for target OS to finish boot up and become accessible over SSH in at most
|
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.
|
:raises TargetStableError: In case of timeout.
|
||||||
"""
|
"""
|
||||||
@ -109,10 +120,10 @@ class TargetRunner:
|
|||||||
while self.boot_timeout >= elapsed:
|
while self.boot_timeout >= elapsed:
|
||||||
try:
|
try:
|
||||||
self.target.connect(timeout=self.boot_timeout - elapsed)
|
self.target.connect(timeout=self.boot_timeout - elapsed)
|
||||||
self.logger.info('Target is ready.')
|
self.logger.debug('Target is ready.')
|
||||||
return
|
return
|
||||||
# pylint: disable=broad-except
|
# pylint: disable=broad-except
|
||||||
except BaseException as ex:
|
except Exception as ex:
|
||||||
self.logger.info('Cannot connect target: %s', ex)
|
self.logger.info('Cannot connect target: %s', ex)
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
@ -123,33 +134,41 @@ class TargetRunner:
|
|||||||
|
|
||||||
def terminate(self):
|
def terminate(self):
|
||||||
"""
|
"""
|
||||||
Terminate :attr:`TargetRunner.runner_process`.
|
Terminate ``SubprocessTargetRunner.runner_process``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.runner_process is None:
|
self.logger.debug('Killing target runner...')
|
||||||
return
|
self.runner_process.kill()
|
||||||
|
self.runner_process.__exit__(None, None, None)
|
||||||
try:
|
|
||||||
self.runner_process.stdin.close()
|
|
||||||
self.runner_process.stdout.close()
|
|
||||||
self.runner_process.stderr.close()
|
|
||||||
|
|
||||||
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 QEMUTargetRunner(TargetRunner):
|
class NOPTargetRunner(TargetRunner):
|
||||||
"""
|
"""
|
||||||
Class for interacting with QEMU runners.
|
Class for implementing a target runner which does nothing except providing .target attribute.
|
||||||
|
|
||||||
:class:`QEMUTargetRunner` is a subclass of :class:`TargetRunner` which performs necessary
|
:param target: Specifies type of target per :class:`Target` based classes.
|
||||||
groundwork for launching a guest OS on QEMU.
|
:type target: Target
|
||||||
|
"""
|
||||||
|
|
||||||
|
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
|
:param qemu_settings: A dictionary which has QEMU related parameters. The full list
|
||||||
of QEMU parameters is below:
|
of QEMU parameters is below:
|
||||||
@ -181,12 +200,11 @@ class QEMUTargetRunner(TargetRunner):
|
|||||||
:type qemu_settings: Dict
|
:type qemu_settings: Dict
|
||||||
|
|
||||||
:param connection_settings: the dictionary to store connection settings
|
:param connection_settings: the dictionary to store connection settings
|
||||||
of :attr:`Target.connection_settings`, defaults to None.
|
of ``Target.connection_settings``, defaults to None.
|
||||||
:type connection_settings: Dict, optional
|
:type connection_settings: Dict or None
|
||||||
|
|
||||||
:param make_target: Lambda function for creating :class:`Target` based
|
:param make_target: Lambda function for creating :class:`Target` based object.
|
||||||
object, defaults to :func:`lambda **kwargs: LinuxTarget(**kwargs)`.
|
:type make_target: func or None
|
||||||
:type make_target: func, optional
|
|
||||||
|
|
||||||
:Variable positional arguments: Forwarded to :class:`TargetRunner`.
|
:Variable positional arguments: Forwarded to :class:`TargetRunner`.
|
||||||
|
|
||||||
@ -196,9 +214,9 @@ class QEMUTargetRunner(TargetRunner):
|
|||||||
def __init__(self,
|
def __init__(self,
|
||||||
qemu_settings,
|
qemu_settings,
|
||||||
connection_settings=None,
|
connection_settings=None,
|
||||||
# pylint: disable=unnecessary-lambda
|
make_target=LinuxTarget,
|
||||||
make_target=lambda **kwargs: LinuxTarget(**kwargs),
|
|
||||||
**args):
|
**args):
|
||||||
|
|
||||||
self.connection_settings = {
|
self.connection_settings = {
|
||||||
'host': '127.0.0.1',
|
'host': '127.0.0.1',
|
||||||
'port': 8022,
|
'port': 8022,
|
||||||
@ -206,62 +224,61 @@ class QEMUTargetRunner(TargetRunner):
|
|||||||
'password': 'root',
|
'password': 'root',
|
||||||
'strict_host_check': False,
|
'strict_host_check': False,
|
||||||
}
|
}
|
||||||
|
self.connection_settings = {**self.connection_settings, **(connection_settings or {})}
|
||||||
if connection_settings is not None:
|
|
||||||
self.connection_settings = self.connection_settings | connection_settings
|
|
||||||
|
|
||||||
qemu_args = {
|
qemu_args = {
|
||||||
'kernel_image': '',
|
|
||||||
'arch': 'aarch64',
|
'arch': 'aarch64',
|
||||||
'cpu_type': 'cortex-a72',
|
'cpu_type': 'cortex-a72',
|
||||||
'initrd_image': '',
|
|
||||||
'mem_size': 512,
|
'mem_size': 512,
|
||||||
'num_cores': 2,
|
'num_cores': 2,
|
||||||
'num_threads': 2,
|
'num_threads': 2,
|
||||||
'cmdline': 'console=ttyAMA0',
|
'cmdline': 'console=ttyAMA0',
|
||||||
'enable_kvm': True,
|
'enable_kvm': True,
|
||||||
}
|
}
|
||||||
|
qemu_args = {**qemu_args, **qemu_settings}
|
||||||
qemu_args = qemu_args | qemu_settings
|
|
||||||
|
|
||||||
qemu_executable = f'qemu-system-{qemu_args["arch"]}'
|
qemu_executable = f'qemu-system-{qemu_args["arch"]}'
|
||||||
qemu_path = which(qemu_executable)
|
qemu_path = which(qemu_executable)
|
||||||
if qemu_path is None:
|
if qemu_path is None:
|
||||||
raise FileNotFoundError(f'Cannot find {qemu_executable} executable!')
|
raise FileNotFoundError(f'Cannot find {qemu_executable} executable!')
|
||||||
|
|
||||||
if not os.path.exists(qemu_args["kernel_image"]):
|
if qemu_args.get("kernel_image"):
|
||||||
raise FileNotFoundError(f'{qemu_args["kernel_image"]} does not exist!')
|
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!')
|
||||||
|
|
||||||
# pylint: disable=consider-using-f-string
|
qemu_cmd = [qemu_path,
|
||||||
qemu_cmd = '''\
|
'-kernel', qemu_args["kernel_image"],
|
||||||
{} -kernel {} -append "{}" -m {} -smp cores={},threads={} -netdev user,id=net0,hostfwd=tcp::{}-:22 \
|
'-append', f"'{qemu_args['cmdline']}'",
|
||||||
-device virtio-net-pci,netdev=net0 --nographic\
|
'-m', str(qemu_args["mem_size"]),
|
||||||
'''.format(
|
'-smp', f'cores={qemu_args["num_cores"]},threads={qemu_args["num_threads"]}',
|
||||||
qemu_path,
|
'-netdev', f'user,id=net0,hostfwd=tcp::{self.connection_settings["port"]}-:22',
|
||||||
qemu_args["kernel_image"],
|
'-device', 'virtio-net-pci,netdev=net0',
|
||||||
qemu_args["cmdline"],
|
'--nographic',
|
||||||
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("initrd_image"):
|
||||||
if not os.path.exists(qemu_args["initrd_image"]):
|
if not os.path.exists(qemu_args["initrd_image"]):
|
||||||
raise FileNotFoundError(f'{qemu_args["initrd_image"]} does not exist!')
|
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"]:
|
||||||
if qemu_args["enable_kvm"]:
|
# Enable KVM accelerator if host and guest architectures match.
|
||||||
qemu_cmd += ' --enable-kvm'
|
# Comparison is done based on x86 for the sake of simplicity.
|
||||||
else:
|
if (qemu_args['arch'].startswith('x86') and machine().startswith('x86')) or (
|
||||||
qemu_cmd += f' -machine virt -cpu {qemu_args["cpu_type"]}'
|
qemu_args['arch'].startswith('x86') and machine().startswith('x86')):
|
||||||
|
qemu_cmd.append('--enable-kvm')
|
||||||
|
|
||||||
self.target = make_target(connect=False,
|
# qemu-system-x86_64 does not support -machine virt as of now.
|
||||||
conn_cls=SshConnection,
|
if not qemu_args['arch'].startswith('x86'):
|
||||||
connection_settings=self.connection_settings)
|
qemu_cmd.extend(['-machine', 'virt', '-cpu', qemu_args["cpu_type"]])
|
||||||
|
|
||||||
|
target = make_target(connect=False,
|
||||||
|
conn_cls=SshConnection,
|
||||||
|
connection_settings=self.connection_settings)
|
||||||
|
|
||||||
super().__init__(runner_cmd=qemu_cmd,
|
super().__init__(runner_cmd=qemu_cmd,
|
||||||
target=self.target,
|
target=target,
|
||||||
**args)
|
**args)
|
@ -1131,14 +1131,14 @@ fi
|
|||||||
Creates temporary file/folder on target and deletes it once it's done.
|
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.
|
: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,
|
:param directory: Temp object will be created under this directory,
|
||||||
defaults to :attr:`Target.working_directory`.
|
defaults to ``Target.working_directory``.
|
||||||
:type directory: str, optional
|
:type directory: str or None
|
||||||
|
|
||||||
:param prefix: Prefix of temp object's name, defaults to 'devlib-test'.
|
:param prefix: Prefix of temp object's name.
|
||||||
:type prefix: str, optional
|
:type prefix: str or None
|
||||||
|
|
||||||
:yield: Full path of temp object.
|
:yield: Full path of temp object.
|
||||||
:rtype: str
|
:rtype: str
|
||||||
@ -1147,7 +1147,7 @@ fi
|
|||||||
directory = directory or self.working_directory
|
directory = directory or self.working_directory
|
||||||
temp_obj = None
|
temp_obj = None
|
||||||
try:
|
try:
|
||||||
cmd = f'mktemp -p {directory} {prefix}-XXXXXX'
|
cmd = f'mktemp -p {quote(directory)} {quote(prefix)}-XXXXXX'
|
||||||
if is_directory:
|
if is_directory:
|
||||||
cmd += ' -d'
|
cmd += ' -d'
|
||||||
|
|
||||||
|
@ -392,10 +392,13 @@ class SshConnection(SshConnectionBase):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
except BaseException:
|
# pylint: disable=broad-except
|
||||||
if self.client is not None:
|
except BaseException as e:
|
||||||
self.client.close()
|
try:
|
||||||
raise
|
if self.client is not None:
|
||||||
|
self.client.close()
|
||||||
|
finally:
|
||||||
|
raise e
|
||||||
|
|
||||||
def _make_client(self):
|
def _make_client(self):
|
||||||
if self.strict_host_check:
|
if self.strict_host_check:
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
LocalLinuxTarget:
|
|
||||||
entry-0:
|
|
||||||
connection_settings:
|
|
||||||
unrooted: True
|
|
||||||
|
|
@ -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'
|
|
5
tests/test_config.yml
Normal file
5
tests/test_config.yml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
target-configs:
|
||||||
|
entry-0:
|
||||||
|
LocalLinuxTarget:
|
||||||
|
connection_settings:
|
||||||
|
unrooted: True
|
40
tests/test_config.yml.example
Normal file
40
tests/test_config.yml.example
Normal file
@ -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'
|
@ -14,136 +14,168 @@
|
|||||||
# limitations under the License.
|
# 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
|
import os
|
||||||
from pprint import pp
|
|
||||||
import pytest
|
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.android import AdbConnection
|
||||||
from devlib.utils.misc import load_struct_from_yaml
|
from devlib.utils.misc import load_struct_from_yaml
|
||||||
|
|
||||||
|
|
||||||
def build_targets():
|
logger = logging.getLogger('test_target')
|
||||||
"""Read targets from a YAML formatted config file"""
|
|
||||||
|
|
||||||
config_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'target_configs.yaml')
|
|
||||||
|
|
||||||
target_configs = load_struct_from_yaml(config_file)
|
def get_class_object(name):
|
||||||
if target_configs is None:
|
"""
|
||||||
|
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!')
|
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!')
|
||||||
|
|
||||||
if target_configs.get('AndroidTarget') is not None:
|
target_runners = []
|
||||||
print('> Android targets:')
|
|
||||||
for entry in target_configs['AndroidTarget'].values():
|
for entry in target_configs.values():
|
||||||
pp(entry)
|
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(
|
a_target = AndroidTarget(
|
||||||
connect=False,
|
connect=False,
|
||||||
connection_settings=entry['connection_settings'],
|
connection_settings=target_info['connection_settings'],
|
||||||
conn_cls=lambda **kwargs: AdbConnection(adb_as_root=True, **kwargs),
|
conn_cls=lambda **kwargs: AdbConnection(adb_as_root=True, **kwargs),
|
||||||
)
|
)
|
||||||
a_target.connect(timeout=entry.get('timeout', 60))
|
a_target.connect(timeout=target_info.get('timeout', 60))
|
||||||
targets.append((a_target, None))
|
target_runners.append(NOPTargetRunner(a_target))
|
||||||
|
|
||||||
if target_configs.get('LinuxTarget') is not None:
|
elif target_class is ChromeOsTarget:
|
||||||
print('> Linux targets:')
|
logger.info('> ChromeOS target: %s', repr(target_info))
|
||||||
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)
|
|
||||||
c_target = ChromeOsTarget(
|
c_target = ChromeOsTarget(
|
||||||
connection_settings=entry['connection_settings'],
|
connection_settings=target_info['connection_settings'],
|
||||||
working_directory='/tmp/devlib-target',
|
working_directory='/tmp/devlib-target',
|
||||||
)
|
)
|
||||||
targets.append((c_target, None))
|
target_runners.append(NOPTargetRunner(c_target))
|
||||||
|
|
||||||
if target_configs.get('LocalLinuxTarget') is not None:
|
elif target_class is LinuxTarget:
|
||||||
print('> LocalLinux targets:')
|
logger.info('> Linux target: %s', repr(target_info))
|
||||||
for entry in target_configs['LocalLinuxTarget'].values():
|
l_target = LinuxTarget(connection_settings=target_info['connection_settings'])
|
||||||
pp(entry)
|
target_runners.append(NOPTargetRunner(l_target))
|
||||||
ll_target = LocalLinuxTarget(connection_settings=entry['connection_settings'])
|
|
||||||
targets.append((ll_target, None))
|
|
||||||
|
|
||||||
if target_configs.get('QEMUTargetRunner') is not None:
|
elif target_class is LocalLinuxTarget:
|
||||||
print('> QEMU target runners:')
|
logger.info('> LocalLinux target: %s', repr(target_info))
|
||||||
for entry in target_configs['QEMUTargetRunner'].values():
|
ll_target = LocalLinuxTarget(connection_settings=target_info['connection_settings'])
|
||||||
pp(entry)
|
target_runners.append(NOPTargetRunner(ll_target))
|
||||||
qemu_settings = entry.get('qemu_settings') and entry['qemu_settings']
|
|
||||||
connection_settings = entry.get(
|
elif target_class is QEMUTargetRunner:
|
||||||
'connection_settings') and entry['connection_settings']
|
logger.info('> QEMU target runner: %s', repr(target_info))
|
||||||
|
|
||||||
qemu_runner = QEMUTargetRunner(
|
qemu_runner = QEMUTargetRunner(
|
||||||
qemu_settings=qemu_settings,
|
qemu_settings=target_info.get('qemu_settings'),
|
||||||
connection_settings=connection_settings,
|
connection_settings=target_info.get('connection_settings'),
|
||||||
)
|
)
|
||||||
|
|
||||||
if entry.get('ChromeOsTarget') is None:
|
if target_info.get('ChromeOsTarget') is not None:
|
||||||
targets.append((qemu_runner.target, qemu_runner))
|
# Leave termination of QEMU runner to ChromeOS target.
|
||||||
continue
|
target_runners.append(NOPTargetRunner(qemu_runner.target))
|
||||||
|
|
||||||
# Leave termination of QEMU runner to ChromeOS target.
|
logger.info('>> ChromeOS target: %s', repr(target_info["ChromeOsTarget"]))
|
||||||
targets.append((qemu_runner.target, None))
|
qemu_runner.target = ChromeOsTarget(
|
||||||
|
connection_settings={
|
||||||
|
**target_info['ChromeOsTarget']['connection_settings'],
|
||||||
|
**qemu_runner.target.connection_settings,
|
||||||
|
},
|
||||||
|
working_directory='/tmp/devlib-target',
|
||||||
|
)
|
||||||
|
|
||||||
print('> ChromeOS targets:')
|
target_runners.append(qemu_runner)
|
||||||
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))
|
|
||||||
|
|
||||||
return targets
|
else:
|
||||||
|
raise ValueError(f'Unknown target type {key}!')
|
||||||
|
|
||||||
|
yield target_runners
|
||||||
|
|
||||||
|
logger.info("Destroying resources...")
|
||||||
|
|
||||||
|
for target_runner in target_runners:
|
||||||
|
target = target_runner.target
|
||||||
|
|
||||||
|
# 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()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("target, target_runner", build_targets())
|
# pylint: disable=redefined-outer-name
|
||||||
def test_read_multiline_values(target, target_runner):
|
def test_read_multiline_values(build_target_runners):
|
||||||
"""
|
"""
|
||||||
Test Target.read_tree_values_flat()
|
Test Target.read_tree_values_flat()
|
||||||
|
|
||||||
:param target: Type of target per :class:`Target` based classes.
|
Runs tests around ``Target.read_tree_values_flat()`` for ``TargetRunner`` objects.
|
||||||
:type target: Target
|
|
||||||
|
|
||||||
:param target_runner: Target runner object to terminate target (if necessary).
|
|
||||||
:type target: TargetRunner
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
logger.info('Running test_read_multiline_values test...')
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'test1': '1',
|
'test1': '1',
|
||||||
'test2': '2\n\n',
|
'test2': '2\n\n',
|
||||||
'test3': '3\n\n4\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:
|
logger.info('target=%s os=%s hostname=%s',
|
||||||
print(f'Created {tempdir}.')
|
target.__class__.__name__, target.os, target.hostname)
|
||||||
|
|
||||||
for key, value in data.items():
|
with target.make_temp() as tempdir:
|
||||||
path = os.path.join(tempdir, key)
|
logger.debug('Created %s.', tempdir)
|
||||||
print(f'Writing {value!r} to {path}...')
|
|
||||||
target.write_value(path, value, verify=False,
|
|
||||||
as_root=target.conn.connected_as_root)
|
|
||||||
|
|
||||||
print('Reading values from target...')
|
for key, value in data.items():
|
||||||
raw_result = target.read_tree_values_flat(tempdir)
|
path = os.path.join(tempdir, key)
|
||||||
result = {os.path.basename(k): v for k, v in raw_result.items()}
|
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}...')
|
logger.debug('Reading values from target...')
|
||||||
target.remove(target.working_directory)
|
raw_result = target.read_tree_values_flat(tempdir)
|
||||||
|
result = {os.path.basename(k): v for k, v in raw_result.items()}
|
||||||
|
|
||||||
if target_runner is not None:
|
assert {k: v.strip() for k, v in data.items()} == result
|
||||||
print('Terminating target runner...')
|
|
||||||
target_runner.terminate()
|
|
||||||
|
|
||||||
assert {k: v.strip() for k, v in data.items()} == result
|
|
||||||
|
@ -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'
|
|
46
tools/docker/target_configs.yml
Normal file
46
tools/docker/target_configs.yml
Normal file
@ -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'
|
Loading…
x
Reference in New Issue
Block a user