@@ -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()