diff mbox series

[3/6] arm/oeqa: Refactor OEFVPTarget to use FVPRunner and pexpect

Message ID 20220712102830.625090-4-peter.hoyes@arm.com
State New
Headers show
Series Refactor runfvp for OEFVPSerialTarget | expand

Commit Message

Peter Hoyes July 12, 2022, 10:28 a.m. UTC
From: Peter Hoyes <Peter.Hoyes@arm.com>

Refactor OEFVPTarget to use the FVPRunner in meta-arm/lib instead of
calling runfvp in a new process.

Use pexpect to wait for the login prompt instead of parsing the FVP
output manually.

This patch introduces a dependency on pexpect for the meta-arm test
targets. It is already in the Yocto host dependency list and the Kas
container image, but may need to be installed on development machines.

Issue-Id: SCM-4957
Signed-off-by: Peter Hoyes <Peter.Hoyes@arm.com>
Change-Id: I7200e958c5701d82493287d021936afcf2f2bac9
---
 meta-arm/lib/fvp/runner.py           | 10 ++++
 meta-arm/lib/oeqa/controllers/fvp.py | 75 +++++++---------------------
 2 files changed, 29 insertions(+), 56 deletions(-)
diff mbox series

Patch

diff --git a/meta-arm/lib/fvp/runner.py b/meta-arm/lib/fvp/runner.py
index e7983c6..74ebc02 100644
--- a/meta-arm/lib/fvp/runner.py
+++ b/meta-arm/lib/fvp/runner.py
@@ -51,6 +51,7 @@  class FVPRunner:
         self._logger = logger
         self._fvp_process = None
         self._telnets = []
+        self._pexpects = []
 
     def add_line_callback(self, callback):
         self._line_callbacks.append(callback)
@@ -87,6 +88,9 @@  class FVPRunner:
             await telnet.terminate()
             await telnet.wait()
 
+        for pexpect in self._pexpects:
+            pexpect.close()
+
     async def run(self, until=None):
         if until and until():
             return
@@ -111,5 +115,11 @@  class FVPRunner:
         self._telnets.append(telnet)
         return telnet
 
+    async def create_pexpect(self, terminal, timeout=15.0, **kwargs):
+        check_telnet()
+        import pexpect
+        port = await self._get_terminal_port(terminal, timeout)
+        return pexpect.spawn(f"telnet localhost {port}", **kwargs)
+
     def pid(self):
         return self._fvp_process.pid
diff --git a/meta-arm/lib/oeqa/controllers/fvp.py b/meta-arm/lib/oeqa/controllers/fvp.py
index 2913f78..c0a87ba 100644
--- a/meta-arm/lib/oeqa/controllers/fvp.py
+++ b/meta-arm/lib/oeqa/controllers/fvp.py
@@ -1,16 +1,11 @@ 
 import asyncio
-import os
 import pathlib
-import signal
-import subprocess
+import pexpect
 
 import oeqa.core.target.ssh
+from fvp import conffile, runner
 
 class OEFVPTarget(oeqa.core.target.ssh.OESSHTarget):
-
-    # meta-arm/scripts isn't on PATH, so work out where it is
-    metaarm = pathlib.Path(__file__).parents[4]
-
     def __init__(self, logger, target_ip, server_ip, timeout=300, user='root',
                  port=None, server_port=0, dir_image=None, rootfs=None, bootlog=None,
                  **kwargs):
@@ -29,45 +24,24 @@  class OEFVPTarget(oeqa.core.target.ssh.OESSHTarget):
         self.logfile = bootlog and open(bootlog, "wb") or None
 
     async def boot_fvp(self):
-        cmd = [OEFVPTarget.metaarm / "scripts" / "runfvp", "--console", "--verbose", self.fvpconf]
-        # Python 3.7 needs the command items to be str
-        cmd = [str(c) for c in cmd]
-        self.logger.debug(f"Starting {cmd}")
-
-        # TODO: refactor runfvp so this can import it and directly hook to the
-        # console callback, then use telnetlib directly to access the console.
-
-        # As we're using --console, telnet expects stdin to be readable too.
-        self.fvp = await asyncio.create_subprocess_exec(*cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE)
-        self.logger.debug(f"Started runfvp PID {self.fvp.pid}")
-
-        async def wait_for_login(bootlog):
-            while True:
-                line = await self.fvp.stdout.read(1024)
-                if not line:
-                    self.logger.debug("runfvp terminated")
-                    return False
-
-                self.logger.debug(f"Read line [{line}]")
-
-                bootlog += line
-                if self.logfile:
-                    self.logfile.write(line)
-
-                if b" login:" in bootlog:
-                    self.logger.debug("Found login prompt")
-                    return True
-
-        bootlog = bytearray()
+        config = conffile.load(self.fvpconf)
+        self.fvp = runner.FVPRunner(self.logger)
+        await self.fvp.start(config)
+        self.logger.debug(f"Started FVP PID {self.fvp.pid()}")
+        console = await self.fvp.create_pexpect(config["console"])
         try:
-            found = await asyncio.wait_for(wait_for_login(bootlog), self.boot_timeout)
-            if found:
-                return
-        except asyncio.TimeoutError:
+            console.expect("login\:", timeout=self.boot_timeout)
+            self.logger.debug("Found login prompt")
+        except pexpect.TIMEOUT:
             self.logger.info("Timed out waiting for login prompt.")
-        self.logger.info("Boot log follows:")
-        self.logger.info(b"\n".join(bootlog.splitlines()[-200:]).decode("utf-8", errors="replace"))
-        raise RuntimeError("Failed to start FVP.")
+            self.logger.info("Boot log follows:")
+            self.logger.info(b"\n".join(console.before.splitlines()[-200:]).decode("utf-8", errors="replace"))
+            raise RuntimeError("Failed to start FVP.")
+
+    async def stop_fvp(self):
+        returncode = await self.fvp.stop()
+
+        self.logger.debug(f"Stopped FVP with return code {returncode}")
 
     def start(self, **kwargs):
         # When we can assume Py3.7+, this can simply be asyncio.run()
@@ -76,15 +50,4 @@  class OEFVPTarget(oeqa.core.target.ssh.OESSHTarget):
 
     def stop(self, **kwargs):
         loop = asyncio.get_event_loop()
-
-        try:
-            # Kill the process group so that the telnet and FVP die too
-            gid = os.getpgid(self.fvp.pid)
-            self.logger.debug(f"Sending SIGTERM to {gid}")
-            os.killpg(gid, signal.SIGTERM)
-            loop.run_until_complete(asyncio.wait_for(self.fvp.wait(), 10))
-        except TimeoutError:
-            self.logger.debug(f"Timed out, sending SIGKILL to {gid}")
-            os.killpg(gid, signal.SIGKILL)
-        except ProcessLookupError:
-            return
+        loop.run_until_complete(asyncio.gather(self.stop_fvp()))