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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user