mirror of
https://git.yoctoproject.org/meta-arm
synced 2026-07-16 15:57:19 +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:
+33
-33
@@ -44,18 +44,39 @@ def check_telnet():
|
|||||||
if not bool(shutil.which("telnet")):
|
if not bool(shutil.which("telnet")):
|
||||||
raise RuntimeError("Cannot find telnet, this is needed to connect to the FVP.")
|
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:
|
class FVPRunner:
|
||||||
def __init__(self, logger):
|
def __init__(self, logger):
|
||||||
self._terminal_ports = {}
|
|
||||||
self._line_callbacks = []
|
|
||||||
self._logger = logger
|
self._logger = logger
|
||||||
self._fvp_process = None
|
self._fvp_process = None
|
||||||
self._telnets = []
|
self._telnets = []
|
||||||
self._pexpects = []
|
self._pexpects = []
|
||||||
|
|
||||||
def add_line_callback(self, callback):
|
|
||||||
self._line_callbacks.append(callback)
|
|
||||||
|
|
||||||
def start(self, config, extra_args=[], terminal_choice="none"):
|
def start(self, config, extra_args=[], terminal_choice="none"):
|
||||||
cli = cli_from_config(config, terminal_choice)
|
cli = cli_from_config(config, terminal_choice)
|
||||||
cli += extra_args
|
cli += extra_args
|
||||||
@@ -73,14 +94,6 @@ class FVPRunner:
|
|||||||
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||||
env=env)
|
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):
|
def stop(self):
|
||||||
if self._fvp_process:
|
if self._fvp_process:
|
||||||
self._logger.debug(f"Terminating FVP PID {self._fvp_process.pid}")
|
self._logger.debug(f"Terminating FVP PID {self._fvp_process.pid}")
|
||||||
@@ -117,34 +130,21 @@ class FVPRunner:
|
|||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def run(self, until=None):
|
def wait(self, timeout):
|
||||||
if until and until():
|
self._fvp_process.wait(timeout)
|
||||||
return
|
|
||||||
|
|
||||||
for line in self._fvp_process.stdout:
|
@property
|
||||||
line = line.strip().decode("utf-8", errors="replace")
|
def stdout(self):
|
||||||
for callback in self._line_callbacks:
|
return self._fvp_process.stdout
|
||||||
callback(line)
|
|
||||||
if until and until():
|
|
||||||
return
|
|
||||||
|
|
||||||
def _get_terminal_port(self, terminal):
|
def create_telnet(self, port):
|
||||||
def terminal_exists():
|
|
||||||
return terminal in self._terminal_ports
|
|
||||||
self.run(terminal_exists)
|
|
||||||
return self._terminal_ports[terminal]
|
|
||||||
|
|
||||||
def create_telnet(self, terminal):
|
|
||||||
check_telnet()
|
check_telnet()
|
||||||
port = self._get_terminal_port(terminal)
|
|
||||||
telnet = subprocess.Popen(["telnet", "localhost", str(port)], stdin=sys.stdin, stdout=sys.stdout)
|
telnet = subprocess.Popen(["telnet", "localhost", str(port)], stdin=sys.stdin, stdout=sys.stdout)
|
||||||
self._telnets.append(telnet)
|
self._telnets.append(telnet)
|
||||||
return telnet
|
return telnet
|
||||||
|
|
||||||
def create_pexpect(self, terminal, **kwargs):
|
def create_pexpect(self, port, **kwargs):
|
||||||
check_telnet()
|
|
||||||
import pexpect
|
import pexpect
|
||||||
port = self._get_terminal_port(terminal)
|
|
||||||
instance = pexpect.spawn(f"telnet localhost {port}", **kwargs)
|
instance = pexpect.spawn(f"telnet localhost {port}", **kwargs)
|
||||||
self._pexpects.append(instance)
|
self._pexpects.append(instance)
|
||||||
return instance
|
return instance
|
||||||
|
|||||||
@@ -52,8 +52,10 @@ class OEFVPTarget(OEFVPSSHTarget):
|
|||||||
self.boot_timeout = 10 * 60
|
self.boot_timeout = 10 * 60
|
||||||
|
|
||||||
def _after_start(self):
|
def _after_start(self):
|
||||||
|
parser = runner.ConsolePortParser(self.fvp.stdout)
|
||||||
self.logger.debug(f"Awaiting console on terminal {self.config['consoles']['default']}")
|
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:
|
try:
|
||||||
console.expect("login\\:", timeout=self.boot_timeout)
|
console.expect("login\\:", timeout=self.boot_timeout)
|
||||||
self.logger.debug("Found login prompt")
|
self.logger.debug("Found login prompt")
|
||||||
@@ -85,12 +87,6 @@ class OEFVPSerialTarget(OEFVPSSHTarget):
|
|||||||
self.test_log_suffix = pathlib.Path(bootlog).suffix
|
self.test_log_suffix = pathlib.Path(bootlog).suffix
|
||||||
self.bootlog = bootlog
|
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):
|
def _create_logfile(self, name):
|
||||||
fvp_log_file = f"{name}_log{self.test_log_suffix}"
|
fvp_log_file = f"{name}_log{self.test_log_suffix}"
|
||||||
fvp_log_path = pathlib.Path(self.test_log_path, fvp_log_file)
|
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')
|
return open(fvp_log_path, 'wb')
|
||||||
|
|
||||||
def _after_start(self):
|
def _after_start(self):
|
||||||
|
parser = runner.ConsolePortParser(self.fvp.stdout)
|
||||||
for name, console in self.config["consoles"].items():
|
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`,
|
# testimage.bbclass expects to see a log file at `bootlog`,
|
||||||
# so make a symlink to the 'default' log file
|
# so make a symlink to the 'default' log file
|
||||||
|
|||||||
+5
-4
@@ -52,17 +52,18 @@ def start_fvp(args, config, extra_args):
|
|||||||
fvp.start(config, extra_args, args.terminals)
|
fvp.start(config, extra_args, args.terminals)
|
||||||
|
|
||||||
if args.console:
|
if args.console:
|
||||||
fvp.add_line_callback(lambda line: logger.debug(f"FVP output: {line}"))
|
|
||||||
expected_terminal = config["consoles"]["default"]
|
expected_terminal = config["consoles"]["default"]
|
||||||
if not expected_terminal:
|
if not expected_terminal:
|
||||||
logger.error("--console used but FVP_CONSOLE not set in machine configuration")
|
logger.error("--console used but FVP_CONSOLE not set in machine configuration")
|
||||||
return 1
|
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()
|
telnet.wait()
|
||||||
logger.debug(f"Telnet quit, cancelling tasks")
|
logger.debug(f"Telnet quit, cancelling tasks")
|
||||||
else:
|
else:
|
||||||
fvp.add_line_callback(lambda line: print(line))
|
for line in fvp.stdout:
|
||||||
fvp.run()
|
print(line.strip().decode(errors='ignore'))
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
fvp.stop()
|
fvp.stop()
|
||||||
|
|||||||
Reference in New Issue
Block a user