Import 80d60e7 from yoctoproject.org meta-arm

To support ARMv8 SoCs.

meta-arm has several patch files.  Since they are maintained by the
upstream meta-arm community, add meta-arm to the ignore list in
run-repotest.

Change-Id: Ia87a2e947bbabd347d256eccc47a343e1c885479
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/meta-arm/meta-arm/lib/oeqa/controllers/__init__.py b/meta-arm/meta-arm/lib/oeqa/controllers/__init__.py
new file mode 100644
index 0000000..df3c142
--- /dev/null
+++ b/meta-arm/meta-arm/lib/oeqa/controllers/__init__.py
@@ -0,0 +1,3 @@
+# This is needed so that multiple locations can provide the same package
+from pkgutil import extend_path
+__path__ = extend_path(__path__, __name__)
diff --git a/meta-arm/meta-arm/lib/oeqa/controllers/fvp.py b/meta-arm/meta-arm/lib/oeqa/controllers/fvp.py
new file mode 100644
index 0000000..c8dcf29
--- /dev/null
+++ b/meta-arm/meta-arm/lib/oeqa/controllers/fvp.py
@@ -0,0 +1,147 @@
+import asyncio
+import pathlib
+import pexpect
+import os
+
+from oeqa.core.target.ssh import OESSHTarget
+from fvp import conffile, runner
+
+
+class OEFVPSSHTarget(OESSHTarget):
+    """
+    Base class for meta-arm FVP targets.
+    Contains common logic to start and stop an FVP.
+    """
+    def __init__(self, logger, target_ip, server_ip, timeout=300, user='root',
+                 port=None, dir_image=None, rootfs=None, **kwargs):
+        super().__init__(logger, target_ip, server_ip, timeout, user, port)
+        image_dir = pathlib.Path(dir_image)
+        # rootfs may have multiple extensions so we need to strip *all* suffixes
+        basename = pathlib.Path(rootfs)
+        basename = basename.name.replace("".join(basename.suffixes), "")
+        self.fvpconf = image_dir / (basename + ".fvpconf")
+        self.config = conffile.load(self.fvpconf)
+
+        if not self.fvpconf.exists():
+            raise FileNotFoundError(f"Cannot find {self.fvpconf}")
+
+    async def boot_fvp(self):
+        self.fvp = runner.FVPRunner(self.logger)
+        await self.fvp.start(self.config)
+        self.logger.debug(f"Started FVP PID {self.fvp.pid()}")
+        await self._after_start()
+
+    async def _after_start(self):
+        pass
+
+    async def _after_stop(self):
+        pass
+
+    async def stop_fvp(self):
+        returncode = await self.fvp.stop()
+        await self._after_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()
+        loop = asyncio.get_event_loop()
+        loop.run_until_complete(asyncio.gather(self.boot_fvp()))
+
+    def stop(self, **kwargs):
+        loop = asyncio.get_event_loop()
+        loop.run_until_complete(asyncio.gather(self.stop_fvp()))
+
+
+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, bootlog=None, **kwargs):
+        super().__init__(logger, target_ip, server_ip, **kwargs)
+        self.logfile = bootlog and open(bootlog, "wb") or None
+
+        # FVPs boot slowly, so allow ten minutes
+        self.boot_timeout = 10 * 60
+
+    async def _after_start(self):
+        self.logger.debug(f"Awaiting console on terminal {self.config['consoles']['default']}")
+        console = await self.fvp.create_pexpect(self.config['consoles']['default'])
+        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 __init__(self, logger, target_ip, server_ip, bootlog=None, **kwargs):
+        super().__init__(logger, target_ip, server_ip, **kwargs)
+        self.terminals = {}
+
+        self.test_log_path = pathlib.Path(bootlog).parent
+        self.test_log_suffix = pathlib.Path(bootlog).suffix
+        self.bootlog = bootlog
+
+    async 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] = \
+            await 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)
+        fvp_log_symlink = pathlib.Path(self.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')
+
+    async def _after_start(self):
+        for name, console in self.config["consoles"].items():
+            await self._add_terminal(name, console)
+
+            # testimage.bbclass expects to see a log file at `bootlog`,
+            # so make a symlink to the 'default' log file
+            if name == 'default':
+                default_test_file = f"{name}_log{self.test_log_suffix}"
+                os.symlink(default_test_file, self.bootlog)
+
+    def _get_terminal(self, name):
+        return self.terminals[name]
+
+    def __getattr__(self, name):
+        """
+        Magic method which automatically exposes the whole pexpect API on the
+        target, with the first argument being the terminal name.
+
+        e.g. self.target.expect(self.target.DEFAULT_CONSOLE, "login\\:")
+        """
+        def call_pexpect(terminal, *args, **kwargs):
+            attr = getattr(self.terminals[terminal], name)
+            if callable(attr):
+                return attr(*args, **kwargs)
+            else:
+                return attr
+
+        return call_pexpect
diff --git a/meta-arm/meta-arm/lib/oeqa/runtime/cases/linuxboot.py b/meta-arm/meta-arm/lib/oeqa/runtime/cases/linuxboot.py
new file mode 100644
index 0000000..8994405
--- /dev/null
+++ b/meta-arm/meta-arm/lib/oeqa/runtime/cases/linuxboot.py
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: MIT
+
+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.
+    """
+
+    def setUp(self):
+        self.console = self.target.DEFAULT_CONSOLE
+
+    def test_linux_boot(self):
+        self.logger.info(f"{self.console}: Waiting for login prompt")
+        self.target.expect(self.console, r"login\:", timeout=10*60)
diff --git a/meta-arm/meta-arm/lib/oeqa/selftest/cases/runfvp.py b/meta-arm/meta-arm/lib/oeqa/selftest/cases/runfvp.py
new file mode 100644
index 0000000..d1e452f
--- /dev/null
+++ b/meta-arm/meta-arm/lib/oeqa/selftest/cases/runfvp.py
@@ -0,0 +1,109 @@
+import asyncio
+import os
+import pathlib
+import subprocess
+import tempfile
+import unittest.mock
+
+from oeqa.selftest.case import OESelftestTestCase
+
+runfvp = pathlib.Path(__file__).parents[5] / "scripts" / "runfvp"
+testdir = pathlib.Path(__file__).parent / "tests"
+
+class RunFVPTests(OESelftestTestCase):
+    def setUpLocal(self):
+        self.assertTrue(runfvp.exists())
+
+    def run_fvp(self, *args, should_succeed=True):
+        """
+        Call runfvp passing any arguments. If check is True verify return stdout
+        on exit code 0 or fail the test, otherwise return the CompletedProcess
+        instance.
+        """
+        # Put the test directory in PATH so that any mock FVPs are found first
+        newenv = {"PATH": str(testdir) + ":" + os.environ["PATH"]}
+        cli = [runfvp,] + list(args)
+        print(f"Calling {cli}")
+        ret = subprocess.run(cli, env=newenv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
+        if should_succeed:
+            self.assertEqual(ret.returncode, 0, f"runfvp exit {ret.returncode}, output: {ret.stdout}")
+            return ret.stdout
+        else:
+            self.assertNotEqual(ret.returncode, 0, f"runfvp exit {ret.returncode}, output: {ret.stdout}")
+            return ret.stdout
+
+    def test_help(self):
+        output = self.run_fvp("--help")
+        self.assertIn("Run images in a FVP", output)
+
+    def test_bad_options(self):
+        self.run_fvp("--this-is-an-invalid-option", should_succeed=False)
+
+    def test_run_auto_tests(self):
+        newenv = {"PATH": str(testdir) + ":" + os.environ["PATH"]}
+
+        cases = list(testdir.glob("auto-*.json"))
+        if not cases:
+            self.fail("No tests found")
+        for case in cases:
+            with self.subTest(case=case.stem):
+                self.run_fvp(case)
+
+    def test_fvp_options(self):
+        # test-parameter sets one argument, add another manually
+        self.run_fvp(testdir / "test-parameter.json", "--", "--parameter", "board.dog=woof")
+
+class ConfFileTests(OESelftestTestCase):
+    def test_no_exe(self):
+        from fvp import conffile
+        with tempfile.NamedTemporaryFile('w') as tf:
+            tf.write('{}')
+            tf.flush()
+
+            with self.assertRaises(ValueError):
+                conffile.load(tf.name)
+
+    def test_minimal(self):
+        from fvp import conffile
+        with tempfile.NamedTemporaryFile('w') as tf:
+            tf.write('{"exe": "FVP_Binary"}')
+            tf.flush()
+
+            conf = conffile.load(tf.name)
+            self.assertTrue('fvp-bindir' in conf)
+            self.assertTrue('fvp-bindir' in conf)
+            self.assertTrue("exe" in conf)
+            self.assertTrue("parameters" in conf)
+            self.assertTrue("data" in conf)
+            self.assertTrue("applications" in conf)
+            self.assertTrue("terminals" in conf)
+            self.assertTrue("args" in conf)
+            self.assertTrue("consoles" in conf)
+
+
+class RunnerTests(OESelftestTestCase):
+    def create_mock(self):
+        return unittest.mock.patch("asyncio.create_subprocess_exec")
+
+    def test_start(self):
+        from fvp import runner
+        with self.create_mock() as m:
+            fvp = runner.FVPRunner(self.logger)
+            asyncio.run(fvp.start({
+                "fvp-bindir": "/usr/bin",
+                "exe": "FVP_Binary",
+                "parameters": {'foo': 'bar'},
+                "data": ['data1'],
+                "applications": {'a1': 'file'},
+                "terminals": {},
+                "args": ['--extra-arg'],
+            }))
+
+            m.assert_called_once_with('/usr/bin/FVP_Binary',
+                '--parameter', 'foo=bar',
+                '--data', 'data1',
+                '--application', 'a1=file',
+                '--extra-arg',
+                stdin=unittest.mock.ANY,
+                stdout=unittest.mock.ANY,
+                stderr=unittest.mock.ANY)
diff --git a/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/auto-basic.json b/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/auto-basic.json
new file mode 100644
index 0000000..476eb57
--- /dev/null
+++ b/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/auto-basic.json
@@ -0,0 +1,3 @@
+{
+    "exe": "auto-basic.sh"
+}
diff --git a/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/auto-basic.sh b/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/auto-basic.sh
new file mode 100755
index 0000000..ea9abac
--- /dev/null
+++ b/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/auto-basic.sh
@@ -0,0 +1,11 @@
+#! /bin/sh
+
+set -e -u
+
+if [ $# = 0 ]; then
+    echo No arguments as expected
+    exit 0
+else
+    echo Unexpected arguments: $*
+    exit 1
+fi
diff --git a/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/auto-parameters.json b/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/auto-parameters.json
new file mode 100644
index 0000000..0c7d4ef
--- /dev/null
+++ b/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/auto-parameters.json
@@ -0,0 +1,7 @@
+{
+    "exe": "test-parameters.py",
+    "parameters": {
+        "board.cow": "moo",
+        "board.dog": "woof"
+    }
+}
diff --git a/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/mock-fvp.py b/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/mock-fvp.py
new file mode 100755
index 0000000..2213c9f
--- /dev/null
+++ b/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/mock-fvp.py
@@ -0,0 +1,22 @@
+#! /usr/bin/env python3
+
+import argparse
+import sys
+
+def do_test_parameters(args):
+    if not args.parameter or set(args.parameter) != set(("board.cow=moo", "board.dog=woof")):
+        print(f"Unexpected arguments: {args}")
+        sys.exit(1)
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    parser.add_argument("-C", "--parameter", action="append")
+    args = parser.parse_args()
+
+    function = "do_" + parser.prog.replace("-", "_").replace(".py", "")
+    if function in locals():
+        locals()[function](args)
+    else:
+        print(f"Unknown mock mode {parser.prog}")
+        sys.exit(1)
diff --git a/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/test-parameter.json b/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/test-parameter.json
new file mode 100644
index 0000000..9b565f2
--- /dev/null
+++ b/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/test-parameter.json
@@ -0,0 +1,6 @@
+{
+    "exe": "test-parameters.py",
+    "parameters": {
+        "board.cow": "moo"
+    }
+}
diff --git a/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/test-parameters.py b/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/test-parameters.py
new file mode 120000
index 0000000..c734eec
--- /dev/null
+++ b/meta-arm/meta-arm/lib/oeqa/selftest/cases/tests/test-parameters.py
@@ -0,0 +1 @@
+mock-fvp.py
\ No newline at end of file