1
0
mirror of https://git.yoctoproject.org/meta-arm synced 2026-04-20 23:41:08 +00:00

arm/lib: Decouple console parsing from the FVPRunner

To simplify the FVPRunner class, create a separate ConsolePortParser
class to handle reading an iterator of lines and parsing port numbers
for FVP consoles. Use this in runfvp and the test targets.

This refactor also allows the stream being monitored to be changed more
easily, e.g. to a log file.

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

View File

@@ -44,18 +44,39 @@ def check_telnet():
if not bool(shutil.which("telnet")):
raise RuntimeError("Cannot find telnet, this is needed to connect to the FVP.")
class ConsolePortParser:
def __init__(self, lines):
self._lines = lines
self._console_ports = {}
def parse_port(self, console):
if console in self._console_ports:
return self._console_ports[console]
while True:
try:
line = next(self._lines).strip().decode(errors='ignore')
m = re.match(r"^(\S+): Listening for serial connection on port (\d+)$", line)
if m:
matched_console = m.group(1)
matched_port = int(m.group(2))
if matched_console == console:
return matched_port
else:
self._console_ports[matched_console] = matched_port
except StopIteration:
# self._lines might be a growing log file
pass
class FVPRunner:
def __init__(self, logger):
self._terminal_ports = {}
self._line_callbacks = []
self._logger = logger
self._fvp_process = None
self._telnets = []
self._pexpects = []
def add_line_callback(self, callback):
self._line_callbacks.append(callback)
def start(self, config, extra_args=[], terminal_choice="none"):
cli = cli_from_config(config, terminal_choice)
cli += extra_args
@@ -73,14 +94,6 @@ class FVPRunner:
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
env=env)
def detect_terminals(line):
m = re.match(r"^(\S+): Listening for serial connection on port (\d+)$", line)
if m:
terminal = m.group(1)
port = int(m.group(2))
self._terminal_ports[terminal] = port
self.add_line_callback(detect_terminals)
def stop(self):
if self._fvp_process:
self._logger.debug(f"Terminating FVP PID {self._fvp_process.pid}")
@@ -117,34 +130,21 @@ class FVPRunner:
else:
return 0
def run(self, until=None):
if until and until():
return
def wait(self, timeout):
self._fvp_process.wait(timeout)
for line in self._fvp_process.stdout:
line = line.strip().decode("utf-8", errors="replace")
for callback in self._line_callbacks:
callback(line)
if until and until():
return
@property
def stdout(self):
return self._fvp_process.stdout
def _get_terminal_port(self, terminal):
def terminal_exists():
return terminal in self._terminal_ports
self.run(terminal_exists)
return self._terminal_ports[terminal]
def create_telnet(self, terminal):
def create_telnet(self, port):
check_telnet()
port = self._get_terminal_port(terminal)
telnet = subprocess.Popen(["telnet", "localhost", str(port)], stdin=sys.stdin, stdout=sys.stdout)
self._telnets.append(telnet)
return telnet
def create_pexpect(self, terminal, **kwargs):
check_telnet()
def create_pexpect(self, port, **kwargs):
import pexpect
port = self._get_terminal_port(terminal)
instance = pexpect.spawn(f"telnet localhost {port}", **kwargs)
self._pexpects.append(instance)
return instance

View File

@@ -52,8 +52,10 @@ class OEFVPTarget(OEFVPSSHTarget):
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']}")
console = self.fvp.create_pexpect(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")
@@ -85,12 +87,6 @@ class OEFVPSerialTarget(OEFVPSSHTarget):
self.test_log_suffix = pathlib.Path(bootlog).suffix
self.bootlog = bootlog
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] = \
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)
@@ -103,8 +99,13 @@ class OEFVPSerialTarget(OEFVPSSHTarget):
return open(fvp_log_path, 'wb')
def _after_start(self):
parser = runner.ConsolePortParser(self.fvp.stdout)
for name, console in self.config["consoles"].items():
self._add_terminal(name, console)
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

View File

@@ -52,17 +52,18 @@ def start_fvp(args, config, extra_args):
fvp.start(config, extra_args, args.terminals)
if args.console:
fvp.add_line_callback(lambda line: logger.debug(f"FVP output: {line}"))
expected_terminal = config["consoles"]["default"]
if not expected_terminal:
logger.error("--console used but FVP_CONSOLE not set in machine configuration")
return 1
telnet = fvp.create_telnet(expected_terminal)
parser = runner.ConsolePortParser(fvp.stdout)
port = parser.parse_port(expected_terminal)
telnet = fvp.create_telnet(port)
telnet.wait()
logger.debug(f"Telnet quit, cancelling tasks")
else:
fvp.add_line_callback(lambda line: print(line))
fvp.run()
for line in fvp.stdout:
print(line.strip().decode(errors='ignore'))
finally:
fvp.stop()