diff mbox series

[2/5] arm/oeqa: Merge all OEFVP*Target classes

Message ID 20230717185626.1793017-2-peter.hoyes@arm.com
State New
Headers show
Series [1/5] runfvp: Add missing conffile include | expand

Commit Message

Peter Hoyes July 17, 2023, 6:56 p.m. UTC
From: Peter Hoyes <Peter.Hoyes@arm.com>

The differences between OEFVPTarget, OEFVPSSHTarget and
OEFVPSerialTarget are not obvious and there is a lot of duplication.

Merge all the logic into one OEFVPTarget (again). This has the following
features:
 * Run SSH commands
 * Run serial console assertions
 * Lazily await a Linux login prompt while running test cases (only when
   self.target.run is called).

Signed-off-by: Peter Hoyes <Peter.Hoyes@arm.com>
---
 documentation/oeqa-fvp.md                     |  46 +++----
 .../conf/machine/corstone1000-fvp.conf        |   2 +-
 meta-arm-bsp/conf/machine/corstone500.conf    |   2 +-
 .../conf/machine/fvp-baser-aemv8r64.conf      |   2 +-
 meta-arm-bsp/conf/machine/tc1.conf            |   2 +-
 meta-arm/lib/oeqa/controllers/fvp.py          | 118 +++++++-----------
 meta-arm/lib/oeqa/runtime/cases/linuxboot.py  |   4 +-
 7 files changed, 71 insertions(+), 105 deletions(-)
diff mbox series

Patch

diff --git a/documentation/oeqa-fvp.md b/documentation/oeqa-fvp.md
index e1468851..b39e0963 100644
--- a/documentation/oeqa-fvp.md
+++ b/documentation/oeqa-fvp.md
@@ -4,32 +4,36 @@  OE-Core's [oeqa][OEQA] framework provides a method of performing runtime tests o
 
 Tests can be configured to run automatically post-build by setting the variable `TESTIMAGE_AUTO="1"`, e.g. in your Kas file or local.conf.
 
-There are two main methods of testing, using different test "targets". Both test targets generate an additional log file with the prefix 'fvp_log' in the image recipe's `${WORKDIR}/testimage` containing the FVP's stdout.
+meta-arm provides the OEFVPTarget which must be set up in the machine configuration:
+```
+TEST_TARGET = "OEFVPTarget"
+TEST_SERVER_IP = "127.0.0.1"
+TEST_TARGET_IP = "127.0.0.1:8022"
+IMAGE_FEATURES:append = " ssh-server-dropbear"
+FVP_CONFIG[bp.virtio_net.hostbridge.userNetPorts] ?= "8022=22"
+FVP_CONSOLES[default] = "terminal_0"
+FVP_CONSOLES[tf-a] = "s_terminal_0"
+```
+
+The test target also generates a log file with the prefix 'fvp_log' in the image recipe's `${WORKDIR}/testimage` containing the FVP's stdout.
 
-## OEFVPTarget
+OEFVPTarget supports two different test interfaces - SSH and pexpect.
 
-This runs test cases on a machine using SSH. It therefore requires that an SSH server is installed in the image.
+## SSH
 
-In test cases, the primary interface with the target is, e.g:
+As in OEQA in OE-core, tests cases can run commands on the machine using SSH. It therefore requires that an SSH server is installed in the image.
+
+This uses the `run` method on the target, e.g:
 ```
 (status, output) = self.target.run('uname -a')
 ```
-which runs a single command on the target (using `ssh -c`) and returns the status code and the output. It is therefore useful for running tests in a Linux environment.
+which executes a single command on the target (using `ssh -c`) and returns the status code and the output. It is therefore useful for running tests in a Linux environment.
 
 For examples of test cases, see meta/lib/oeqa/runtime/cases in OE-Core. The majority of test cases depend on `ssh.SSHTest.test_ssh`, which first validates that the SSH connection is functioning.
 
-Example machine configuration:
-```
-TEST_TARGET = "OEFVPTarget"
-TEST_SERVER_IP = "127.0.0.1"
-TEST_TARGET_IP = "127.0.0.1:8022"
-IMAGE_FEATURES:append = " ssh-server-dropbear"
-FVP_CONFIG[bp.virtio_net.hostbridge.userNetPorts] ?= "8022=22"
-```
-
-## OEFVPSerialTarget
+## pexpect
 
-This runs tests against one or more serial consoles on the FVP. It is more flexible than OEFVPTarget, but test cases written for this test target do not support the test cases in OE-core. As it does not require an SSH server, it is suitable for machines with performance or memory limitations.
+To support firmware and baremetal testing, OEFVPTarget also allows test cases to make assertions against one or more consoles using the pexpect library.
 
 Internally, this test target launches a [Pexpect][PEXPECT] instance for each entry in FVP_CONSOLES which can be used with the provided alias. The whole Pexpect API is exposed on the target, where the alias is always passed as the first argument, e.g.:
 ```
@@ -39,16 +43,6 @@  self.assertNotIn(b'ERROR:', self.target.before('tf-a'))
 
 For an example of a full test case, see meta-arm/lib/oeqa/runtime/cases/linuxboot.py This test case can be used to minimally verify that a machine boots to a Linux shell. The default timeout is 10 minutes, but this can be configured with the variable TEST_FVP_LINUX_BOOT_TIMEOUT, which expects a value in seconds.
 
-The SSH interface described above is also available on OEFVPSerialTarget to support writing a set of hybrid test suites that use a combination of serial and SSH access. Note however that this test target does not guarantee that Linux has booted to shell prior to running any tests, so the test cases in OE-core are not supported.
-
-Example machine configuration:
-```
-TEST_TARGET="OEFVPSerialTarget"
-TEST_SUITES="linuxboot"
-FVP_CONSOLES[default] = "terminal_0"
-FVP_CONSOLES[tf-a] = "s_terminal_0"
-```
-
 [OEQA]: https://docs.yoctoproject.org/test-manual/intro.html
 [FVP]: https://developer.arm.com/tools-and-software/simulation-models/fixed-virtual-platforms
 [RUNFVP]: runfvp.md
diff --git a/meta-arm-bsp/conf/machine/corstone1000-fvp.conf b/meta-arm-bsp/conf/machine/corstone1000-fvp.conf
index 66236515..40f69297 100644
--- a/meta-arm-bsp/conf/machine/corstone1000-fvp.conf
+++ b/meta-arm-bsp/conf/machine/corstone1000-fvp.conf
@@ -8,7 +8,7 @@  TFA_TARGET_PLATFORM = "fvp"
 TFM_PLATFORM_IS_FVP = "TRUE"
 
 # testimage config
-TEST_TARGET = "OEFVPSerialTarget"
+TEST_TARGET = "OEFVPTarget"
 TEST_SUITES = "linuxboot"
 
 # FVP Config
diff --git a/meta-arm-bsp/conf/machine/corstone500.conf b/meta-arm-bsp/conf/machine/corstone500.conf
index 4794028a..36ba7703 100644
--- a/meta-arm-bsp/conf/machine/corstone500.conf
+++ b/meta-arm-bsp/conf/machine/corstone500.conf
@@ -32,7 +32,7 @@  WKS_FILE_DEPENDS:append = " ${EXTRA_IMAGEDEPENDS}"
 
 WKS_FILE ?= "core-image-minimal.corstone500.wks"
 
-TEST_TARGET = "OEFVPSerialTarget"
+TEST_TARGET = "OEFVPTarget"
 TEST_SUITES = "linuxboot"
 
 FVP_PROVIDER ?= "fvp-corstone500-native"
diff --git a/meta-arm-bsp/conf/machine/fvp-baser-aemv8r64.conf b/meta-arm-bsp/conf/machine/fvp-baser-aemv8r64.conf
index 62c9cbd0..b8a6aa46 100644
--- a/meta-arm-bsp/conf/machine/fvp-baser-aemv8r64.conf
+++ b/meta-arm-bsp/conf/machine/fvp-baser-aemv8r64.conf
@@ -29,7 +29,7 @@  MACHINE_FEATURES:append = " efi"
 MACHINE_EXTRA_RRECOMMENDS += "ssh-pregen-hostkeys"
 
 # testimage configuration
-TEST_TARGET = "OEFVPSerialTarget"
+TEST_TARGET = "OEFVPTarget"
 TEST_SUITES = "linuxboot"
 TEST_TARGET_IP ?= "127.0.0.1:8022"
 TEST_SERVER_IP ?= "127.0.1.1"
diff --git a/meta-arm-bsp/conf/machine/tc1.conf b/meta-arm-bsp/conf/machine/tc1.conf
index 5f68cc7a..bba2c191 100644
--- a/meta-arm-bsp/conf/machine/tc1.conf
+++ b/meta-arm-bsp/conf/machine/tc1.conf
@@ -6,7 +6,7 @@ 
 
 require conf/machine/include/tc.inc
 
-TEST_TARGET = "OEFVPSerialTarget"
+TEST_TARGET = "OEFVPTarget"
 TEST_SUITES = "linuxboot"
 
 # FVP Config
diff --git a/meta-arm/lib/oeqa/controllers/fvp.py b/meta-arm/lib/oeqa/controllers/fvp.py
index 38484072..ea572dd7 100644
--- a/meta-arm/lib/oeqa/controllers/fvp.py
+++ b/meta-arm/lib/oeqa/controllers/fvp.py
@@ -6,11 +6,14 @@  from oeqa.core.target.ssh import OESSHTarget
 from fvp import runner
 
 
-class OEFVPSSHTarget(OESSHTarget):
+class OEFVPTarget(OESSHTarget):
     """
-    Base class for meta-arm FVP targets.
-    Contains common logic to start and stop an FVP.
+    For compatibility with OE-core test cases, this target's start() method
+    waits for a Linux shell before returning to ensure that SSH commands work
+    with the default test dependencies.
     """
+    DEFAULT_CONSOLE = "default"
+
     def __init__(self, logger, target_ip, server_ip, timeout=300, user='root',
                  port=None, dir_image=None, rootfs=None, bootlog=None, **kwargs):
         super().__init__(logger, target_ip, server_ip, timeout, user, port)
@@ -19,90 +22,45 @@  class OEFVPSSHTarget(OESSHTarget):
         basename = pathlib.Path(rootfs)
         basename = basename.name.replace("".join(basename.suffixes), "")
         self.fvpconf = image_dir / (basename + ".fvpconf")
-        self.bootlog = bootlog
-
         if not self.fvpconf.exists():
             raise FileNotFoundError(f"Cannot find {self.fvpconf}")
 
-    def _after_start(self):
-        pass
+        self.bootlog = bootlog
+        self.terminals = {}
+        self.booted = False
 
     def start(self, **kwargs):
         self.fvp_log = self._create_logfile("fvp")
         self.fvp = runner.FVPRunner(self.logger)
         self.fvp.start(self.fvpconf, stdout=self.fvp_log)
         self.logger.debug(f"Started FVP PID {self.fvp.pid()}")
-        self._after_start()
-
-    def stop(self, **kwargs):
-        returncode = self.fvp.stop()
-        self.logger.debug(f"Stopped FVP with return code {returncode}")
+        self._setup_consoles()
 
-    def _create_logfile(self, name):
-        if not self.bootlog:
-            return None
-
-        test_log_path = pathlib.Path(self.bootlog).parent
-        test_log_suffix = pathlib.Path(self.bootlog).suffix
-        fvp_log_file = f"{name}_log{test_log_suffix}"
-        fvp_log_path = pathlib.Path(test_log_path, fvp_log_file)
-        fvp_log_symlink = pathlib.Path(test_log_path, f"{name}_log")
-        try:
-            os.remove(fvp_log_symlink)
-        except:
-            pass
-        os.symlink(fvp_log_file, fvp_log_symlink)
-        return open(fvp_log_path, 'wb')
-
-
-class OEFVPTarget(OEFVPSSHTarget):
-    """
-    For compatibility with OE-core test cases, this target's start() method
-    waits for a Linux shell before returning to ensure that SSH commands work
-    with the default test dependencies.
-    """
-    def __init__(self, logger, target_ip, server_ip, **kwargs):
-        super().__init__(logger, target_ip, server_ip, **kwargs)
-        self.logfile = self.bootlog and open(self.bootlog, "wb") or None
+    def await_boot(self):
+        if self.booted:
+            return
 
         # FVPs boot slowly, so allow ten minutes
-        self.boot_timeout = 10 * 60
+        boot_timeout = 10*60
+        try:
+            self.expect(OEFVPTarget.DEFAULT_CONSOLE, "login\\:", timeout=boot_timeout)
+            self.logger.debug("Found login prompt")
+            self.booted = True
+        except pexpect.TIMEOUT:
+            self.logger.info("Timed out waiting for login prompt.")
+            self.logger.info("Boot log follows:")
+            self.logger.info(b"\n".join(self.before(OEFVPTarget.DEFAULT_CONSOLE).splitlines()[-200:]).decode("utf-8", errors="replace"))
+            raise RuntimeError("Failed to start FVP.")
 
-    def _after_start(self):
-        with open(self.fvp_log.name, 'rb') as logfile:
-            parser = runner.ConsolePortParser(logfile)
-            config = self.fvp.getConfig()
-            self.logger.debug(f"Awaiting console on terminal {config['consoles']['default']}")
-            port = parser.parse_port(config['consoles']['default'])
-            console = self.fvp.create_pexpect(port)
-            try:
-                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(console.before.splitlines()[-200:]).decode("utf-8", errors="replace"))
-                raise RuntimeError("Failed to start FVP.")
-
-
-class OEFVPSerialTarget(OEFVPSSHTarget):
-    """
-    This target is intended for interaction with the target over one or more
-    telnet consoles using pexpect.
-
-    This still depends on OEFVPSSHTarget so SSH commands can still be run on
-    the target, but note that this class does not inherently guarantee that
-    the SSH server is running prior to running test cases. Test cases that use
-    SSH should first validate that SSH is available, e.g. by depending on the
-    "linuxboot" test case in meta-arm.
-    """
-    DEFAULT_CONSOLE = "default"
+    def stop(self, **kwargs):
+        returncode = self.fvp.stop()
+        self.logger.debug(f"Stopped FVP with return code {returncode}")
 
-    def __init__(self, logger, target_ip, server_ip, **kwargs):
-        super().__init__(logger, target_ip, server_ip, **kwargs)
-        self.terminals = {}
+    def run(self, cmd, timeout=None):
+        self.await_boot()
+        return super().run(cmd, timeout)
 
-    def _after_start(self):
+    def _setup_consoles(self):
         with open(self.fvp_log.name, 'rb') as logfile:
             parser = runner.ConsolePortParser(logfile)
             config = self.fvp.getConfig()
@@ -119,6 +77,22 @@  class OEFVPSerialTarget(OEFVPSSHTarget):
                     default_test_file = f"{name}_log{self.test_log_suffix}"
                     os.symlink(default_test_file, self.bootlog)
 
+    def _create_logfile(self, name):
+        if not self.bootlog:
+            return None
+
+        test_log_path = pathlib.Path(self.bootlog).parent
+        test_log_suffix = pathlib.Path(self.bootlog).suffix
+        fvp_log_file = f"{name}_log{test_log_suffix}"
+        fvp_log_path = pathlib.Path(test_log_path, fvp_log_file)
+        fvp_log_symlink = pathlib.Path(test_log_path, f"{name}_log")
+        try:
+            os.remove(fvp_log_symlink)
+        except:
+            pass
+        os.symlink(fvp_log_file, fvp_log_symlink)
+        return open(fvp_log_path, 'wb')
+
     def _get_terminal(self, name):
         return self.terminals[name]
 
diff --git a/meta-arm/lib/oeqa/runtime/cases/linuxboot.py b/meta-arm/lib/oeqa/runtime/cases/linuxboot.py
index 99a8e78b..184eb6c9 100644
--- a/meta-arm/lib/oeqa/runtime/cases/linuxboot.py
+++ b/meta-arm/lib/oeqa/runtime/cases/linuxboot.py
@@ -5,9 +5,7 @@  from oeqa.runtime.case import OERuntimeTestCase
 
 class LinuxBootTest(OERuntimeTestCase):
     """
-    This test case is only compatible with the OEFVPSerialTarget as it uses
-    the pexpect interface. It waits for a Linux login prompt on the default
-    console.
+    This test waits for a Linux login prompt on the default console.
     """
 
     def setUp(self):