1
0
mirror of https://git.yoctoproject.org/meta-arm synced 2026-01-12 03:10:15 +00:00

arm/oeqa: Log the FVP output in OEFVPSSHTarget

It is often useful to inspect the FVP output after running the tests.

Refactor OEFVPSerialTarget._create_logfile into
OEFVPSSHTarget._create_log_filename, so that all FVP test controllers
are able to create secondary log files.

Pass a filehandle to the stdout argument of run_fvp so that the FVP
output is asynchronously logged to a file. Change the port parsing logic
to read back from the same log file instead of reading the stdout
directly.

Issue-Id: SCM-5314
Signed-off-by: Peter Hoyes <Peter.Hoyes@arm.com>
Change-Id: I2ddb423fa0d896c13d3e96884858c680c4d34555
Signed-off-by: Jon Mason <jon.mason@arm.com>
This commit is contained in:
Peter Hoyes
2022-11-15 15:01:15 +00:00
committed by Jon Mason
parent 2265bffdc7
commit 79e78fb31d
3 changed files with 53 additions and 48 deletions

View File

@@ -4,7 +4,7 @@ OE-Core's [oeqa][OEQA] framework provides a method of performing runtime tests o
Tests can be configured to run automatically post-build by setting the variable `TESTIMAGE_AUTO="1"`, e.g. in your Kas file or local.conf.
There are two main methods of testing, using different test "targets".
There are two main methods of testing, using different test "targets". Both test targets generate an additional log file with the prefix 'fvp_log' in the image recipe's `${WORKDIR}/testimage` containing the FVP's stdout.
## OEFVPTarget

View File

@@ -77,7 +77,7 @@ class FVPRunner:
self._telnets = []
self._pexpects = []
def start(self, config, extra_args=[], terminal_choice="none"):
def start(self, config, extra_args=[], terminal_choice="none", stdout=subprocess.PIPE):
cli = cli_from_config(config, terminal_choice)
cli += extra_args
@@ -91,7 +91,7 @@ class FVPRunner:
self._logger.debug(f"Constructed FVP call: {shlex.join(cli)}")
self._fvp_process = subprocess.Popen(
cli,
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
stdin=subprocess.DEVNULL, stdout=stdout, stderr=subprocess.STDOUT,
env=env)
def stop(self):

View File

@@ -12,7 +12,7 @@ class OEFVPSSHTarget(OESSHTarget):
Contains common logic to start and stop an FVP.
"""
def __init__(self, logger, target_ip, server_ip, timeout=300, user='root',
port=None, dir_image=None, rootfs=None, **kwargs):
port=None, dir_image=None, rootfs=None, bootlog=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
@@ -20,6 +20,7 @@ class OEFVPSSHTarget(OESSHTarget):
basename = basename.name.replace("".join(basename.suffixes), "")
self.fvpconf = image_dir / (basename + ".fvpconf")
self.config = conffile.load(self.fvpconf)
self.bootlog = bootlog
if not self.fvpconf.exists():
raise FileNotFoundError(f"Cannot find {self.fvpconf}")
@@ -28,8 +29,9 @@ class OEFVPSSHTarget(OESSHTarget):
pass
def start(self, **kwargs):
self.fvp_log = self._create_logfile("fvp")
self.fvp = runner.FVPRunner(self.logger)
self.fvp.start(self.config)
self.fvp.start(self.config, stdout=self.fvp_log)
self.logger.debug(f"Started FVP PID {self.fvp.pid()}")
self._after_start()
@@ -37,6 +39,22 @@ class OEFVPSSHTarget(OESSHTarget):
returncode = self.fvp.stop()
self.logger.debug(f"Stopped FVP with return code {returncode}")
def _create_logfile(self, name):
if not self.bootlog:
return None
test_log_path = pathlib.Path(self.bootlog).parent
test_log_suffix = pathlib.Path(self.bootlog).suffix
fvp_log_file = f"{name}_log{test_log_suffix}"
fvp_log_path = pathlib.Path(test_log_path, fvp_log_file)
fvp_log_symlink = pathlib.Path(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')
class OEFVPTarget(OEFVPSSHTarget):
"""
@@ -44,33 +62,34 @@ class OEFVPTarget(OEFVPSSHTarget):
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):
def __init__(self, logger, target_ip, server_ip, **kwargs):
super().__init__(logger, target_ip, server_ip, **kwargs)
self.logfile = bootlog and open(bootlog, "wb") or None
self.logfile = self.bootlog and open(self.bootlog, "wb") or None
# FVPs boot slowly, so allow ten minutes
self.boot_timeout = 10 * 60
def _after_start(self):
parser = runner.ConsolePortParser(self.fvp.stdout)
self.logger.debug(f"Awaiting console on terminal {self.config['consoles']['default']}")
port = parser.parse_port(self.config['consoles']['default'])
console = self.fvp.create_pexpect(port)
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.")
with open(self.fvp_log.name, 'rb') as logfile:
parser = runner.ConsolePortParser(logfile)
self.logger.debug(f"Awaiting console on terminal {self.config['consoles']['default']}")
port = parser.parse_port(self.config['consoles']['default'])
console = self.fvp.create_pexpect(port)
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
@@ -79,39 +98,25 @@ class OEFVPSerialTarget(OEFVPSSHTarget):
"""
DEFAULT_CONSOLE = "default"
def __init__(self, logger, target_ip, server_ip, bootlog=None, **kwargs):
def __init__(self, logger, target_ip, server_ip, **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
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')
def _after_start(self):
parser = runner.ConsolePortParser(self.fvp.stdout)
for name, console in self.config["consoles"].items():
logfile = self._create_logfile(name)
self.logger.info(f'Creating terminal {name} on {console}')
port = parser.parse_port(console)
self.terminals[name] = \
self.fvp.create_pexpect(port, logfile=logfile)
with open(self.fvp_log.name, 'rb') as logfile:
parser = runner.ConsolePortParser(logfile)
for name, console in self.config["consoles"].items():
logfile = self._create_logfile(name)
self.logger.info(f'Creating terminal {name} on {console}')
port = parser.parse_port(console)
self.terminals[name] = \
self.fvp.create_pexpect(port, logfile=logfile)
# 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)
# 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)
def _get_terminal(self, name):
return self.terminals[name]