Adds the framework for testing SDKs that ties into the oeqa test
framework. This allows commands like:
$ bitbake -c testsdk ...
to be run for MinGW SDKs.
The test framework currently executes all tests under Wine in lieu of
having access to actual Windows machines.
[YOCTO #13020]
Signed-off-by: Joshua Watt <***@gmail.com>
---
conf/machine-sdk/i686-mingw32.conf | 1 +
conf/machine-sdk/include/mingw32-common.inc | 7 ++
conf/machine-sdk/x86_64-mingw32.conf | 1 +
lib/oeqa/sdkmingw/__init__.py | 0
lib/oeqa/sdkmingw/case.py | 87 +++++++++++++++++++++
lib/oeqa/sdkmingw/cases/__init__.py | 0
lib/oeqa/sdkmingw/context.py | 69 ++++++++++++++++
lib/oeqa/sdkmingw/testsdk.py | 42 ++++++++++
8 files changed, 207 insertions(+)
create mode 100644 lib/oeqa/sdkmingw/__init__.py
create mode 100644 lib/oeqa/sdkmingw/case.py
create mode 100644 lib/oeqa/sdkmingw/cases/__init__.py
create mode 100644 lib/oeqa/sdkmingw/context.py
create mode 100644 lib/oeqa/sdkmingw/testsdk.py
diff --git a/conf/machine-sdk/i686-mingw32.conf b/conf/machine-sdk/i686-mingw32.conf
index 5090168..fef48b5 100644
--- a/conf/machine-sdk/i686-mingw32.conf
+++ b/conf/machine-sdk/i686-mingw32.conf
@@ -1,3 +1,4 @@
SDK_ARCH = "i686"
+TESTSDK_WINEARCH = "win32"
require conf/machine-sdk/include/mingw32-common.inc
diff --git a/conf/machine-sdk/include/mingw32-common.inc b/conf/machine-sdk/include/mingw32-common.inc
index 733d092..71e8d45 100644
--- a/conf/machine-sdk/include/mingw32-common.inc
+++ b/conf/machine-sdk/include/mingw32-common.inc
@@ -26,6 +26,9 @@ SDKPKGSUFFIX = "nativesdk-mingw32"
MACHINEOVERRIDES .= ":sdkmingw32"
+TESTSDK_CLASS_NAME = "oeqa.sdkmingw.testsdk.TestSDKMinGW"
+TESTSDKEXT_CLASS_NAME = ""
+
WINDRES_mingw32 = "${HOST_PREFIX}windres --include-dir=${STAGING_INCDIR}"
RC_mingw32 = "${WINDRES}"
@@ -39,3 +42,7 @@ DISABLE_STATIC_mingw32 = ""
# disable security flags
GCCPIE_mingw32 = ""
+
+# wine and wineserver are required to test MinGW SDKs
+HOSTTOOLS += "${@'wine wineserver' if (bb.utils.contains_any('IMAGE_CLASSES', 'testsdk-mingw', True, False, d) or any(x in (d.getVar("BBINCLUDED") or "") for x in ["testsdk-mingw.bbclass"])) else ''}"
+
diff --git a/conf/machine-sdk/x86_64-mingw32.conf b/conf/machine-sdk/x86_64-mingw32.conf
index fc53822..188debc 100644
--- a/conf/machine-sdk/x86_64-mingw32.conf
+++ b/conf/machine-sdk/x86_64-mingw32.conf
@@ -1,3 +1,4 @@
SDK_ARCH = "x86_64"
+TESTSDK_WINEARCH = "win64"
require conf/machine-sdk/include/mingw32-common.inc
diff --git a/lib/oeqa/sdkmingw/__init__.py b/lib/oeqa/sdkmingw/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lib/oeqa/sdkmingw/case.py b/lib/oeqa/sdkmingw/case.py
new file mode 100644
index 0000000..169c143
--- /dev/null
+++ b/lib/oeqa/sdkmingw/case.py
@@ -0,0 +1,87 @@
+# Copyright 2018 by Garmin Ltd. or its subsidiaries
+# Released under the MIT license (see COPYING.MIT)
+
+import subprocess
+import os
+import tempfile
+import shutil
+import bb
+
+from oeqa.core.utils.path import remove_safe
+from oeqa.sdk.case import OESDKTestCase
+
+from oeqa.utils.subprocesstweak import errors_have_output
+errors_have_output()
+
+class OESDKMinGWTestCase(OESDKTestCase):
+ def setUp(self):
+ super().setUp()
+
+ self.test_dir = tempfile.mkdtemp(prefix=self.__class__.__name__ + '-', dir=self.tc.sdk_dir)
+ self.addCleanup(lambda: bb.utils.prunedir(self.test_dir))
+
+ self.wine_test_dir = self.tc.wine_path(self.test_dir)
+
+ def copyTestFile(self, src, dest=None):
+ dest_path = dest or os.path.join(self.test_dir, os.path.basename(src))
+ shutil.copyfile(src, dest_path)
+ self.addCleanup(lambda: remove_safe(dest_path))
+
+ def fetch(self, url, destdir=None, dl_dir=None, archive=None):
+ if not destdir:
+ destdir = self.test_dir
+
+ if not dl_dir:
+ dl_dir = self.td.get('DL_DIR', None)
+
+ if not archive:
+ from urllib.parse import urlparse
+ archive = os.path.basename(urlparse(url).path)
+
+ if dl_dir:
+ tarball = os.path.join(dl_dir, archive)
+ if os.path.exists(tarball):
+ return tarball
+
+ tarball = os.path.join(destdir, archive)
+ subprocess.check_output(["wget", "-O", tarball, url])
+ return tarball
+
+
+ def _run(self, cmd):
+ import shlex
+
+ def strip_quotes(s):
+ if s[0] == '"' and s[-1] == '"':
+ return s[1:-1]
+ return s
+
+ command = ['wine', 'cmd', '/c', self.tc.wine_sdk_env, '>', 'NUL', '&&', 'cd', self.wine_test_dir, '&&']
+
+ # Perform some massaging so that commands can be written naturally in
+ # test cases. shlex.split() in Non-posix mode gets us most of the way
+ # there, but it leaves the quotes around a quoted argument, so we
+ # remove them manually.
+ command.extend(strip_quotes(s) for s in shlex.split(cmd, posix=False))
+
+ return subprocess.check_output(command, env=self.tc.get_wine_env(),
+ stderr=subprocess.STDOUT, universal_newlines=True)
+
+ def assertIsTargetElf(self, path):
+ import oe.qa
+ import oe.elf
+
+ elf = oe.qa.ELFFile(path)
+ elf.open()
+
+ if not getattr(self, 'target_os', None):
+ output = self._run("echo %OECORE_TARGET_OS%:%OECORE_TARGET_ARCH%")
+ self.target_os, self.target_arch = output.strip().split(":")
+
+ machine_data = oe.elf.machine_dict(None)[self.target_os][self.target_arch]
+ (machine, osabi, abiversion, endian, bits) = machine_data
+
+ self.assertEqual(machine, elf.machine())
+ self.assertEqual(bits, elf.abiSize())
+ self.assertEqual(endian, elf.isLittleEndian())
+
diff --git a/lib/oeqa/sdkmingw/cases/__init__.py b/lib/oeqa/sdkmingw/cases/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lib/oeqa/sdkmingw/context.py b/lib/oeqa/sdkmingw/context.py
new file mode 100644
index 0000000..edabcbd
--- /dev/null
+++ b/lib/oeqa/sdkmingw/context.py
@@ -0,0 +1,69 @@
+# Copyright (C) 2018 by Garmin Ltd. or its subsidiaries
+# Released under the MIT license (see COPYING.MIT)
+import os
+import subprocess
+
+from oeqa.sdk.context import OESDKTestContext, OESDKTestContextExecutor
+
+from oeqa.utils.subprocesstweak import errors_have_output
+errors_have_output()
+
+class OESDKMinGWTestContext(OESDKTestContext):
+ sdk_files_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "files")
+
+ def __init__(self, td=None, logger=None, sdk_dir=None, sdk_env=None, wine_prefix=None,
+ wine_arch=None, target_pkg_manifest=None, host_pkg_manifest=None):
+ super(OESDKMinGWTestContext, self).__init__(td, logger, sdk_dir, sdk_env, target_pkg_manifest, host_pkg_manifest)
+ self.wine_prefix = wine_prefix
+ self.wine_arch = wine_arch
+ self.wine_sdk_dir = self.wine_path(sdk_dir)
+ self.wine_sdk_env = self.wine_path(sdk_env)
+
+ def get_wine_env(self):
+ env = os.environ.copy()
+
+ # Turn off all Wine debug logging so it doesn't interfere with command output
+ env['WINEDEBUG'] = '-all'
+
+ env['WINEPREFIX'] = self.wine_prefix
+ env['WINEARCH'] = self.wine_arch
+
+ # Convenience variables to make test cases easier to write
+ env['SDK_DIR'] = getattr(self, 'wine_sdk_dir', '')
+
+ return env
+
+ def wine_path(self, p):
+ """
+ Converts a host POSIX path to a path in Wine
+ """
+ o = subprocess.check_output(['wine', 'winepath', '-w', p], env=self.get_wine_env())
+ return o.decode('utf-8').rstrip()
+
+
+class OESDKMinGWTestContextExecutor(OESDKTestContextExecutor):
+ _context_class = OESDKMinGWTestContext
+
+ name = 'sdk-mingw'
+ help = 'MinGW sdk test component'
+ description = 'executes MinGW sdk tests'
+
+ default_cases = [os.path.join(os.path.abspath(os.path.dirname(__file__)),
+ 'cases')]
+
+ def register_commands(self, logger, subparsers):
+ super(OESDKMinGWTestContextExecutor, self).register_commands(logger, subparsers)
+
+ wine_group = self.parser.add_argument_group('wine options')
+ wine_group.add_argument('--wine-prefix', action='store',
+ help='Wine prefix (bottle). Default is $SDK_DIR/.wine')
+ wine_group.add_argument('--wine-arch', action='store', choices=('win32', 'win64'),
+ default='win64', help='Wine architecture. Defaults to %(default)s')
+
+ def _process_args(self, logger, args):
+ super(OESDKMinGWTestContextExecutor, self)._process_args(logger, args)
+ self.tc_kwargs['init']['wine_prefix'] = args.wine_prefix or os.path.join(args.sdk_dir, '.wine')
+ self.tc_kwargs['init']['wine_arch'] = args.wine_arch
+
+_executor_class = OESDKMinGWTestContextExecutor
+
diff --git a/lib/oeqa/sdkmingw/testsdk.py b/lib/oeqa/sdkmingw/testsdk.py
new file mode 100644
index 0000000..85fe3c6
--- /dev/null
+++ b/lib/oeqa/sdkmingw/testsdk.py
@@ -0,0 +1,42 @@
+# Copyright 2018 by Garmin Ltd. or its subsidiaries
+# Released under the MIT license (see COPYING.MIT)
+
+from oeqa.sdk.testsdk import TestSDK
+from oeqa.sdkmingw.context import OESDKMinGWTestContext, OESDKMinGWTestContextExecutor
+
+class TestSDKMinGW(TestSDK):
+ context_executor_class = OESDKMinGWTestContextExecutor
+ context_class = OESDKMinGWTestContext
+
+ def get_tcname(self, d):
+ """
+ Get the name of the SDK file
+ """
+ return d.expand("${SDK_DEPLOY}/${TOOLCHAIN_OUTPUTNAME}.tar.xz")
+
+ def extract_sdk(self, tcname, sdk_dir, d):
+ """
+ Extract the SDK to the specified location
+ """
+ import subprocess
+
+ try:
+ # TODO: It would be nice to try and extract the SDK in Wine to make
+ # sure it is well formed
+ subprocess.check_output(['tar', '-xf', tcname, '-C', sdk_dir])
+ except subprocess.CalledProcessError as e:
+ bb.fatal("Couldn't install the SDK:\n%s" % e.output.decode("utf-8"))
+
+ def setup_context(self, d):
+ """
+ Return a dictionary of additional arguments that should be passed to
+ the context_class on construction
+ """
+ wine_prefix = d.getVar('TESTSDK_WINEPREFIX') or d.expand('${WORKDIR}/testimage-wine/')
+ bb.utils.remove(wine_prefix, True)
+
+ return {
+ 'wine_prefix': wine_prefix,
+ 'wine_arch': d.getVar('TESTSDK_WINEARCH') or 'win64'
+ }
+
--
2.19.1
--