From patchwork Tue Nov 15 15:01:14 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Peter Hoyes X-Patchwork-Id: 15492 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 42038C433FE for ; Tue, 15 Nov 2022 15:01:47 +0000 (UTC) Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by mx.groups.io with SMTP id smtpd.web11.6503.1668524497317902423 for ; Tue, 15 Nov 2022 07:01:37 -0800 Authentication-Results: mx.groups.io; dkim=missing; spf=pass (domain: arm.com, ip: 217.140.110.172, mailfrom: peter.hoyes@arm.com) Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 129CC1477; Tue, 15 Nov 2022 07:01:43 -0800 (PST) Received: from e125920.cambridge.arm.com (unknown [10.1.199.64]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPSA id 536113F587; Tue, 15 Nov 2022 07:01:36 -0800 (PST) From: Peter Hoyes To: meta-arm@lists.yoctoproject.org Cc: Peter Hoyes Subject: [PATCH 3/5] arm/lib: Decouple console parsing from the FVPRunner Date: Tue, 15 Nov 2022 15:01:14 +0000 Message-Id: <20221115150116.314729-3-peter.hoyes@arm.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20221115150116.314729-1-peter.hoyes@arm.com> References: <20221115150116.314729-1-peter.hoyes@arm.com> MIME-Version: 1.0 List-Id: X-Webhook-Received: from li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Tue, 15 Nov 2022 15:01:47 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/meta-arm/message/4131 From: Peter Hoyes 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 Change-Id: Iade3a4c803fb355b04af7afa298d0a41fe707d94 --- meta-arm/lib/fvp/runner.py | 66 ++++++++++++++-------------- meta-arm/lib/oeqa/controllers/fvp.py | 17 +++---- scripts/runfvp | 9 ++-- 3 files changed, 47 insertions(+), 45 deletions(-) diff --git a/meta-arm/lib/fvp/runner.py b/meta-arm/lib/fvp/runner.py index 9b537e27..5c5ded28 100644 --- a/meta-arm/lib/fvp/runner.py +++ b/meta-arm/lib/fvp/runner.py @@ -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 diff --git a/meta-arm/lib/oeqa/controllers/fvp.py b/meta-arm/lib/oeqa/controllers/fvp.py index dfc2b88b..0c3c2214 100644 --- a/meta-arm/lib/oeqa/controllers/fvp.py +++ b/meta-arm/lib/oeqa/controllers/fvp.py @@ -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 diff --git a/scripts/runfvp b/scripts/runfvp index 727223ca..454acb86 100755 --- a/scripts/runfvp +++ b/scripts/runfvp @@ -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()