mirror of
https://git.yoctoproject.org/meta-arm
synced 2026-05-31 12:50:02 +00:00
arm/oeqa: Refactor OEFVPTarget to use FVPRunner and pexpect
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 Signed-off-by: Jon Mason <jon.mason@arm.com>
This commit is contained in:
@@ -51,6 +51,7 @@ class FVPRunner:
|
|||||||
self._logger = logger
|
self._logger = logger
|
||||||
self._fvp_process = None
|
self._fvp_process = None
|
||||||
self._telnets = []
|
self._telnets = []
|
||||||
|
self._pexpects = []
|
||||||
|
|
||||||
def add_line_callback(self, callback):
|
def add_line_callback(self, callback):
|
||||||
self._line_callbacks.append(callback)
|
self._line_callbacks.append(callback)
|
||||||
@@ -87,6 +88,9 @@ class FVPRunner:
|
|||||||
await telnet.terminate()
|
await telnet.terminate()
|
||||||
await telnet.wait()
|
await telnet.wait()
|
||||||
|
|
||||||
|
for pexpect in self._pexpects:
|
||||||
|
pexpect.close()
|
||||||
|
|
||||||
async def run(self, until=None):
|
async def run(self, until=None):
|
||||||
if until and until():
|
if until and until():
|
||||||
return
|
return
|
||||||
@@ -111,5 +115,11 @@ class FVPRunner:
|
|||||||
self._telnets.append(telnet)
|
self._telnets.append(telnet)
|
||||||
return 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):
|
def pid(self):
|
||||||
return self._fvp_process.pid
|
return self._fvp_process.pid
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import signal
|
import pexpect
|
||||||
import subprocess
|
|
||||||
|
|
||||||
import oeqa.core.target.ssh
|
import oeqa.core.target.ssh
|
||||||
|
from fvp import conffile, runner
|
||||||
|
|
||||||
class OEFVPTarget(oeqa.core.target.ssh.OESSHTarget):
|
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',
|
def __init__(self, logger, target_ip, server_ip, timeout=300, user='root',
|
||||||
port=None, server_port=0, dir_image=None, rootfs=None, bootlog=None,
|
port=None, server_port=0, dir_image=None, rootfs=None, bootlog=None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
@@ -29,45 +24,24 @@ class OEFVPTarget(oeqa.core.target.ssh.OESSHTarget):
|
|||||||
self.logfile = bootlog and open(bootlog, "wb") or None
|
self.logfile = bootlog and open(bootlog, "wb") or None
|
||||||
|
|
||||||
async def boot_fvp(self):
|
async def boot_fvp(self):
|
||||||
cmd = [OEFVPTarget.metaarm / "scripts" / "runfvp", "--console", "--verbose", self.fvpconf]
|
config = conffile.load(self.fvpconf)
|
||||||
# Python 3.7 needs the command items to be str
|
self.fvp = runner.FVPRunner(self.logger)
|
||||||
cmd = [str(c) for c in cmd]
|
await self.fvp.start(config)
|
||||||
self.logger.debug(f"Starting {cmd}")
|
self.logger.debug(f"Started FVP PID {self.fvp.pid()}")
|
||||||
|
console = await self.fvp.create_pexpect(config["console"])
|
||||||
# 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()
|
|
||||||
try:
|
try:
|
||||||
found = await asyncio.wait_for(wait_for_login(bootlog), self.boot_timeout)
|
console.expect("login\:", timeout=self.boot_timeout)
|
||||||
if found:
|
self.logger.debug("Found login prompt")
|
||||||
return
|
except pexpect.TIMEOUT:
|
||||||
except asyncio.TimeoutError:
|
|
||||||
self.logger.info("Timed out waiting for login prompt.")
|
self.logger.info("Timed out waiting for login prompt.")
|
||||||
self.logger.info("Boot log follows:")
|
self.logger.info("Boot log follows:")
|
||||||
self.logger.info(b"\n".join(bootlog.splitlines()[-200:]).decode("utf-8", errors="replace"))
|
self.logger.info(b"\n".join(console.before.splitlines()[-200:]).decode("utf-8", errors="replace"))
|
||||||
raise RuntimeError("Failed to start FVP.")
|
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):
|
def start(self, **kwargs):
|
||||||
# When we can assume Py3.7+, this can simply be asyncio.run()
|
# 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):
|
def stop(self, **kwargs):
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.run_until_complete(asyncio.gather(self.stop_fvp()))
|
||||||
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
|
|
||||||
|
|||||||
Reference in New Issue
Block a user