1
0
mirror of https://git.yoctoproject.org/meta-arm synced 2026-05-08 05:09:56 +00:00

arm/oeqa: Create new OEFVPSerialTarget with pexpect interface

Refactor OEFVPTarget into new base class, OEFVPSSHTarget. OEFVPTarget
extends OEFVPSSHTarget and additionally waits for a Linux login prompt
for compatibility with tests in OE-core.

OEFVPSerialTarget also extends OEFVPSSHTarget. It also exposes the
entire API of pexpect, with the first argument being the
FVP_TEST_CONSOLE varflag key. It logs each console output to separate
files inside the core-image-minimal work directory.

Issue-Id: SCM-4957
Signed-off-by: Peter Hoyes <Peter.Hoyes@arm.com>
Change-Id: I1b93f94471c6311da9ee71a48239640ee37de0af
Signed-off-by: Jon Mason <jon.mason@arm.com>
This commit is contained in:
Peter Hoyes
2022-07-12 11:28:29 +01:00
committed by Jon Mason
parent ff90c8ad54
commit 30b1452025
+118 -19
View File
@@ -1,45 +1,45 @@
import asyncio
import pathlib
import pexpect
import os
import oeqa.core.target.ssh
from oeqa.core.target.ssh import OESSHTarget
from fvp import conffile, runner
class OEFVPTarget(oeqa.core.target.ssh.OESSHTarget):
class OEFVPSSHTarget(OESSHTarget):
"""
Base class for meta-arm FVP targets.
Contains common logic to start and stop an FVP.
"""
def __init__(self, logger, target_ip, server_ip, timeout=300, user='root',
port=None, server_port=0, dir_image=None, rootfs=None, bootlog=None,
**kwargs):
port=None, dir_image=None, rootfs=None, **kwargs):
super().__init__(logger, target_ip, server_ip, timeout, user, port)
image_dir = pathlib.Path(dir_image)
# rootfs may have multiple extensions so we need to strip *all* suffixes
basename = pathlib.Path(rootfs)
basename = basename.name.replace("".join(basename.suffixes), "")
self.fvpconf = image_dir / (basename + ".fvpconf")
self.config = conffile.load(self.fvpconf)
if not self.fvpconf.exists():
raise FileNotFoundError(f"Cannot find {self.fvpconf}")
# FVPs boot slowly, so allow ten minutes
self.boot_timeout = 10 * 60
self.logfile = bootlog and open(bootlog, "wb") or None
async def boot_fvp(self):
config = conffile.load(self.fvpconf)
self.fvp = runner.FVPRunner(self.logger)
await self.fvp.start(config)
await self.fvp.start(self.config)
self.logger.debug(f"Started FVP PID {self.fvp.pid()}")
console = await self.fvp.create_pexpect(config["console"])
try:
console.expect("login\:", timeout=self.boot_timeout)
self.logger.debug("Found login prompt")
except pexpect.TIMEOUT:
self.logger.info("Timed out waiting for login prompt.")
self.logger.info("Boot log follows:")
self.logger.info(b"\n".join(console.before.splitlines()[-200:]).decode("utf-8", errors="replace"))
raise RuntimeError("Failed to start FVP.")
await self._after_start()
async def _after_start(self):
pass
async def _after_stop(self):
pass
async def stop_fvp(self):
returncode = await self.fvp.stop()
await self._after_stop()
self.logger.debug(f"Stopped FVP with return code {returncode}")
@@ -51,3 +51,102 @@ class OEFVPTarget(oeqa.core.target.ssh.OESSHTarget):
def stop(self, **kwargs):
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(self.stop_fvp()))
class OEFVPTarget(OEFVPSSHTarget):
"""
For compatibility with OE-core test cases, this target's start() method
waits for a Linux shell before returning to ensure that SSH commands work
with the default test dependencies.
"""
def __init__(self, logger, target_ip, server_ip, bootlog=None, **kwargs):
super().__init__(logger, target_ip, server_ip, **kwargs)
self.logfile = bootlog and open(bootlog, "wb") or None
# FVPs boot slowly, so allow ten minutes
self.boot_timeout = 10 * 60
async def _after_start(self):
self.logger.debug(f"Awaiting console on terminal {self.config['consoles']['default']}")
console = await self.fvp.create_pexpect(self.config['consoles']['default'])
try:
console.expect("login\\:", timeout=self.boot_timeout)
self.logger.debug("Found login prompt")
except pexpect.TIMEOUT:
self.logger.info("Timed out waiting for login prompt.")
self.logger.info("Boot log follows:")
self.logger.info(b"\n".join(console.before.splitlines()[-200:]).decode("utf-8", errors="replace"))
raise RuntimeError("Failed to start FVP.")
class OEFVPSerialTarget(OEFVPSSHTarget):
"""
This target is intended for interaction with the target over one or more
telnet consoles using pexpect.
This still depends on OEFVPSSHTarget so SSH commands can still be run on
the target, but note that this class does not inherently guarantee that
the SSH server is running prior to running test cases. Test cases that use
SSH should first validate that SSH is available.
"""
DEFAULT_CONSOLE = "default"
def __init__(self, logger, target_ip, server_ip, bootlog=None, **kwargs):
super().__init__(logger, target_ip, server_ip, **kwargs)
self.terminals = {}
self.test_log_path = pathlib.Path(bootlog).parent
self.test_log_suffix = pathlib.Path(bootlog).suffix
self.bootlog = bootlog
async def _add_terminal(self, name, fvp_name):
logfile = self._create_logfile(name)
self.logger.info(f'Creating terminal {name} on {fvp_name}')
self.terminals[name] = \
await self.fvp.create_pexpect(fvp_name, logfile=logfile)
def _create_logfile(self, name):
fvp_log_file = f"{name}_log{self.test_log_suffix}"
fvp_log_path = pathlib.Path(self.test_log_path, fvp_log_file)
fvp_log_symlink = pathlib.Path(self.test_log_path, f"{name}_log")
try:
os.remove(fvp_log_symlink)
except:
pass
os.symlink(fvp_log_file, fvp_log_symlink)
return open(fvp_log_path, 'wb')
async def _after_start(self):
for name, console in self.config["consoles"].items():
await self._add_terminal(name, console)
# testimage.bbclass expects to see a log file at `bootlog`,
# so make a symlink to the 'default' log file
if name == 'default':
default_test_file = f"{name}_log{self.test_log_suffix}"
os.symlink(default_test_file, self.bootlog)
async def _after_stop(self):
# Ensure pexpect logs all remaining output to the logfile
for terminal in self.terminals.values():
terminal.expect(pexpect.EOF, timeout=5)
terminal.close()
def _get_terminal(self, name):
return self.terminals[name]
def __getattr__(self, name):
"""
Magic method which automatically exposes the whole pexpect API on the
target, with the first argument being the terminal name.
e.g. self.target.expect(self.target.DEFAULT_CONSOLE, "login\\:")
"""
def call_pexpect(terminal, *args, **kwargs):
attr = getattr(self.terminals[terminal], name)
if callable(attr):
return attr(*args, **kwargs)
else:
return attr
return call_pexpect