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 <jpewhac...@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 -- _______________________________________________ yocto mailing list yocto@yoctoproject.org https://lists.yoctoproject.org/listinfo/yocto