Yocto 2.4

Move OpenBMC to Yocto 2.4(rocko)

Tested: Built and verified Witherspoon and Palmetto images
Change-Id: I12057b18610d6fb0e6903c60213690301e9b0c67
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/__init__.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/__init__.py
deleted file mode 100644
index 3ad9513..0000000
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from pkgutil import extend_path
-__path__ = extend_path(__path__, __name__)
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/base.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/base.py
deleted file mode 100644
index 47a8ea8..0000000
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/base.py
+++ /dev/null
@@ -1,235 +0,0 @@
-# Copyright (c) 2013 Intel Corporation
-#
-# Released under the MIT license (see COPYING.MIT)
-
-
-# DESCRIPTION
-# Base class inherited by test classes in meta/lib/oeqa/selftest
-
-import unittest
-import os
-import sys
-import shutil
-import logging
-import errno
-
-import oeqa.utils.ftools as ftools
-from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_test_layer
-from oeqa.utils.decorators import LogResults
-from random import choice
-import glob
-
-@LogResults
-class oeSelfTest(unittest.TestCase):
-
-    log = logging.getLogger("selftest.base")
-    longMessage = True
-
-    def __init__(self, methodName="runTest"):
-        self.builddir = os.environ.get("BUILDDIR")
-        self.localconf_path = os.path.join(self.builddir, "conf/local.conf")
-        self.localconf_backup = os.path.join(self.builddir, "conf/local.bk")
-        self.testinc_path = os.path.join(self.builddir, "conf/selftest.inc")
-        self.local_bblayers_path = os.path.join(self.builddir, "conf/bblayers.conf")
-        self.local_bblayers_backup = os.path.join(self.builddir,
-                                                  "conf/bblayers.bk")
-        self.testinc_bblayers_path = os.path.join(self.builddir, "conf/bblayers.inc")
-        self.machineinc_path = os.path.join(self.builddir, "conf/machine.inc")
-        self.testlayer_path = oeSelfTest.testlayer_path
-        self._extra_tear_down_commands = []
-        self._track_for_cleanup = [
-            self.testinc_path, self.testinc_bblayers_path,
-            self.machineinc_path, self.localconf_backup,
-            self.local_bblayers_backup]
-        super(oeSelfTest, self).__init__(methodName)
-
-    def setUp(self):
-        os.chdir(self.builddir)
-        # Check if local.conf or bblayers.conf files backup exists
-        # from a previous failed test and restore them
-        if os.path.isfile(self.localconf_backup) or os.path.isfile(
-                self.local_bblayers_backup):
-            self.log.debug("Found a local.conf and/or bblayers.conf backup \
-from a previously aborted test. Restoring these files now, but tests should \
-be re-executed from a clean environment to ensure accurate results.")
-            try:
-                shutil.copyfile(self.localconf_backup, self.localconf_path)
-            except OSError as e:
-                if e.errno != errno.ENOENT:
-                    raise
-            try:
-                shutil.copyfile(self.local_bblayers_backup,
-                                self.local_bblayers_path)
-            except OSError as e:
-                if e.errno != errno.ENOENT:
-                    raise
-        else:
-            # backup local.conf and bblayers.conf
-            shutil.copyfile(self.localconf_path, self.localconf_backup)
-            shutil.copyfile(self.local_bblayers_path,
-                            self.local_bblayers_backup)
-            self.log.debug("Creating local.conf and bblayers.conf backups.")
-        # we don't know what the previous test left around in config or inc files
-        # if it failed so we need a fresh start
-        try:
-            os.remove(self.testinc_path)
-        except OSError as e:
-            if e.errno != errno.ENOENT:
-                raise
-        for root, _, files in os.walk(self.testlayer_path):
-            for f in files:
-                if f == 'test_recipe.inc':
-                    os.remove(os.path.join(root, f))
-
-        for incl_file in [self.testinc_bblayers_path, self.machineinc_path]:
-            try:
-                os.remove(incl_file)
-            except OSError as e:
-                if e.errno != errno.ENOENT:
-                    raise
-
-        # Get CUSTOMMACHINE from env (set by --machine argument to oe-selftest)
-        custommachine = os.getenv('CUSTOMMACHINE')
-        if custommachine:
-            if custommachine == 'random':
-                machine = get_random_machine()
-            else:
-                machine = custommachine
-            machine_conf = 'MACHINE ??= "%s"\n' % machine
-            self.set_machine_config(machine_conf)
-            print('MACHINE: %s' % machine)
-
-        # tests might need their own setup
-        # but if they overwrite this one they have to call
-        # super each time, so let's give them an alternative
-        self.setUpLocal()
-
-    def setUpLocal(self):
-        pass
-
-    def tearDown(self):
-        if self._extra_tear_down_commands:
-            failed_extra_commands = []
-            for command in self._extra_tear_down_commands:
-                result = runCmd(command, ignore_status=True)
-                if not result.status ==  0:
-                    failed_extra_commands.append(command)
-            if failed_extra_commands:
-                self.log.warning("tearDown commands have failed: %s" % ', '.join(map(str, failed_extra_commands)))
-                self.log.debug("Trying to move on.")
-            self._extra_tear_down_commands = []
-
-        if self._track_for_cleanup:
-            for path in self._track_for_cleanup:
-                if os.path.isdir(path):
-                    shutil.rmtree(path)
-                if os.path.isfile(path):
-                    os.remove(path)
-            self._track_for_cleanup = []
-
-        self.tearDownLocal()
-
-    def tearDownLocal(self):
-        pass
-
-    # add test specific commands to the tearDown method.
-    def add_command_to_tearDown(self, command):
-        self.log.debug("Adding command '%s' to tearDown for this test." % command)
-        self._extra_tear_down_commands.append(command)
-    # add test specific files or directories to be removed in the tearDown method
-    def track_for_cleanup(self, path):
-        self.log.debug("Adding path '%s' to be cleaned up when test is over" % path)
-        self._track_for_cleanup.append(path)
-
-    # write to <builddir>/conf/selftest.inc
-    def write_config(self, data):
-        self.log.debug("Writing to: %s\n%s\n" % (self.testinc_path, data))
-        ftools.write_file(self.testinc_path, data)
-
-        custommachine = os.getenv('CUSTOMMACHINE')
-        if custommachine and 'MACHINE' in data:
-            machine = get_bb_var('MACHINE')
-            self.log.warning('MACHINE overridden: %s' % machine)
-
-    # append to <builddir>/conf/selftest.inc
-    def append_config(self, data):
-        self.log.debug("Appending to: %s\n%s\n" % (self.testinc_path, data))
-        ftools.append_file(self.testinc_path, data)
-
-        custommachine = os.getenv('CUSTOMMACHINE')
-        if custommachine and 'MACHINE' in data:
-            machine = get_bb_var('MACHINE')
-            self.log.warning('MACHINE overridden: %s' % machine)
-
-    # remove data from <builddir>/conf/selftest.inc
-    def remove_config(self, data):
-        self.log.debug("Removing from: %s\n%s\n" % (self.testinc_path, data))
-        ftools.remove_from_file(self.testinc_path, data)
-
-    # write to meta-sefltest/recipes-test/<recipe>/test_recipe.inc
-    def write_recipeinc(self, recipe, data):
-        inc_file = os.path.join(self.testlayer_path, 'recipes-test', recipe, 'test_recipe.inc')
-        self.log.debug("Writing to: %s\n%s\n" % (inc_file, data))
-        ftools.write_file(inc_file, data)
-
-    # append data to meta-sefltest/recipes-test/<recipe>/test_recipe.inc
-    def append_recipeinc(self, recipe, data):
-        inc_file = os.path.join(self.testlayer_path, 'recipes-test', recipe, 'test_recipe.inc')
-        self.log.debug("Appending to: %s\n%s\n" % (inc_file, data))
-        ftools.append_file(inc_file, data)
-
-    # remove data from meta-sefltest/recipes-test/<recipe>/test_recipe.inc
-    def remove_recipeinc(self, recipe, data):
-        inc_file = os.path.join(self.testlayer_path, 'recipes-test', recipe, 'test_recipe.inc')
-        self.log.debug("Removing from: %s\n%s\n" % (inc_file, data))
-        ftools.remove_from_file(inc_file, data)
-
-    # delete meta-sefltest/recipes-test/<recipe>/test_recipe.inc file
-    def delete_recipeinc(self, recipe):
-        inc_file = os.path.join(self.testlayer_path, 'recipes-test', recipe, 'test_recipe.inc')
-        self.log.debug("Deleting file: %s" % inc_file)
-        try:
-            os.remove(inc_file)
-        except OSError as e:
-            if e.errno != errno.ENOENT:
-                raise
-
-    # write to <builddir>/conf/bblayers.inc
-    def write_bblayers_config(self, data):
-        self.log.debug("Writing to: %s\n%s\n" % (self.testinc_bblayers_path, data))
-        ftools.write_file(self.testinc_bblayers_path, data)
-
-    # append to <builddir>/conf/bblayers.inc
-    def append_bblayers_config(self, data):
-        self.log.debug("Appending to: %s\n%s\n" % (self.testinc_bblayers_path, data))
-        ftools.append_file(self.testinc_bblayers_path, data)
-
-    # remove data from <builddir>/conf/bblayers.inc
-    def remove_bblayers_config(self, data):
-        self.log.debug("Removing from: %s\n%s\n" % (self.testinc_bblayers_path, data))
-        ftools.remove_from_file(self.testinc_bblayers_path, data)
-
-    # write to <builddir>/conf/machine.inc
-    def set_machine_config(self, data):
-        self.log.debug("Writing to: %s\n%s\n" % (self.machineinc_path, data))
-        ftools.write_file(self.machineinc_path, data)
-
-
-def get_available_machines():
-    # Get a list of all available machines
-    bbpath = get_bb_var('BBPATH').split(':')
-    machines = []
-
-    for path in bbpath:
-        found_machines = glob.glob(os.path.join(path, 'conf', 'machine', '*.conf'))
-        if found_machines:
-            for i in found_machines:
-                # eg: '/home/<user>/poky/meta-intel/conf/machine/intel-core2-32.conf'
-                machines.append(os.path.splitext(os.path.basename(i))[0])
-
-    return machines
-
-
-def get_random_machine():
-    # Get a random machine
-    return choice(get_available_machines())
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/case.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/case.py
new file mode 100644
index 0000000..e09915b
--- /dev/null
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/case.py
@@ -0,0 +1,278 @@
+# Copyright (C) 2013-2017 Intel Corporation
+# Released under the MIT license (see COPYING.MIT)
+
+import sys
+import os
+import shutil
+import glob
+import errno
+from unittest.util import safe_repr
+
+import oeqa.utils.ftools as ftools
+from oeqa.utils.commands import runCmd, bitbake, get_bb_var
+from oeqa.core.case import OETestCase
+
+class OESelftestTestCase(OETestCase):
+    def __init__(self, methodName="runTest"):
+        self._extra_tear_down_commands = []
+        super(OESelftestTestCase, self).__init__(methodName)
+
+    @classmethod
+    def setUpClass(cls):
+        super(OESelftestTestCase, cls).setUpClass()
+
+        cls.testlayer_path = cls.tc.config_paths['testlayer_path']
+        cls.builddir = cls.tc.config_paths['builddir']
+
+        cls.localconf_path = cls.tc.config_paths['localconf']
+        cls.localconf_backup = cls.tc.config_paths['localconf_class_backup']
+        cls.local_bblayers_path = cls.tc.config_paths['bblayers']
+        cls.local_bblayers_backup = cls.tc.config_paths['bblayers_class_backup']
+
+        cls.testinc_path = os.path.join(cls.tc.config_paths['builddir'],
+                "conf/selftest.inc")
+        cls.testinc_bblayers_path = os.path.join(cls.tc.config_paths['builddir'],
+                "conf/bblayers.inc")
+        cls.machineinc_path = os.path.join(cls.tc.config_paths['builddir'],
+                "conf/machine.inc")
+
+        cls._track_for_cleanup = [
+            cls.testinc_path, cls.testinc_bblayers_path,
+            cls.machineinc_path, cls.localconf_backup,
+            cls.local_bblayers_backup]
+
+        cls.add_include()
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.remove_include()
+        cls.remove_inc_files()
+        super(OESelftestTestCase, cls).tearDownClass()
+
+    @classmethod
+    def add_include(cls):
+        if "#include added by oe-selftest" \
+            not in ftools.read_file(os.path.join(cls.builddir, "conf/local.conf")):
+                cls.logger.info("Adding: \"include selftest.inc\" in %s" % os.path.join(cls.builddir, "conf/local.conf"))
+                ftools.append_file(os.path.join(cls.builddir, "conf/local.conf"), \
+                        "\n#include added by oe-selftest\ninclude machine.inc\ninclude selftest.inc")
+
+        if "#include added by oe-selftest" \
+            not in ftools.read_file(os.path.join(cls.builddir, "conf/bblayers.conf")):
+                cls.logger.info("Adding: \"include bblayers.inc\" in bblayers.conf")
+                ftools.append_file(os.path.join(cls.builddir, "conf/bblayers.conf"), \
+                        "\n#include added by oe-selftest\ninclude bblayers.inc")
+
+    @classmethod
+    def remove_include(cls):
+        if "#include added by oe-selftest.py" \
+            in ftools.read_file(os.path.join(cls.builddir, "conf/local.conf")):
+                cls.logger.info("Removing the include from local.conf")
+                ftools.remove_from_file(os.path.join(cls.builddir, "conf/local.conf"), \
+                        "\n#include added by oe-selftest.py\ninclude machine.inc\ninclude selftest.inc")
+
+        if "#include added by oe-selftest.py" \
+            in ftools.read_file(os.path.join(cls.builddir, "conf/bblayers.conf")):
+                cls.logger.info("Removing the include from bblayers.conf")
+                ftools.remove_from_file(os.path.join(cls.builddir, "conf/bblayers.conf"), \
+                        "\n#include added by oe-selftest.py\ninclude bblayers.inc")
+
+    @classmethod
+    def remove_inc_files(cls):
+        try:
+            os.remove(os.path.join(cls.builddir, "conf/selftest.inc"))
+            for root, _, files in os.walk(cls.testlayer_path):
+                for f in files:
+                    if f == 'test_recipe.inc':
+                        os.remove(os.path.join(root, f))
+        except OSError as e:
+            pass
+
+        for incl_file in ['conf/bblayers.inc', 'conf/machine.inc']:
+            try:
+                os.remove(os.path.join(cls.builddir, incl_file))
+            except:
+                pass
+
+    def setUp(self):
+        super(OESelftestTestCase, self).setUp()
+        os.chdir(self.builddir)
+        # Check if local.conf or bblayers.conf files backup exists
+        # from a previous failed test and restore them
+        if os.path.isfile(self.localconf_backup) or os.path.isfile(
+                self.local_bblayers_backup):
+            self.logger.debug("\
+Found a local.conf and/or bblayers.conf backup from a previously aborted test.\
+Restoring these files now, but tests should be re-executed from a clean environment\
+to ensure accurate results.")
+            try:
+                shutil.copyfile(self.localconf_backup, self.localconf_path)
+            except OSError as e:
+                if e.errno != errno.ENOENT:
+                    raise
+            try:
+                shutil.copyfile(self.local_bblayers_backup,
+                                self.local_bblayers_path)
+            except OSError as e:
+                if e.errno != errno.ENOENT:
+                    raise
+        else:
+            # backup local.conf and bblayers.conf
+            shutil.copyfile(self.localconf_path, self.localconf_backup)
+            shutil.copyfile(self.local_bblayers_path, self.local_bblayers_backup)
+            self.logger.debug("Creating local.conf and bblayers.conf backups.")
+        # we don't know what the previous test left around in config or inc files
+        # if it failed so we need a fresh start
+        try:
+            os.remove(self.testinc_path)
+        except OSError as e:
+            if e.errno != errno.ENOENT:
+                raise
+        for root, _, files in os.walk(self.testlayer_path):
+            for f in files:
+                if f == 'test_recipe.inc':
+                    os.remove(os.path.join(root, f))
+
+        for incl_file in [self.testinc_bblayers_path, self.machineinc_path]:
+            try:
+                os.remove(incl_file)
+            except OSError as e:
+                if e.errno != errno.ENOENT:
+                    raise
+
+        if self.tc.custommachine:
+            machine_conf = 'MACHINE ??= "%s"\n' % self.tc.custommachine
+            self.set_machine_config(machine_conf)
+
+        # tests might need their own setup
+        # but if they overwrite this one they have to call
+        # super each time, so let's give them an alternative
+        self.setUpLocal()
+
+    def setUpLocal(self):
+        pass
+
+    def tearDown(self):
+        if self._extra_tear_down_commands:
+            failed_extra_commands = []
+            for command in self._extra_tear_down_commands:
+                result = runCmd(command, ignore_status=True)
+                if not result.status ==  0:
+                    failed_extra_commands.append(command)
+            if failed_extra_commands:
+                self.logger.warning("tearDown commands have failed: %s" % ', '.join(map(str, failed_extra_commands)))
+                self.logger.debug("Trying to move on.")
+            self._extra_tear_down_commands = []
+
+        if self._track_for_cleanup:
+            for path in self._track_for_cleanup:
+                if os.path.isdir(path):
+                    shutil.rmtree(path)
+                if os.path.isfile(path):
+                    os.remove(path)
+            self._track_for_cleanup = []
+
+        self.tearDownLocal()
+        super(OESelftestTestCase, self).tearDown()
+
+    def tearDownLocal(self):
+        pass
+
+    def add_command_to_tearDown(self, command):
+        """Add test specific commands to the tearDown method"""
+        self.logger.debug("Adding command '%s' to tearDown for this test." % command)
+        self._extra_tear_down_commands.append(command)
+
+    def track_for_cleanup(self, path):
+        """Add test specific files or directories to be removed in the tearDown method"""
+        self.logger.debug("Adding path '%s' to be cleaned up when test is over" % path)
+        self._track_for_cleanup.append(path)
+
+    def write_config(self, data):
+        """Write to <builddir>/conf/selftest.inc"""
+
+        self.logger.debug("Writing to: %s\n%s\n" % (self.testinc_path, data))
+        ftools.write_file(self.testinc_path, data)
+
+        if self.tc.custommachine and 'MACHINE' in data:
+            machine = get_bb_var('MACHINE')
+            self.logger.warning('MACHINE overridden: %s' % machine)
+
+    def append_config(self, data):
+        """Append to <builddir>/conf/selftest.inc"""
+        self.logger.debug("Appending to: %s\n%s\n" % (self.testinc_path, data))
+        ftools.append_file(self.testinc_path, data)
+
+        if self.tc.custommachine and 'MACHINE' in data:
+            machine = get_bb_var('MACHINE')
+            self.logger.warning('MACHINE overridden: %s' % machine)
+
+    def remove_config(self, data):
+        """Remove data from <builddir>/conf/selftest.inc"""
+        self.logger.debug("Removing from: %s\n%s\n" % (self.testinc_path, data))
+        ftools.remove_from_file(self.testinc_path, data)
+
+    def recipeinc(self, recipe):
+        """Return absolute path of meta-sefltest/recipes-test/<recipe>/test_recipe.inc"""
+        return os.path.join(self.testlayer_path, 'recipes-test', recipe, 'test_recipe.inc')
+
+    def write_recipeinc(self, recipe, data):
+        """Write to meta-sefltest/recipes-test/<recipe>/test_recipe.inc"""
+        inc_file = self.recipeinc(recipe)
+        self.logger.debug("Writing to: %s\n%s\n" % (inc_file, data))
+        ftools.write_file(inc_file, data)
+        return inc_file
+
+    def append_recipeinc(self, recipe, data):
+        """Append data to meta-sefltest/recipes-test/<recipe>/test_recipe.inc"""
+        inc_file = self.recipeinc(recipe)
+        self.logger.debug("Appending to: %s\n%s\n" % (inc_file, data))
+        ftools.append_file(inc_file, data)
+        return inc_file
+
+    def remove_recipeinc(self, recipe, data):
+        """Remove data from meta-sefltest/recipes-test/<recipe>/test_recipe.inc"""
+        inc_file = self.recipeinc(recipe)
+        self.logger.debug("Removing from: %s\n%s\n" % (inc_file, data))
+        ftools.remove_from_file(inc_file, data)
+
+    def delete_recipeinc(self, recipe):
+        """Delete meta-sefltest/recipes-test/<recipe>/test_recipe.inc file"""
+        inc_file = self.recipeinc(recipe)
+        self.logger.debug("Deleting file: %s" % inc_file)
+        try:
+            os.remove(inc_file)
+        except OSError as e:
+            if e.errno != errno.ENOENT:
+                raise
+    def write_bblayers_config(self, data):
+        """Write to <builddir>/conf/bblayers.inc"""
+        self.logger.debug("Writing to: %s\n%s\n" % (self.testinc_bblayers_path, data))
+        ftools.write_file(self.testinc_bblayers_path, data)
+
+    def append_bblayers_config(self, data):
+        """Append to <builddir>/conf/bblayers.inc"""
+        self.logger.debug("Appending to: %s\n%s\n" % (self.testinc_bblayers_path, data))
+        ftools.append_file(self.testinc_bblayers_path, data)
+
+    def remove_bblayers_config(self, data):
+        """Remove data from <builddir>/conf/bblayers.inc"""
+        self.logger.debug("Removing from: %s\n%s\n" % (self.testinc_bblayers_path, data))
+        ftools.remove_from_file(self.testinc_bblayers_path, data)
+
+    def set_machine_config(self, data):
+        """Write to <builddir>/conf/machine.inc"""
+        self.logger.debug("Writing to: %s\n%s\n" % (self.machineinc_path, data))
+        ftools.write_file(self.machineinc_path, data)
+
+    # check does path exist
+    def assertExists(self, expr, msg=None):
+        if not os.path.exists(expr):
+            msg = self._formatMessage(msg, "%s does not exist" % safe_repr(expr))
+            raise self.failureException(msg)
+
+    # check does path not exist
+    def assertNotExists(self, expr, msg=None):
+        if os.path.exists(expr):
+            msg = self._formatMessage(msg, "%s exists when it should not" % safe_repr(expr))
+            raise self.failureException(msg)
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/_sstatetests_noauto.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/_sstatetests_noauto.py
similarity index 96%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/_sstatetests_noauto.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/_sstatetests_noauto.py
index fc9ae7e..0e58962 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/_sstatetests_noauto.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/_sstatetests_noauto.py
@@ -1,19 +1,16 @@
-import datetime
-import unittest
 import os
-import re
 import shutil
 
 import oeqa.utils.ftools as ftools
-from oeqa.selftest.base import oeSelfTest
 from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_test_layer
-from oeqa.selftest.sstate import SStateBase
+from oeqa.selftest.cases.sstate import SStateBase
 
 
 class RebuildFromSState(SStateBase):
 
     @classmethod
     def setUpClass(self):
+        super(RebuildFromSState, self).setUpClass()
         self.builddir = os.path.join(os.environ.get('BUILDDIR'))
 
     def get_dep_targets(self, primary_targets):
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/archiver.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/archiver.py
similarity index 96%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/archiver.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/archiver.py
index 7f01c36..f61a522 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/archiver.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/archiver.py
@@ -1,14 +1,12 @@
-from oeqa.selftest.base import oeSelfTest
-from oeqa.utils.commands import bitbake, get_bb_vars
-from oeqa.utils.decorators import testcase
-import glob
 import os
-import shutil
+import glob
+from oeqa.utils.commands import bitbake, get_bb_vars
+from oeqa.selftest.case import OESelftestTestCase
+from oeqa.core.decorator.oeid import OETestID
 
+class Archiver(OESelftestTestCase):
 
-class Archiver(oeSelfTest):
-
-    @testcase(1345)
+    @OETestID(1345)
     def test_archiver_allows_to_filter_on_recipe_name(self):
         """
         Summary:     The archiver should offer the possibility to filter on the recipe. (#6929)
@@ -42,7 +40,7 @@
         excluded_present = len(glob.glob(src_path + '/%s-*' % exclude_recipe))
         self.assertFalse(excluded_present, 'Recipe %s was not excluded.' % exclude_recipe)
 
-
+    @OETestID(1900)
     def test_archiver_filters_by_type(self):
         """
         Summary:     The archiver is documented to filter on the recipe type.
@@ -75,6 +73,7 @@
         excluded_present = len(glob.glob(src_path_native + '/%s-*' % native_recipe))
         self.assertFalse(excluded_present, 'Recipe %s was not excluded.' % native_recipe)
 
+    @OETestID(1901)
     def test_archiver_filters_by_type_and_name(self):
         """
         Summary:     Test that the archiver archives by recipe type, taking the
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/bblayers.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/bblayers.py
similarity index 94%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/bblayers.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/bblayers.py
index cd658c5..90a2249 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/bblayers.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/bblayers.py
@@ -1,39 +1,37 @@
-import unittest
 import os
-import logging
 import re
-import shutil
 
 import oeqa.utils.ftools as ftools
-from oeqa.selftest.base import oeSelfTest
 from oeqa.utils.commands import runCmd, get_bb_var
-from oeqa.utils.decorators import testcase
 
-class BitbakeLayers(oeSelfTest):
+from oeqa.selftest.case import OESelftestTestCase
+from oeqa.core.decorator.oeid import OETestID
 
-    @testcase(756)
+class BitbakeLayers(OESelftestTestCase):
+
+    @OETestID(756)
     def test_bitbakelayers_showcrossdepends(self):
         result = runCmd('bitbake-layers show-cross-depends')
         self.assertTrue('aspell' in result.output, msg = "No dependencies were shown. bitbake-layers show-cross-depends output: %s" % result.output)
 
-    @testcase(83)
+    @OETestID(83)
     def test_bitbakelayers_showlayers(self):
         result = runCmd('bitbake-layers show-layers')
         self.assertTrue('meta-selftest' in result.output, msg = "No layers were shown. bitbake-layers show-layers output: %s" % result.output)
 
-    @testcase(93)
+    @OETestID(93)
     def test_bitbakelayers_showappends(self):
         recipe = "xcursor-transparent-theme"
         bb_file = self.get_recipe_basename(recipe)
         result = runCmd('bitbake-layers show-appends')
         self.assertTrue(bb_file in result.output, msg="%s file was not recognised. bitbake-layers show-appends output: %s" % (bb_file, result.output))
 
-    @testcase(90)
+    @OETestID(90)
     def test_bitbakelayers_showoverlayed(self):
         result = runCmd('bitbake-layers show-overlayed')
         self.assertTrue('aspell' in result.output, msg="aspell overlayed recipe was not recognised bitbake-layers show-overlayed %s" % result.output)
 
-    @testcase(95)
+    @OETestID(95)
     def test_bitbakelayers_flatten(self):
         recipe = "xcursor-transparent-theme"
         recipe_path = "recipes-graphics/xcursor-transparent-theme"
@@ -48,7 +46,7 @@
         find_in_contents = re.search("##### bbappended from meta-selftest #####\n(.*\n)*include test_recipe.inc", contents)
         self.assertTrue(find_in_contents, msg = "Flattening layers did not work. bitbake-layers flatten output: %s" % result.output)
 
-    @testcase(1195)
+    @OETestID(1195)
     def test_bitbakelayers_add_remove(self):
         test_layer = os.path.join(get_bb_var('COREBASE'), 'meta-skeleton')
         result = runCmd('bitbake-layers show-layers')
@@ -66,7 +64,7 @@
         result = runCmd('bitbake-layers show-layers')
         self.assertNotIn('meta-skeleton', result.output, msg = "meta-skeleton should have been removed at this step.  bitbake-layers show-layers output: %s" % result.output)
 
-    @testcase(1384)
+    @OETestID(1384)
     def test_bitbakelayers_showrecipes(self):
         result = runCmd('bitbake-layers show-recipes')
         self.assertIn('aspell:', result.output)
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/bbtests.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/bbtests.py
similarity index 95%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/bbtests.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/bbtests.py
index 46e09f5..4c82049 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/bbtests.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/bbtests.py
@@ -2,30 +2,31 @@
 import re
 
 import oeqa.utils.ftools as ftools
-from oeqa.selftest.base import oeSelfTest
 from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars
-from oeqa.utils.decorators import testcase
 
-class BitbakeTests(oeSelfTest):
+from oeqa.selftest.case import OESelftestTestCase
+from oeqa.core.decorator.oeid import OETestID
+
+class BitbakeTests(OESelftestTestCase):
 
     def getline(self, res, line):
         for l in res.output.split('\n'):
             if line in l:
                 return l
 
-    @testcase(789)
+    @OETestID(789)
     def test_run_bitbake_from_dir_1(self):
         os.chdir(os.path.join(self.builddir, 'conf'))
         self.assertEqual(bitbake('-e').status, 0, msg = "bitbake couldn't run from \"conf\" dir")
 
-    @testcase(790)
+    @OETestID(790)
     def test_run_bitbake_from_dir_2(self):
         my_env = os.environ.copy()
         my_env['BBPATH'] = my_env['BUILDDIR']
         os.chdir(os.path.dirname(os.environ['BUILDDIR']))
         self.assertEqual(bitbake('-e', env=my_env).status, 0, msg = "bitbake couldn't run from builddir")
 
-    @testcase(806)
+    @OETestID(806)
     def test_event_handler(self):
         self.write_config("INHERIT += \"test_events\"")
         result = bitbake('m4-native')
@@ -35,7 +36,7 @@
         self.assertTrue(find_build_completed, msg = "Match failed in:\n%s" % result.output)
         self.assertFalse('Test for bb.event.InvalidEvent' in result.output, msg = "\"Test for bb.event.InvalidEvent\" message found during bitbake process. bitbake output: %s" % result.output)
 
-    @testcase(103)
+    @OETestID(103)
     def test_local_sstate(self):
         bitbake('m4-native')
         bitbake('m4-native -cclean')
@@ -43,17 +44,17 @@
         find_setscene = re.search("m4-native.*do_.*_setscene", result.output)
         self.assertTrue(find_setscene, msg = "No \"m4-native.*do_.*_setscene\" message found during bitbake m4-native. bitbake output: %s" % result.output )
 
-    @testcase(105)
+    @OETestID(105)
     def test_bitbake_invalid_recipe(self):
         result = bitbake('-b asdf', ignore_status=True)
         self.assertTrue("ERROR: Unable to find any recipe file matching 'asdf'" in result.output, msg = "Though asdf recipe doesn't exist, bitbake didn't output any err. message. bitbake output: %s" % result.output)
 
-    @testcase(107)
+    @OETestID(107)
     def test_bitbake_invalid_target(self):
         result = bitbake('asdf', ignore_status=True)
         self.assertTrue("ERROR: Nothing PROVIDES 'asdf'" in result.output, msg = "Though no 'asdf' target exists, bitbake didn't output any err. message. bitbake output: %s" % result.output)
 
-    @testcase(106)
+    @OETestID(106)
     def test_warnings_errors(self):
         result = bitbake('-b asdf', ignore_status=True)
         find_warnings = re.search("Summary: There w.{2,3}? [1-9][0-9]* WARNING messages* shown", result.output)
@@ -61,7 +62,7 @@
         self.assertTrue(find_warnings, msg="Did not find the mumber of warnings at the end of the build:\n" + result.output)
         self.assertTrue(find_errors, msg="Did not find the mumber of errors at the end of the build:\n" + result.output)
 
-    @testcase(108)
+    @OETestID(108)
     def test_invalid_patch(self):
         # This patch already exists in SRC_URI so adding it again will cause the
         # patch to fail.
@@ -73,7 +74,7 @@
         line = self.getline(result, "Function failed: patch_do_patch")
         self.assertTrue(line and line.startswith("ERROR:"), msg = "Repeated patch application didn't fail. bitbake output: %s" % result.output)
 
-    @testcase(1354)
+    @OETestID(1354)
     def test_force_task_1(self):
         # test 1 from bug 5875
         test_recipe = 'zlib'
@@ -98,7 +99,7 @@
         ret = bitbake(test_recipe)
         self.assertIn('task do_package_write_rpm:', ret.output, 'Task do_package_write_rpm did not re-executed.')
 
-    @testcase(163)
+    @OETestID(163)
     def test_force_task_2(self):
         # test 2 from bug 5875
         test_recipe = 'zlib'
@@ -111,7 +112,7 @@
         for task in look_for_tasks:
             self.assertIn(task, result.output, msg="Couldn't find %s task.")
 
-    @testcase(167)
+    @OETestID(167)
     def test_bitbake_g(self):
         result = bitbake('-g core-image-minimal')
         for f in ['pn-buildlist', 'recipe-depends.dot', 'task-depends.dot']:
@@ -119,7 +120,7 @@
         self.assertTrue('Task dependencies saved to \'task-depends.dot\'' in result.output, msg = "No task dependency \"task-depends.dot\" file was generated for the given task target. bitbake output: %s" % result.output)
         self.assertTrue('busybox' in ftools.read_file(os.path.join(self.builddir, 'task-depends.dot')), msg = "No \"busybox\" dependency found in task-depends.dot file.")
 
-    @testcase(899)
+    @OETestID(899)
     def test_image_manifest(self):
         bitbake('core-image-minimal')
         bb_vars = get_bb_vars(["DEPLOY_DIR_IMAGE", "IMAGE_LINK_NAME"], "core-image-minimal")
@@ -128,7 +129,7 @@
         manifest = os.path.join(deploydir, imagename + ".manifest")
         self.assertTrue(os.path.islink(manifest), msg="No manifest file created for image. It should have been created in %s" % manifest)
 
-    @testcase(168)
+    @OETestID(168)
     def test_invalid_recipe_src_uri(self):
         data = 'SRC_URI = "file://invalid"'
         self.write_recipeinc('man', data)
@@ -149,7 +150,7 @@
         self.assertTrue(line and line.startswith("ERROR:"), msg = "\"invalid\" file \
 doesn't exist, yet fetcher didn't report any error. bitbake output: %s" % result.output)
 
-    @testcase(171)
+    @OETestID(171)
     def test_rename_downloaded_file(self):
         # TODO unique dldir instead of using cleanall
         # TODO: need to set sstatedir?
@@ -167,29 +168,29 @@
         self.assertTrue(os.path.isfile(os.path.join(dl_dir, 'test-aspell.tar.gz')), msg = "File rename failed. No corresponding test-aspell.tar.gz file found under %s" % dl_dir)
         self.assertTrue(os.path.isfile(os.path.join(dl_dir, 'test-aspell.tar.gz.done')), "File rename failed. No corresponding test-aspell.tar.gz.done file found under %s" % dl_dir)
 
-    @testcase(1028)
+    @OETestID(1028)
     def test_environment(self):
         self.write_config("TEST_ENV=\"localconf\"")
         result = runCmd('bitbake -e | grep TEST_ENV=')
         self.assertTrue('localconf' in result.output, msg = "bitbake didn't report any value for TEST_ENV variable. To test, run 'bitbake -e | grep TEST_ENV='")
 
-    @testcase(1029)
+    @OETestID(1029)
     def test_dry_run(self):
         result = runCmd('bitbake -n m4-native')
         self.assertEqual(0, result.status, "bitbake dry run didn't run as expected. %s" % result.output)
 
-    @testcase(1030)
+    @OETestID(1030)
     def test_just_parse(self):
         result = runCmd('bitbake -p')
         self.assertEqual(0, result.status, "errors encountered when parsing recipes. %s" % result.output)
 
-    @testcase(1031)
+    @OETestID(1031)
     def test_version(self):
         result = runCmd('bitbake -s | grep wget')
         find = re.search("wget *:([0-9a-zA-Z\.\-]+)", result.output)
         self.assertTrue(find, "No version returned for searched recipe. bitbake output: %s" % result.output)
 
-    @testcase(1032)
+    @OETestID(1032)
     def test_prefile(self):
         preconf = os.path.join(self.builddir, 'conf/prefile.conf')
         self.track_for_cleanup(preconf)
@@ -200,7 +201,7 @@
         result = runCmd('bitbake -r conf/prefile.conf -e | grep TEST_PREFILE=')
         self.assertTrue('localconf' in result.output, "Preconfigure file \"prefile.conf\"was not taken into consideration.")
 
-    @testcase(1033)
+    @OETestID(1033)
     def test_postfile(self):
         postconf = os.path.join(self.builddir, 'conf/postfile.conf')
         self.track_for_cleanup(postconf)
@@ -209,12 +210,12 @@
         result = runCmd('bitbake -R conf/postfile.conf -e | grep TEST_POSTFILE=')
         self.assertTrue('postfile' in result.output, "Postconfigure file \"postfile.conf\"was not taken into consideration.")
 
-    @testcase(1034)
+    @OETestID(1034)
     def test_checkuri(self):
         result = runCmd('bitbake -c checkuri m4')
         self.assertEqual(0, result.status, msg = "\"checkuri\" task was not executed. bitbake output: %s" % result.output)
 
-    @testcase(1035)
+    @OETestID(1035)
     def test_continue(self):
         self.write_config("""DL_DIR = \"${TOPDIR}/download-selftest\"
 SSTATE_DIR = \"${TOPDIR}/download-selftest\"
@@ -229,7 +230,7 @@
         continuepos = result.output.find('NOTE: recipe xcursor-transparent-theme-%s: task do_unpack: Started' % manver.group(1))
         self.assertLess(errorpos,continuepos, msg = "bitbake didn't pass do_fail_task. bitbake output: %s" % result.output)
 
-    @testcase(1119)
+    @OETestID(1119)
     def test_non_gplv3(self):
         self.write_config('INCOMPATIBLE_LICENSE = "GPLv3"')
         result = bitbake('selftest-ed', ignore_status=True)
@@ -238,7 +239,7 @@
         self.assertFalse(os.path.isfile(os.path.join(lic_dir, 'selftest-ed/generic_GPLv3')))
         self.assertTrue(os.path.isfile(os.path.join(lic_dir, 'selftest-ed/generic_GPLv2')))
 
-    @testcase(1422)
+    @OETestID(1422)
     def test_setscene_only(self):
         """ Bitbake option to restore from sstate only within a build (i.e. execute no real tasks, only setscene)"""
         test_recipe = 'ed'
@@ -253,7 +254,7 @@
             self.assertIn('_setscene', task, 'A task different from _setscene ran: %s.\n'
                                              'Executed tasks were: %s' % (task, str(tasks)))
 
-    @testcase(1425)
+    @OETestID(1425)
     def test_bbappend_order(self):
         """ Bitbake should bbappend to recipe in a predictable order """
         test_recipe = 'ed'
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/buildhistory.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/buildhistory.py
similarity index 94%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/buildhistory.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/buildhistory.py
index 008c39c..06792d9 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/buildhistory.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/buildhistory.py
@@ -2,12 +2,11 @@
 import re
 import datetime
 
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import bitbake, get_bb_vars
-from oeqa.utils.decorators import testcase
 
 
-class BuildhistoryBase(oeSelfTest):
+class BuildhistoryBase(OESelftestTestCase):
 
     def config_buildhistory(self, tmp_bh_location=False):
         bb_vars = get_bb_vars(['USER_CLASSES', 'INHERIT'])
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/buildoptions.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/buildoptions.py
similarity index 84%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/buildoptions.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/buildoptions.py
index a6e0203..cf221c3 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/buildoptions.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/buildoptions.py
@@ -3,15 +3,15 @@
 import glob as g
 import shutil
 import tempfile
-from oeqa.selftest.base import oeSelfTest
-from oeqa.selftest.buildhistory import BuildhistoryBase
+from oeqa.selftest.case import OESelftestTestCase
+from oeqa.selftest.cases.buildhistory import BuildhistoryBase
 from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars
 import oeqa.utils.ftools as ftools
-from oeqa.utils.decorators import testcase
+from oeqa.core.decorator.oeid import OETestID
 
-class ImageOptionsTests(oeSelfTest):
+class ImageOptionsTests(OESelftestTestCase):
 
-    @testcase(761)
+    @OETestID(761)
     def test_incremental_image_generation(self):
         image_pkgtype = get_bb_var("IMAGE_PKGTYPE")
         if image_pkgtype != 'rpm':
@@ -22,15 +22,15 @@
         bitbake("core-image-minimal")
         log_data_file = os.path.join(get_bb_var("WORKDIR", "core-image-minimal"), "temp/log.do_rootfs")
         log_data_created = ftools.read_file(log_data_file)
-        incremental_created = re.search("Installing  : packagegroup-core-ssh-openssh", log_data_created)
+        incremental_created = re.search(r"Installing\s*:\s*packagegroup-core-ssh-openssh", log_data_created)
         self.remove_config('IMAGE_FEATURES += "ssh-server-openssh"')
         self.assertTrue(incremental_created, msg = "Match failed in:\n%s" % log_data_created)
         bitbake("core-image-minimal")
         log_data_removed = ftools.read_file(log_data_file)
-        incremental_removed = re.search("Erasing     : packagegroup-core-ssh-openssh", log_data_removed)
+        incremental_removed = re.search(r"Erasing\s*:\s*packagegroup-core-ssh-openssh", log_data_removed)
         self.assertTrue(incremental_removed, msg = "Match failed in:\n%s" % log_data_removed)
 
-    @testcase(286)
+    @OETestID(286)
     def test_ccache_tool(self):
         bitbake("ccache-native")
         bb_vars = get_bb_vars(['SYSROOT_DESTDIR', 'bindir'], 'ccache-native')
@@ -43,7 +43,7 @@
         res = runCmd("grep ccache %s" % log_compile, ignore_status=True)
         self.assertEqual(0, res.status, msg="No match for ccache in m4 log.do_compile. For further details: %s" % log_compile)
 
-    @testcase(1435)
+    @OETestID(1435)
     def test_read_only_image(self):
         distro_features = get_bb_var('DISTRO_FEATURES')
         if not ('x11' in distro_features and 'opengl' in distro_features):
@@ -52,9 +52,9 @@
         bitbake("core-image-sato")
         # do_image will fail if there are any pending postinsts
 
-class DiskMonTest(oeSelfTest):
+class DiskMonTest(OESelftestTestCase):
 
-    @testcase(277)
+    @OETestID(277)
     def test_stoptask_behavior(self):
         self.write_config('BB_DISKMON_DIRS = "STOPTASKS,${TMPDIR},100000G,100K"')
         res = bitbake("m4", ignore_status = True)
@@ -68,13 +68,13 @@
         res = bitbake("m4")
         self.assertTrue('WARNING: The free space' in res.output, msg = "A warning should have been displayed for disk monitor is set to WARN: %s" %res.output)
 
-class SanityOptionsTest(oeSelfTest):
+class SanityOptionsTest(OESelftestTestCase):
     def getline(self, res, line):
         for l in res.output.split('\n'):
             if line in l:
                 return l
 
-    @testcase(927)
+    @OETestID(927)
     def test_options_warnqa_errorqa_switch(self):
 
         self.write_config("INHERIT_remove = \"report-error\"")
@@ -96,25 +96,7 @@
         line = self.getline(res, "QA Issue: xcursor-transparent-theme-dbg is listed in PACKAGES multiple times, this leads to packaging errors.")
         self.assertTrue(line and line.startswith("WARNING:"), msg=res.output)
 
-    @testcase(278)
-    def test_sanity_unsafe_script_references(self):
-        self.write_config('WARN_QA_append = " unsafe-references-in-scripts"')
-
-        self.add_command_to_tearDown('bitbake -c clean gzip')
-        res = bitbake("gzip -f -c package_qa")
-        line = self.getline(res, "QA Issue: gzip")
-        self.assertFalse(line, "WARNING: QA Issue: gzip message is present in bitbake's output and shouldn't be: %s" % res.output)
-
-        self.append_config("""
-do_install_append_pn-gzip () {
-	echo "\n${bindir}/test" >> ${D}${bindir}/zcat
-}
-""")
-        res = bitbake("gzip -f -c package_qa")
-        line = self.getline(res, "QA Issue: gzip")
-        self.assertTrue(line and line.startswith("WARNING:"), "WARNING: QA Issue: gzip message is not present in bitbake's output: %s" % res.output)
-
-    @testcase(1421)
+    @OETestID(1421)
     def test_layer_without_git_dir(self):
         """
         Summary:     Test that layer git revisions are displayed and do not fail without git repository
@@ -156,20 +138,20 @@
 
 class BuildhistoryTests(BuildhistoryBase):
 
-    @testcase(293)
+    @OETestID(293)
     def test_buildhistory_basic(self):
         self.run_buildhistory_operation('xcursor-transparent-theme')
         self.assertTrue(os.path.isdir(get_bb_var('BUILDHISTORY_DIR')), "buildhistory dir was not created.")
 
-    @testcase(294)
+    @OETestID(294)
     def test_buildhistory_buildtime_pr_backwards(self):
         target = 'xcursor-transparent-theme'
         error = "ERROR:.*QA Issue: Package version for package %s went backwards which would break package feeds from (.*-r1.* to .*-r0.*)" % target
         self.run_buildhistory_operation(target, target_config="PR = \"r1\"", change_bh_location=True)
         self.run_buildhistory_operation(target, target_config="PR = \"r0\"", change_bh_location=False, expect_error=True, error_regex=error)
 
-class ArchiverTest(oeSelfTest):
-    @testcase(926)
+class ArchiverTest(OESelftestTestCase):
+    @OETestID(926)
     def test_arch_work_dir_and_export_source(self):
         """
         Test for archiving the work directory and exporting the source files.
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/containerimage.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/containerimage.py
similarity index 95%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/containerimage.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/containerimage.py
index def481f..99a5cc9 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/containerimage.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/containerimage.py
@@ -1,7 +1,8 @@
 import os
 
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import bitbake, get_bb_vars, runCmd
+from oeqa.core.decorator.oeid import OETestID
 
 # This test builds an image with using the "container" IMAGE_FSTYPE, and
 # ensures that then files in the image are only the ones expected.
@@ -16,10 +17,11 @@
 # of them, but this test is more to catch if other packages get added by
 # default other than what is in ROOTFS_BOOTSTRAP_INSTALL.
 #
-class ContainerImageTests(oeSelfTest):
+class ContainerImageTests(OESelftestTestCase):
 
     # Verify that when specifying a IMAGE_TYPEDEP_ of the form "foo.bar" that
     # the conversion type bar gets added as a dep as well
+    @OETestID(1619)
     def test_expected_files(self):
 
         def get_each_path_part(path):
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/devtool.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/devtool.py
similarity index 89%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/devtool.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/devtool.py
index 5704866..43280cd 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/devtool.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/devtool.py
@@ -1,6 +1,4 @@
-import unittest
 import os
-import logging
 import re
 import shutil
 import tempfile
@@ -8,12 +6,14 @@
 import fnmatch
 
 import oeqa.utils.ftools as ftools
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import runCmd, bitbake, get_bb_var, create_temp_layer
 from oeqa.utils.commands import get_bb_vars, runqemu, get_test_layer
-from oeqa.utils.decorators import testcase
+from oeqa.core.decorator.oeid import OETestID
 
-class DevtoolBase(oeSelfTest):
+class DevtoolBase(OESelftestTestCase):
+
+    buffer = True
 
     def _test_recipe_contents(self, recipefile, checkvars, checkinherits):
         with open(recipefile, 'r') as f:
@@ -120,6 +120,7 @@
 
     @classmethod
     def setUpClass(cls):
+        super(DevtoolTests, cls).setUpClass()
         bb_vars = get_bb_vars(['TOPDIR', 'SSTATE_DIR'])
         cls.original_sstate = bb_vars['SSTATE_DIR']
         cls.devtool_sstate = os.path.join(bb_vars['TOPDIR'], 'sstate_devtool')
@@ -129,8 +130,9 @@
 
     @classmethod
     def tearDownClass(cls):
-        cls.log.debug('Deleting devtool sstate cache on %s' % cls.devtool_sstate)
+        cls.logger.debug('Deleting devtool sstate cache on %s' % cls.devtool_sstate)
         runCmd('rm -rf %s' % cls.devtool_sstate)
+        super(DevtoolTests, cls).tearDownClass()
 
     def setUp(self):
         """Test case setup function"""
@@ -168,7 +170,7 @@
         if expected_status:
             self.fail('Missing file changes: %s' % expected_status)
 
-    @testcase(1158)
+    @OETestID(1158)
     def test_create_workspace(self):
         # Check preconditions
         result = runCmd('bitbake-layers show-layers')
@@ -189,31 +191,40 @@
         self.assertNotIn(tempdir, result.output)
         self.assertIn(self.workspacedir, result.output)
 
-    @testcase(1159)
+    @OETestID(1159)
     def test_devtool_add(self):
         # Fetch source
         tempdir = tempfile.mkdtemp(prefix='devtoolqa')
         self.track_for_cleanup(tempdir)
+        pn = 'pv'
+        pv = '1.5.3'
         url = 'http://www.ivarch.com/programs/sources/pv-1.5.3.tar.bz2'
         result = runCmd('wget %s' % url, cwd=tempdir)
-        result = runCmd('tar xfv pv-1.5.3.tar.bz2', cwd=tempdir)
-        srcdir = os.path.join(tempdir, 'pv-1.5.3')
+        result = runCmd('tar xfv %s' % os.path.basename(url), cwd=tempdir)
+        srcdir = os.path.join(tempdir, '%s-%s' % (pn, pv))
         self.assertTrue(os.path.isfile(os.path.join(srcdir, 'configure')), 'Unable to find configure script in source directory')
         # Test devtool add
         self.track_for_cleanup(self.workspacedir)
-        self.add_command_to_tearDown('bitbake -c cleansstate pv')
+        self.add_command_to_tearDown('bitbake -c cleansstate %s' % pn)
         self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
-        result = runCmd('devtool add pv %s' % srcdir)
-        self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created')
+        result = runCmd('devtool add %s %s' % (pn, srcdir))
+        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created')
         # Test devtool status
         result = runCmd('devtool status')
-        self.assertIn('pv', result.output)
+        recipepath = '%s/recipes/%s/%s_%s.bb' % (self.workspacedir, pn, pn, pv)
+        self.assertIn(recipepath, result.output)
         self.assertIn(srcdir, result.output)
+        # Test devtool find-recipe
+        result = runCmd('devtool -q find-recipe %s' % pn)
+        self.assertEqual(recipepath, result.output.strip())
+        # Test devtool edit-recipe
+        result = runCmd('VISUAL="echo 123" devtool -q edit-recipe %s' % pn)
+        self.assertEqual('123 %s' % recipepath, result.output.strip())
         # Clean up anything in the workdir/sysroot/sstate cache (have to do this *after* devtool add since the recipe only exists then)
-        bitbake('pv -c cleansstate')
+        bitbake('%s -c cleansstate' % pn)
         # Test devtool build
-        result = runCmd('devtool build pv')
-        bb_vars = get_bb_vars(['D', 'bindir'], 'pv')
+        result = runCmd('devtool build %s' % pn)
+        bb_vars = get_bb_vars(['D', 'bindir'], pn)
         installdir = bb_vars['D']
         self.assertTrue(installdir, 'Could not query installdir variable')
         bindir = bb_vars['bindir']
@@ -222,7 +233,7 @@
             bindir = bindir[1:]
         self.assertTrue(os.path.isfile(os.path.join(installdir, bindir, 'pv')), 'pv binary not found in D')
 
-    @testcase(1423)
+    @OETestID(1423)
     def test_devtool_add_git_local(self):
         # Fetch source from a remote URL, but do it outside of devtool
         tempdir = tempfile.mkdtemp(prefix='devtoolqa')
@@ -242,7 +253,7 @@
         self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
         # Don't specify a name since we should be able to auto-detect it
         result = runCmd('devtool add %s' % srcdir)
-        self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created')
+        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created')
         # Check the recipe name is correct
         recipefile = get_bb_var('FILE', pn)
         self.assertIn('%s_git.bb' % pn, recipefile, 'Recipe file incorrectly named')
@@ -262,7 +273,7 @@
         checkvars['DEPENDS'] = set(['dbus'])
         self._test_recipe_contents(recipefile, checkvars, [])
 
-    @testcase(1162)
+    @OETestID(1162)
     def test_devtool_add_library(self):
         # Fetch source
         tempdir = tempfile.mkdtemp(prefix='devtoolqa')
@@ -277,7 +288,7 @@
         self.track_for_cleanup(self.workspacedir)
         self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
         result = runCmd('devtool add libftdi %s -V %s' % (srcdir, version))
-        self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created')
+        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created')
         # Test devtool status
         result = runCmd('devtool status')
         self.assertIn('libftdi', result.output)
@@ -311,7 +322,7 @@
         self.assertFalse(matches, 'Stamp files exist for recipe libftdi that should have been cleaned')
         self.assertFalse(os.path.isfile(os.path.join(staging_libdir, 'libftdi1.so.2.1.0')), 'libftdi binary still found in STAGING_LIBDIR after cleaning')
 
-    @testcase(1160)
+    @OETestID(1160)
     def test_devtool_add_fetch(self):
         # Fetch source
         tempdir = tempfile.mkdtemp(prefix='devtoolqa')
@@ -325,7 +336,7 @@
         self.add_command_to_tearDown('bitbake -c cleansstate %s' % testrecipe)
         self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
         result = runCmd('devtool add %s %s -f %s' % (testrecipe, srcdir, url))
-        self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created. %s' % result.output)
+        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created. %s' % result.output)
         self.assertTrue(os.path.isfile(os.path.join(srcdir, 'setup.py')), 'Unable to find setup.py in source directory')
         self.assertTrue(os.path.isdir(os.path.join(srcdir, '.git')), 'git repository for external source tree was not created')
         # Test devtool status
@@ -357,7 +368,7 @@
         checkvars['SRC_URI'] = url
         self._test_recipe_contents(recipefile, checkvars, [])
 
-    @testcase(1161)
+    @OETestID(1161)
     def test_devtool_add_fetch_git(self):
         tempdir = tempfile.mkdtemp(prefix='devtoolqa')
         self.track_for_cleanup(tempdir)
@@ -370,7 +381,7 @@
         self.add_command_to_tearDown('bitbake -c cleansstate %s' % testrecipe)
         self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
         result = runCmd('devtool add %s %s -a -f %s' % (testrecipe, srcdir, url))
-        self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created: %s' % result.output)
+        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created: %s' % result.output)
         self.assertTrue(os.path.isfile(os.path.join(srcdir, 'imraa', 'imraa.c')), 'Unable to find imraa/imraa.c in source directory')
         # Test devtool status
         result = runCmd('devtool status')
@@ -405,7 +416,7 @@
         checkvars['SRCREV'] = checkrev
         self._test_recipe_contents(recipefile, checkvars, [])
 
-    @testcase(1391)
+    @OETestID(1391)
     def test_devtool_add_fetch_simple(self):
         # Fetch source from a remote URL, auto-detecting name
         tempdir = tempfile.mkdtemp(prefix='devtoolqa')
@@ -418,7 +429,7 @@
         self.track_for_cleanup(self.workspacedir)
         self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
         result = runCmd('devtool add %s' % url)
-        self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created. %s' % result.output)
+        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created. %s' % result.output)
         self.assertTrue(os.path.isfile(os.path.join(srcdir, 'configure')), 'Unable to find configure script in source directory')
         self.assertTrue(os.path.isdir(os.path.join(srcdir, '.git')), 'git repository for external source tree was not created')
         # Test devtool status
@@ -433,7 +444,7 @@
         checkvars['SRC_URI'] = url.replace(testver, '${PV}')
         self._test_recipe_contents(recipefile, checkvars, [])
 
-    @testcase(1164)
+    @OETestID(1164)
     def test_devtool_modify(self):
         import oe.path
 
@@ -443,8 +454,8 @@
         self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
         self.add_command_to_tearDown('bitbake -c clean mdadm')
         result = runCmd('devtool modify mdadm -x %s' % tempdir)
-        self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile')), 'Extracted source could not be found')
-        self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created')
+        self.assertExists(os.path.join(tempdir, 'Makefile'), 'Extracted source could not be found')
+        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created')
         matches = glob.glob(os.path.join(self.workspacedir, 'appends', 'mdadm_*.bbappend'))
         self.assertTrue(matches, 'bbappend not created %s' % result.output)
 
@@ -491,13 +502,14 @@
         result = runCmd('devtool status')
         self.assertNotIn('mdadm', result.output)
 
+    @OETestID(1620)
     def test_devtool_buildclean(self):
         def assertFile(path, *paths):
             f = os.path.join(path, *paths)
-            self.assertTrue(os.path.exists(f), "%r does not exist" % f)
+            self.assertExists(f)
         def assertNoFile(path, *paths):
             f = os.path.join(path, *paths)
-            self.assertFalse(os.path.exists(os.path.join(f)), "%r exists" % f)
+            self.assertNotExists(f)
 
         # Clean up anything in the workdir/sysroot/sstate cache
         bitbake('mdadm m4 -c cleansstate')
@@ -537,7 +549,7 @@
         finally:
             self.delete_recipeinc('m4')
 
-    @testcase(1166)
+    @OETestID(1166)
     def test_devtool_modify_invalid(self):
         # Try modifying some recipes
         tempdir = tempfile.mkdtemp(prefix='devtoolqa')
@@ -566,7 +578,7 @@
             self.assertNotEqual(result.status, 0, 'devtool modify on %s should have failed. devtool output: %s' %  (testrecipe, result.output))
             self.assertIn('ERROR: ', result.output, 'devtool modify on %s should have given an ERROR' % testrecipe)
 
-    @testcase(1365)
+    @OETestID(1365)
     def test_devtool_modify_native(self):
         # Check preconditions
         self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
@@ -596,7 +608,7 @@
         self.assertTrue(inheritnative, 'None of these recipes do "inherit native" - need to adjust testrecipes list: %s' % ', '.join(testrecipes))
 
 
-    @testcase(1165)
+    @OETestID(1165)
     def test_devtool_modify_git(self):
         # Check preconditions
         testrecipe = 'mkelfimage'
@@ -611,8 +623,8 @@
         self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
         self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe)
         result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir))
-        self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile')), 'Extracted source could not be found')
-        self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created. devtool output: %s' % result.output)
+        self.assertExists(os.path.join(tempdir, 'Makefile'), 'Extracted source could not be found')
+        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created. devtool output: %s' % result.output)
         matches = glob.glob(os.path.join(self.workspacedir, 'appends', 'mkelfimage_*.bbappend'))
         self.assertTrue(matches, 'bbappend not created')
         # Test devtool status
@@ -624,7 +636,7 @@
         # Try building
         bitbake(testrecipe)
 
-    @testcase(1167)
+    @OETestID(1167)
     def test_devtool_modify_localfiles(self):
         # Check preconditions
         testrecipe = 'lighttpd'
@@ -644,8 +656,8 @@
         self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
         self.add_command_to_tearDown('bitbake -c clean %s' % testrecipe)
         result = runCmd('devtool modify %s -x %s' % (testrecipe, tempdir))
-        self.assertTrue(os.path.exists(os.path.join(tempdir, 'configure.ac')), 'Extracted source could not be found')
-        self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created')
+        self.assertExists(os.path.join(tempdir, 'configure.ac'), 'Extracted source could not be found')
+        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created')
         matches = glob.glob(os.path.join(self.workspacedir, 'appends', '%s_*.bbappend' % testrecipe))
         self.assertTrue(matches, 'bbappend not created')
         # Test devtool status
@@ -655,7 +667,7 @@
         # Try building
         bitbake(testrecipe)
 
-    @testcase(1378)
+    @OETestID(1378)
     def test_devtool_modify_virtual(self):
         # Try modifying a virtual recipe
         virtrecipe = 'virtual/make'
@@ -665,8 +677,8 @@
         self.track_for_cleanup(self.workspacedir)
         self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
         result = runCmd('devtool modify %s -x %s' % (virtrecipe, tempdir))
-        self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile.am')), 'Extracted source could not be found')
-        self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'conf', 'layer.conf')), 'Workspace directory not created')
+        self.assertExists(os.path.join(tempdir, 'Makefile.am'), 'Extracted source could not be found')
+        self.assertExists(os.path.join(self.workspacedir, 'conf', 'layer.conf'), 'Workspace directory not created')
         matches = glob.glob(os.path.join(self.workspacedir, 'appends', '%s_*.bbappend' % realrecipe))
         self.assertTrue(matches, 'bbappend not created %s' % result.output)
         # Test devtool status
@@ -678,7 +690,7 @@
         # This is probably sufficient
 
 
-    @testcase(1169)
+    @OETestID(1169)
     def test_devtool_update_recipe(self):
         # Check preconditions
         testrecipe = 'minicom'
@@ -711,7 +723,7 @@
                            ('??', '.*/0002-Add-a-new-file.patch$')]
         self._check_repo_status(os.path.dirname(recipefile), expected_status)
 
-    @testcase(1172)
+    @OETestID(1172)
     def test_devtool_update_recipe_git(self):
         # Check preconditions
         testrecipe = 'mtd-utils'
@@ -781,7 +793,7 @@
                            ('??', '%s/0002-Add-a-new-file.patch' % relpatchpath)]
         self._check_repo_status(os.path.dirname(recipefile), expected_status)
 
-    @testcase(1170)
+    @OETestID(1170)
     def test_devtool_update_recipe_append(self):
         # Check preconditions
         testrecipe = 'mdadm'
@@ -817,7 +829,7 @@
         appenddir = os.path.join(templayerdir, splitpath[-2], splitpath[-1])
         bbappendfile = self._check_bbappend(testrecipe, recipefile, appenddir)
         patchfile = os.path.join(appenddir, testrecipe, '0001-Add-our-custom-version.patch')
-        self.assertTrue(os.path.exists(patchfile), 'Patch file not created')
+        self.assertExists(patchfile, 'Patch file not created')
 
         # Check bbappend contents
         expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
@@ -834,7 +846,7 @@
         # Drop new commit and check patch gets deleted
         result = runCmd('git reset HEAD^', cwd=tempsrcdir)
         result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir))
-        self.assertFalse(os.path.exists(patchfile), 'Patch file not deleted')
+        self.assertNotExists(patchfile, 'Patch file not deleted')
         expectedlines2 = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
                          '\n']
         with open(bbappendfile, 'r') as f:
@@ -845,12 +857,12 @@
         result = runCmd('bitbake-layers remove-layer %s' % templayerdir, cwd=self.builddir)
         result = runCmd('devtool update-recipe %s -a %s' % (testrecipe, templayerdir))
         self.assertIn('WARNING: Specified layer is not currently enabled in bblayers.conf', result.output)
-        self.assertTrue(os.path.exists(patchfile), 'Patch file not created (with disabled layer)')
+        self.assertExists(patchfile, 'Patch file not created (with disabled layer)')
         with open(bbappendfile, 'r') as f:
             self.assertEqual(expectedlines, f.readlines())
         # Deleting isn't expected to work under these circumstances
 
-    @testcase(1171)
+    @OETestID(1171)
     def test_devtool_update_recipe_append_git(self):
         # Check preconditions
         testrecipe = 'mtd-utils'
@@ -898,7 +910,7 @@
         splitpath = os.path.dirname(recipefile).split(os.sep)
         appenddir = os.path.join(templayerdir, splitpath[-2], splitpath[-1])
         bbappendfile = self._check_bbappend(testrecipe, recipefile, appenddir)
-        self.assertFalse(os.path.exists(os.path.join(appenddir, testrecipe)), 'Patch directory should not be created')
+        self.assertNotExists(os.path.join(appenddir, testrecipe), 'Patch directory should not be created')
 
         # Check bbappend contents
         result = runCmd('git rev-parse HEAD', cwd=tempsrcdir)
@@ -916,7 +928,7 @@
         # Drop new commit and check SRCREV changes
         result = runCmd('git reset HEAD^', cwd=tempsrcdir)
         result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir))
-        self.assertFalse(os.path.exists(os.path.join(appenddir, testrecipe)), 'Patch directory should not be created')
+        self.assertNotExists(os.path.join(appenddir, testrecipe), 'Patch directory should not be created')
         result = runCmd('git rev-parse HEAD', cwd=tempsrcdir)
         expectedlines = set(['SRCREV = "%s"\n' % result.output,
                              '\n',
@@ -930,7 +942,7 @@
         result = runCmd('bitbake-layers remove-layer %s' % templayerdir, cwd=self.builddir)
         result = runCmd('devtool update-recipe -m srcrev %s -a %s' % (testrecipe, templayerdir))
         self.assertIn('WARNING: Specified layer is not currently enabled in bblayers.conf', result.output)
-        self.assertFalse(os.path.exists(os.path.join(appenddir, testrecipe)), 'Patch directory should not be created')
+        self.assertNotExists(os.path.join(appenddir, testrecipe), 'Patch directory should not be created')
         result = runCmd('git rev-parse HEAD', cwd=tempsrcdir)
         expectedlines = set(['SRCREV = "%s"\n' % result.output,
                              '\n',
@@ -940,7 +952,7 @@
             self.assertEqual(expectedlines, set(f.readlines()))
         # Deleting isn't expected to work under these circumstances
 
-    @testcase(1370)
+    @OETestID(1370)
     def test_devtool_update_recipe_local_files(self):
         """Check that local source files are copied over instead of patched"""
         testrecipe = 'makedevs'
@@ -972,7 +984,7 @@
                            ('??', '.*/makedevs/0001-Add-new-file.patch$')]
         self._check_repo_status(os.path.dirname(recipefile), expected_status)
 
-    @testcase(1371)
+    @OETestID(1371)
     def test_devtool_update_recipe_local_files_2(self):
         """Check local source files support when oe-local-files is in Git"""
         testrecipe = 'lzo'
@@ -1013,6 +1025,7 @@
                            ('??', '.*/0001-Add-new-file.patch$')]
         self._check_repo_status(os.path.dirname(recipefile), expected_status)
 
+    @OETestID(1627)
     def test_devtool_update_recipe_local_files_3(self):
         # First, modify the recipe
         testrecipe = 'devtool-test-localonly'
@@ -1032,6 +1045,7 @@
         expected_status = [(' M', '.*/%s/file2$' % testrecipe)]
         self._check_repo_status(os.path.dirname(recipefile), expected_status)
 
+    @OETestID(1629)
     def test_devtool_update_recipe_local_patch_gz(self):
         # First, modify the recipe
         testrecipe = 'devtool-test-patch-gz'
@@ -1059,8 +1073,9 @@
         if 'gzip compressed data' not in result.output:
             self.fail('New patch file is not gzipped - file reports:\n%s' % result.output)
 
+    @OETestID(1628)
     def test_devtool_update_recipe_local_files_subdir(self):
-        # Try devtool extract on a recipe that has a file with subdir= set in
+        # Try devtool update-recipe on a recipe that has a file with subdir= set in
         # SRC_URI such that it overwrites a file that was in an archive that
         # was also in SRC_URI
         # First, modify the recipe
@@ -1075,7 +1090,7 @@
         # (don't bother with cleaning the recipe on teardown, we won't be building it)
         result = runCmd('devtool modify %s' % testrecipe)
         testfile = os.path.join(self.workspacedir, 'sources', testrecipe, 'testfile')
-        self.assertTrue(os.path.exists(testfile), 'Extracted source could not be found')
+        self.assertExists(testfile, 'Extracted source could not be found')
         with open(testfile, 'r') as f:
             contents = f.read().rstrip()
         self.assertEqual(contents, 'Modified version', 'File has apparently not been overwritten as it should have been')
@@ -1085,30 +1100,29 @@
         expected_status = []
         self._check_repo_status(os.path.dirname(recipefile), expected_status)
 
-    @testcase(1163)
+    @OETestID(1163)
     def test_devtool_extract(self):
         tempdir = tempfile.mkdtemp(prefix='devtoolqa')
         # Try devtool extract
         self.track_for_cleanup(tempdir)
-        self.append_config('PREFERRED_PROVIDER_virtual/make = "remake"')
-        result = runCmd('devtool extract remake %s' % tempdir)
-        self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile.am')), 'Extracted source could not be found')
-        # devtool extract shouldn't create the workspace
-        self.assertFalse(os.path.exists(self.workspacedir))
+        self.track_for_cleanup(self.workspacedir)
+        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
+        result = runCmd('devtool extract matchbox-terminal %s' % tempdir)
+        self.assertExists(os.path.join(tempdir, 'Makefile.am'), 'Extracted source could not be found')
         self._check_src_repo(tempdir)
 
-    @testcase(1379)
+    @OETestID(1379)
     def test_devtool_extract_virtual(self):
         tempdir = tempfile.mkdtemp(prefix='devtoolqa')
         # Try devtool extract
         self.track_for_cleanup(tempdir)
+        self.track_for_cleanup(self.workspacedir)
+        self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
         result = runCmd('devtool extract virtual/make %s' % tempdir)
-        self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile.am')), 'Extracted source could not be found')
-        # devtool extract shouldn't create the workspace
-        self.assertFalse(os.path.exists(self.workspacedir))
+        self.assertExists(os.path.join(tempdir, 'Makefile.am'), 'Extracted source could not be found')
         self._check_src_repo(tempdir)
 
-    @testcase(1168)
+    @OETestID(1168)
     def test_devtool_reset_all(self):
         tempdir = tempfile.mkdtemp(prefix='devtoolqa')
         self.track_for_cleanup(tempdir)
@@ -1135,7 +1149,7 @@
         matches2 = glob.glob(stampprefix2 + '*')
         self.assertFalse(matches2, 'Stamp files exist for recipe %s that should have been cleaned' % testrecipe2)
 
-    @testcase(1272)
+    @OETestID(1272)
     def test_devtool_deploy_target(self):
         # NOTE: Whilst this test would seemingly be better placed as a runtime test,
         # unfortunately the runtime tests run under bitbake and you can't run
@@ -1221,7 +1235,7 @@
             result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand), ignore_status=True)
             self.assertNotEqual(result, 0, 'undeploy-target did not remove command as it should have')
 
-    @testcase(1366)
+    @OETestID(1366)
     def test_devtool_build_image(self):
         """Test devtool build-image plugin"""
         # Check preconditions
@@ -1255,7 +1269,7 @@
         if reqpkgs:
             self.fail('The following packages were not present in the image as expected: %s' % ', '.join(reqpkgs))
 
-    @testcase(1367)
+    @OETestID(1367)
     def test_devtool_upgrade(self):
         # Check preconditions
         self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
@@ -1280,10 +1294,10 @@
         # Check if srctree at least is populated
         self.assertTrue(len(os.listdir(tempdir)) > 0, 'srctree (%s) should be populated with new (%s) source code' % (tempdir, version))
         # Check new recipe subdirectory is present
-        self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe, '%s-%s' % (recipe, version))), 'Recipe folder should exist')
+        self.assertExists(os.path.join(self.workspacedir, 'recipes', recipe, '%s-%s' % (recipe, version)), 'Recipe folder should exist')
         # Check new recipe file is present
         newrecipefile = os.path.join(self.workspacedir, 'recipes', recipe, '%s_%s.bb' % (recipe, version))
-        self.assertTrue(os.path.exists(newrecipefile), 'Recipe file should exist after upgrade')
+        self.assertExists(newrecipefile, 'Recipe file should exist after upgrade')
         # Check devtool status and make sure recipe is present
         result = runCmd('devtool status')
         self.assertIn(recipe, result.output)
@@ -1298,9 +1312,9 @@
         result = runCmd('devtool reset %s -n' % recipe)
         result = runCmd('devtool status')
         self.assertNotIn(recipe, result.output)
-        self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after resetting')
+        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after resetting')
 
-    @testcase(1433)
+    @OETestID(1433)
     def test_devtool_upgrade_git(self):
         # Check preconditions
         self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
@@ -1320,7 +1334,7 @@
         self.assertTrue(len(os.listdir(tempdir)) > 0, 'srctree (%s) should be populated with new (%s) source code' % (tempdir, commit))
         # Check new recipe file is present
         newrecipefile = os.path.join(self.workspacedir, 'recipes', recipe, os.path.basename(oldrecipefile))
-        self.assertTrue(os.path.exists(newrecipefile), 'Recipe file should exist after upgrade')
+        self.assertExists(newrecipefile, 'Recipe file should exist after upgrade')
         # Check devtool status and make sure recipe is present
         result = runCmd('devtool status')
         self.assertIn(recipe, result.output)
@@ -1335,9 +1349,9 @@
         result = runCmd('devtool reset %s -n' % recipe)
         result = runCmd('devtool status')
         self.assertNotIn(recipe, result.output)
-        self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after resetting')
+        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after resetting')
 
-    @testcase(1352)
+    @OETestID(1352)
     def test_devtool_layer_plugins(self):
         """Test that devtool can use plugins from other layers.
 
@@ -1352,7 +1366,7 @@
 
     def _copy_file_with_cleanup(self, srcfile, basedstdir, *paths):
         dstdir = basedstdir
-        self.assertTrue(os.path.exists(dstdir))
+        self.assertExists(dstdir)
         for p in paths:
             dstdir = os.path.join(dstdir, p)
             if not os.path.exists(dstdir):
@@ -1363,6 +1377,7 @@
             shutil.copy(srcfile, dstfile)
             self.track_for_cleanup(dstfile)
 
+    @OETestID(1625)
     def test_devtool_load_plugin(self):
         """Test that devtool loads only the first found plugin in BBPATH."""
 
@@ -1427,9 +1442,10 @@
         recipedir = os.path.dirname(oldrecipefile)
         olddir = os.path.join(recipedir, recipe + '-' + oldversion)
         patchfn = '0001-Add-a-note-line-to-the-quick-reference.patch'
-        self.assertTrue(os.path.exists(os.path.join(olddir, patchfn)), 'Original patch file does not exist')
+        self.assertExists(os.path.join(olddir, patchfn), 'Original patch file does not exist')
         return recipe, oldrecipefile, recipedir, olddir, newversion, patchfn
 
+    @OETestID(1623)
     def test_devtool_finish_upgrade_origlayer(self):
         recipe, oldrecipefile, recipedir, olddir, newversion, patchfn = self._setup_test_devtool_finish_upgrade()
         # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things)
@@ -1439,15 +1455,16 @@
         result = runCmd('devtool finish %s meta-selftest' % recipe)
         result = runCmd('devtool status')
         self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t')
-        self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after finish')
-        self.assertFalse(os.path.exists(oldrecipefile), 'Old recipe file should have been deleted but wasn\'t')
-        self.assertFalse(os.path.exists(os.path.join(olddir, patchfn)), 'Old patch file should have been deleted but wasn\'t')
+        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after finish')
+        self.assertNotExists(oldrecipefile, 'Old recipe file should have been deleted but wasn\'t')
+        self.assertNotExists(os.path.join(olddir, patchfn), 'Old patch file should have been deleted but wasn\'t')
         newrecipefile = os.path.join(recipedir, '%s_%s.bb' % (recipe, newversion))
         newdir = os.path.join(recipedir, recipe + '-' + newversion)
-        self.assertTrue(os.path.exists(newrecipefile), 'New recipe file should have been copied into existing layer but wasn\'t')
-        self.assertTrue(os.path.exists(os.path.join(newdir, patchfn)), 'Patch file should have been copied into new directory but wasn\'t')
-        self.assertTrue(os.path.exists(os.path.join(newdir, '0002-Add-a-comment-to-the-code.patch')), 'New patch file should have been created but wasn\'t')
+        self.assertExists(newrecipefile, 'New recipe file should have been copied into existing layer but wasn\'t')
+        self.assertExists(os.path.join(newdir, patchfn), 'Patch file should have been copied into new directory but wasn\'t')
+        self.assertExists(os.path.join(newdir, '0002-Add-a-comment-to-the-code.patch'), 'New patch file should have been created but wasn\'t')
 
+    @OETestID(1624)
     def test_devtool_finish_upgrade_otherlayer(self):
         recipe, oldrecipefile, recipedir, olddir, newversion, patchfn = self._setup_test_devtool_finish_upgrade()
         # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things)
@@ -1462,13 +1479,13 @@
         result = runCmd('devtool finish %s oe-core' % recipe)
         result = runCmd('devtool status')
         self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t')
-        self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after finish')
-        self.assertTrue(os.path.exists(oldrecipefile), 'Old recipe file should not have been deleted')
-        self.assertTrue(os.path.exists(os.path.join(olddir, patchfn)), 'Old patch file should not have been deleted')
+        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after finish')
+        self.assertExists(oldrecipefile, 'Old recipe file should not have been deleted')
+        self.assertExists(os.path.join(olddir, patchfn), 'Old patch file should not have been deleted')
         newdir = os.path.join(newrecipedir, recipe + '-' + newversion)
-        self.assertTrue(os.path.exists(newrecipefile), 'New recipe file should have been copied into existing layer but wasn\'t')
-        self.assertTrue(os.path.exists(os.path.join(newdir, patchfn)), 'Patch file should have been copied into new directory but wasn\'t')
-        self.assertTrue(os.path.exists(os.path.join(newdir, '0002-Add-a-comment-to-the-code.patch')), 'New patch file should have been created but wasn\'t')
+        self.assertExists(newrecipefile, 'New recipe file should have been copied into existing layer but wasn\'t')
+        self.assertExists(os.path.join(newdir, patchfn), 'Patch file should have been copied into new directory but wasn\'t')
+        self.assertExists(os.path.join(newdir, '0002-Add-a-comment-to-the-code.patch'), 'New patch file should have been created but wasn\'t')
 
     def _setup_test_devtool_finish_modify(self):
         # Check preconditions
@@ -1485,7 +1502,7 @@
         self.track_for_cleanup(tempdir)
         self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
         result = runCmd('devtool modify %s %s' % (recipe, tempdir))
-        self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile')), 'Extracted source could not be found')
+        self.assertExists(os.path.join(tempdir, 'Makefile'), 'Extracted source could not be found')
         # Test devtool status
         result = runCmd('devtool status')
         self.assertIn(recipe, result.output)
@@ -1503,6 +1520,7 @@
             self.fail('Unable to find recipe files directory for %s' % recipe)
         return recipe, oldrecipefile, recipedir, filesdir
 
+    @OETestID(1621)
     def test_devtool_finish_modify_origlayer(self):
         recipe, oldrecipefile, recipedir, filesdir = self._setup_test_devtool_finish_modify()
         # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things)
@@ -1512,11 +1530,12 @@
         result = runCmd('devtool finish %s meta' % recipe)
         result = runCmd('devtool status')
         self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t')
-        self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after finish')
+        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after finish')
         expected_status = [(' M', '.*/%s$' % os.path.basename(oldrecipefile)),
                            ('??', '.*/.*-Add-a-comment-to-the-code.patch$')]
         self._check_repo_status(recipedir, expected_status)
 
+    @OETestID(1622)
     def test_devtool_finish_modify_otherlayer(self):
         recipe, oldrecipefile, recipedir, filesdir = self._setup_test_devtool_finish_modify()
         # Ensure the recipe is where we think it should be (so that cleanup doesn't trash things)
@@ -1529,14 +1548,14 @@
         result = runCmd('devtool finish %s meta-selftest' % recipe)
         result = runCmd('devtool status')
         self.assertNotIn(recipe, result.output, 'Recipe should have been reset by finish but wasn\'t')
-        self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipe)), 'Recipe directory should not exist after finish')
+        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipe), 'Recipe directory should not exist after finish')
         result = runCmd('git status --porcelain .', cwd=recipedir)
         if result.output.strip():
             self.fail('Recipe directory for %s contains the following unexpected changes after finish:\n%s' % (recipe, result.output.strip()))
         recipefn = os.path.splitext(os.path.basename(oldrecipefile))[0]
         recipefn = recipefn.split('_')[0] + '_%'
         appendfile = os.path.join(appenddir, recipefn + '.bbappend')
-        self.assertTrue(os.path.exists(appendfile), 'bbappend %s should have been created but wasn\'t' % appendfile)
+        self.assertExists(appendfile, 'bbappend %s should have been created but wasn\'t' % appendfile)
         newdir = os.path.join(appenddir, recipe)
         files = os.listdir(newdir)
         foundpatch = None
@@ -1549,6 +1568,7 @@
         if files:
             self.fail('Unexpected file(s) copied next to bbappend: %s' % ', '.join(files))
 
+    @OETestID(1626)
     def test_devtool_rename(self):
         # Check preconditions
         self.assertTrue(not os.path.exists(self.workspacedir), 'This test cannot be run with a workspace directory under the build directory')
@@ -1563,8 +1583,8 @@
         url = 'http://downloads.yoctoproject.org/mirror/sources/i2c-tools-%s.tar.bz2' % recipever
         def add_recipe():
             result = runCmd('devtool add %s' % url)
-            self.assertTrue(os.path.exists(recipefile), 'Expected recipe file not created')
-            self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'sources', recipename)), 'Source directory not created')
+            self.assertExists(recipefile, 'Expected recipe file not created')
+            self.assertExists(os.path.join(self.workspacedir, 'sources', recipename), 'Source directory not created')
             checkvars = {}
             checkvars['S'] = None
             checkvars['SRC_URI'] = url.replace(recipever, '${PV}')
@@ -1575,10 +1595,10 @@
         newrecipever = '456'
         newrecipefile = os.path.join(self.workspacedir, 'recipes', newrecipename, '%s_%s.bb' % (newrecipename, newrecipever))
         result = runCmd('devtool rename %s %s -V %s' % (recipename, newrecipename, newrecipever))
-        self.assertTrue(os.path.exists(newrecipefile), 'Recipe file not renamed')
-        self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipename)), 'Old recipe directory still exists')
+        self.assertExists(newrecipefile, 'Recipe file not renamed')
+        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipename), 'Old recipe directory still exists')
         newsrctree = os.path.join(self.workspacedir, 'sources', newrecipename)
-        self.assertTrue(os.path.exists(newsrctree), 'Source directory not renamed')
+        self.assertExists(newsrctree, 'Source directory not renamed')
         checkvars = {}
         checkvars['S'] = '${WORKDIR}/%s-%s' % (recipename, recipever)
         checkvars['SRC_URI'] = url
@@ -1589,9 +1609,9 @@
         add_recipe()
         newrecipefile = os.path.join(self.workspacedir, 'recipes', newrecipename, '%s_%s.bb' % (newrecipename, recipever))
         result = runCmd('devtool rename %s %s' % (recipename, newrecipename))
-        self.assertTrue(os.path.exists(newrecipefile), 'Recipe file not renamed')
-        self.assertFalse(os.path.exists(os.path.join(self.workspacedir, 'recipes', recipename)), 'Old recipe directory still exists')
-        self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'sources', newrecipename)), 'Source directory not renamed')
+        self.assertExists(newrecipefile, 'Recipe file not renamed')
+        self.assertNotExists(os.path.join(self.workspacedir, 'recipes', recipename), 'Old recipe directory still exists')
+        self.assertExists(os.path.join(self.workspacedir, 'sources', newrecipename), 'Source directory not renamed')
         checkvars = {}
         checkvars['S'] = '${WORKDIR}/%s-${PV}' % recipename
         checkvars['SRC_URI'] = url.replace(recipever, '${PV}')
@@ -1602,14 +1622,14 @@
         add_recipe()
         newrecipefile = os.path.join(self.workspacedir, 'recipes', recipename, '%s_%s.bb' % (recipename, newrecipever))
         result = runCmd('devtool rename %s -V %s' % (recipename, newrecipever))
-        self.assertTrue(os.path.exists(newrecipefile), 'Recipe file not renamed')
-        self.assertTrue(os.path.exists(os.path.join(self.workspacedir, 'sources', recipename)), 'Source directory no longer exists')
+        self.assertExists(newrecipefile, 'Recipe file not renamed')
+        self.assertExists(os.path.join(self.workspacedir, 'sources', recipename), 'Source directory no longer exists')
         checkvars = {}
         checkvars['S'] = '${WORKDIR}/${BPN}-%s' % recipever
         checkvars['SRC_URI'] = url
         self._test_recipe_contents(newrecipefile, checkvars, [])
 
-    @testcase(1577)
+    @OETestID(1577)
     def test_devtool_virtual_kernel_modify(self):
         """
         Summary:        The purpose of this test case is to verify that
@@ -1632,15 +1652,13 @@
                          and modification to the source and configurations are reflected
                          when building the kernel.
          """
-        #Set machine to qemxu86 to be able to modify the kernel and
-        #verify the modification.
-        features = 'MACHINE = "qemux86"\n'
-        self.write_config(features)
         kernel_provider = get_bb_var('PREFERRED_PROVIDER_virtual/kernel')
         # Clean up the enviroment
         bitbake('%s -c clean' % kernel_provider)
         tempdir = tempfile.mkdtemp(prefix='devtoolqa')
+        tempdir_cfg = tempfile.mkdtemp(prefix='config_qa')
         self.track_for_cleanup(tempdir)
+        self.track_for_cleanup(tempdir_cfg)
         self.track_for_cleanup(self.workspacedir)
         self.add_command_to_tearDown('bitbake-layers remove-layer */workspace')
         self.add_command_to_tearDown('bitbake -c clean %s' % kernel_provider)
@@ -1649,19 +1667,16 @@
         #time of executing this test case.
         bitbake('%s -c configure' % kernel_provider)
         bbconfig = os.path.join(get_bb_var('B', kernel_provider),'.config')
-        buildir= get_bb_var('TOPDIR')
         #Step 2
-        runCmd('cp %s %s' % (bbconfig, buildir))
-        self.assertTrue(os.path.exists(os.path.join(buildir, '.config')),
-                        'Could not copy .config file from kernel')
+        runCmd('cp %s %s' % (bbconfig, tempdir_cfg))
+        self.assertExists(os.path.join(tempdir_cfg, '.config'), 'Could not copy .config file from kernel')
 
-        tmpconfig = os.path.join(buildir, '.config')
+        tmpconfig = os.path.join(tempdir_cfg, '.config')
         #Step 3
         bitbake('%s -c clean' % kernel_provider)
         #Step 4.1
         runCmd('devtool modify virtual/kernel -x %s' % tempdir)
-        self.assertTrue(os.path.exists(os.path.join(tempdir, 'Makefile')),
-                        'Extracted source could not be found')
+        self.assertExists(os.path.join(tempdir, 'Makefile'), 'Extracted source could not be found')
         #Step 4.2
         configfile = os.path.join(tempdir,'.config')
         diff = runCmd('diff %s %s' % (tmpconfig, configfile))
@@ -1671,12 +1686,12 @@
         result = runCmd('devtool build %s' % kernel_provider)
         self.assertEqual(0,result.status,'Cannot build kernel using `devtool build`')
         kernelfile = os.path.join(get_bb_var('KBUILD_OUTPUT', kernel_provider), 'vmlinux')
-        self.assertTrue(os.path.exists(kernelfile),'Kernel was not build correctly')
+        self.assertExists(kernelfile, 'Kernel was not build correctly')
 
-        #Modify the kernel source, this is specific for qemux86
+        #Modify the kernel source
         modfile = os.path.join(tempdir,'arch/x86/boot/header.S')
-        modstring = "use a boot loader - Devtool kernel testing"
-        modapplied = runCmd("sed -i 's/boot loader/%s/' %s" % (modstring, modfile))
+        modstring = "Use a boot loader. Devtool testing."
+        modapplied = runCmd("sed -i 's/Use a boot loader./%s/' %s" % (modstring, modfile))
         self.assertEqual(0,modapplied.status,'Modification to %s on kernel source failed' % modfile)
         #Modify the configuration
         codeconfigfile = os.path.join(tempdir,'.config.new')
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/distrodata.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/distrodata.py
new file mode 100644
index 0000000..12540ad
--- /dev/null
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/distrodata.py
@@ -0,0 +1,42 @@
+from oeqa.selftest.case import OESelftestTestCase
+from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars
+from oeqa.utils.decorators import testcase
+from oeqa.utils.ftools import write_file
+from oeqa.core.decorator.oeid import OETestID
+
+class Distrodata(OESelftestTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        super(Distrodata, cls).setUpClass()
+
+    @OETestID(1902)
+    def test_checkpkg(self):
+        """
+        Summary:     Test that upstream version checks do not regress
+        Expected:    Upstream version checks should succeed except for the recipes listed in the exception list.
+        Product:     oe-core
+        Author:      Alexander Kanavin <alexander.kanavin@intel.com>
+        """
+        feature = 'INHERIT += "distrodata"\n'
+        feature += 'LICENSE_FLAGS_WHITELIST += " commercial"\n'
+
+        self.write_config(feature)
+        bitbake('-c checkpkg world')
+        checkpkg_result = open(os.path.join(get_bb_var("LOG_DIR"), "checkpkg.csv")).readlines()[1:]
+        regressed_failures = [pkg_data[0] for pkg_data in [pkg_line.split('\t') for pkg_line in checkpkg_result] if pkg_data[11] == 'UNKNOWN_BROKEN']
+        regressed_successes = [pkg_data[0] for pkg_data in [pkg_line.split('\t') for pkg_line in checkpkg_result] if pkg_data[11] == 'KNOWN_BROKEN']
+        msg = ""
+        if len(regressed_failures) > 0:
+            msg = msg + """
+The following packages failed upstream version checks. Please fix them using UPSTREAM_CHECK_URI/UPSTREAM_CHECK_REGEX
+(when using tarballs) or UPSTREAM_CHECK_GITTAGREGEX (when using git). If an upstream version check cannot be performed
+(for example, if upstream does not use git tags), you can set UPSTREAM_VERSION_UNKNOWN to '1' in the recipe to acknowledge
+that the check cannot be performed.
+""" + "\n".join(regressed_failures)
+        if len(regressed_successes) > 0:
+            msg = msg + """
+The following packages have been checked successfully for upstream versions,
+but their recipes claim otherwise by setting UPSTREAM_VERSION_UNKNOWN. Please remove that line from the recipes.
+""" + "\n".join(regressed_successes)
+        self.assertTrue(len(regressed_failures) == 0 and len(regressed_successes) == 0, msg)
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/eSDK.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/eSDK.py
similarity index 91%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/eSDK.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/eSDK.py
index 1596c6e..d03188f 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/eSDK.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/eSDK.py
@@ -1,16 +1,12 @@
-import unittest
 import tempfile
 import shutil
 import os
 import glob
-import logging
-import subprocess
-import oeqa.utils.ftools as ftools
-from oeqa.utils.decorators import testcase
-from oeqa.selftest.base import oeSelfTest
+from oeqa.core.decorator.oeid import OETestID
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars
 
-class oeSDKExtSelfTest(oeSelfTest):
+class oeSDKExtSelfTest(OESelftestTestCase):
     """
     # Bugzilla Test Plan: 6033
     # This code is planned to be part of the automation for eSDK containig
@@ -73,6 +69,7 @@
 
     @classmethod
     def setUpClass(cls):
+        super(oeSDKExtSelfTest, cls).setUpClass()
         cls.tmpdir_eSDKQA = tempfile.mkdtemp(prefix='eSDKQA')
 
         sstate_dir = get_bb_var('SSTATE_DIR')
@@ -96,20 +93,19 @@
 
     @classmethod
     def tearDownClass(cls):
-        shutil.rmtree(cls.tmpdir_eSDKQA)
+        shutil.rmtree(cls.tmpdir_eSDKQA, ignore_errors=True)
+        super(oeSDKExtSelfTest, cls).tearDownClass()
 
-    @testcase (1602)
+    @OETestID(1602)
     def test_install_libraries_headers(self):
         pn_sstate = 'bc'
         bitbake(pn_sstate)
         cmd = "devtool sdk-install %s " % pn_sstate
         oeSDKExtSelfTest.run_esdk_cmd(self.env_eSDK, self.tmpdir_eSDKQA, cmd)
 
-    @testcase(1603)
+    @OETestID(1603)
     def test_image_generation_binary_feeds(self):
         image = 'core-image-minimal'
         cmd = "devtool build-image %s" % image
         oeSDKExtSelfTest.run_esdk_cmd(self.env_eSDK, self.tmpdir_eSDKQA, cmd)
 
-if __name__ == '__main__':
-    unittest.main()
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/image_typedep.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/image_typedep.py
similarity index 90%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/image_typedep.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/image_typedep.py
index 256142d..e678885 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/image_typedep.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/image_typedep.py
@@ -1,12 +1,14 @@
 import os
 
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import bitbake
+from oeqa.core.decorator.oeid import OETestID
 
-class ImageTypeDepTests(oeSelfTest):
+class ImageTypeDepTests(OESelftestTestCase):
 
     # Verify that when specifying a IMAGE_TYPEDEP_ of the form "foo.bar" that
     # the conversion type bar gets added as a dep as well
+    @OETestID(1633)
     def test_conversion_typedep_added(self):
 
         self.write_recipeinc('emptytest', """
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/imagefeatures.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/imagefeatures.py
new file mode 100644
index 0000000..0ffb686
--- /dev/null
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/imagefeatures.py
@@ -0,0 +1,240 @@
+from oeqa.selftest.case import OESelftestTestCase
+from oeqa.utils.commands import runCmd, bitbake, get_bb_var, runqemu
+from oeqa.core.decorator.oeid import OETestID
+from oeqa.utils.sshcontrol import SSHControl
+import os
+import json
+
+class ImageFeatures(OESelftestTestCase):
+
+    test_user = 'tester'
+    root_user = 'root'
+
+    buffer = True
+
+    @OETestID(1107)
+    def test_non_root_user_can_connect_via_ssh_without_password(self):
+        """
+        Summary: Check if non root user can connect via ssh without password
+        Expected: 1. Connection to the image via ssh using root user without providing a password should be allowed.
+                  2. Connection to the image via ssh using tester user without providing a password should be allowed.
+        Product: oe-core
+        Author: Ionut Chisanovici <ionutx.chisanovici@intel.com>
+        AutomatedBy: Daniel Istrate <daniel.alexandrux.istrate@intel.com>
+        """
+
+        features = 'EXTRA_IMAGE_FEATURES = "ssh-server-openssh empty-root-password allow-empty-password"\n'
+        features += 'INHERIT += "extrausers"\n'
+        features += 'EXTRA_USERS_PARAMS = "useradd -p \'\' {}; usermod -s /bin/sh {};"'.format(self.test_user, self.test_user)
+        self.write_config(features)
+
+        # Build a core-image-minimal
+        bitbake('core-image-minimal')
+
+        with runqemu("core-image-minimal") as qemu:
+            # Attempt to ssh with each user into qemu with empty password
+            for user in [self.root_user, self.test_user]:
+                ssh = SSHControl(ip=qemu.ip, logfile=qemu.sshlog, user=user)
+                status, output = ssh.run("true")
+                self.assertEqual(status, 0, 'ssh to user %s failed with %s' % (user, output))
+
+    @OETestID(1115)
+    def test_all_users_can_connect_via_ssh_without_password(self):
+        """
+        Summary:     Check if all users can connect via ssh without password
+        Expected: 1. Connection to the image via ssh using root user without providing a password should NOT be allowed.
+                  2. Connection to the image via ssh using tester user without providing a password should be allowed.
+        Product:     oe-core
+        Author:      Ionut Chisanovici <ionutx.chisanovici@intel.com>
+        AutomatedBy: Daniel Istrate <daniel.alexandrux.istrate@intel.com>
+        """
+
+        features = 'EXTRA_IMAGE_FEATURES = "ssh-server-openssh allow-empty-password"\n'
+        features += 'INHERIT += "extrausers"\n'
+        features += 'EXTRA_USERS_PARAMS = "useradd -p \'\' {}; usermod -s /bin/sh {};"'.format(self.test_user, self.test_user)
+        self.write_config(features)
+
+        # Build a core-image-minimal
+        bitbake('core-image-minimal')
+
+        with runqemu("core-image-minimal") as qemu:
+            # Attempt to ssh with each user into qemu with empty password
+            for user in [self.root_user, self.test_user]:
+                ssh = SSHControl(ip=qemu.ip, logfile=qemu.sshlog, user=user)
+                status, output = ssh.run("true")
+                if user == 'root':
+                    self.assertNotEqual(status, 0, 'ssh to user root was allowed when it should not have been')
+                else:
+                    self.assertEqual(status, 0, 'ssh to user tester failed with %s' % output)
+
+
+    @OETestID(1116)
+    def test_clutter_image_can_be_built(self):
+        """
+        Summary:     Check if clutter image can be built
+        Expected:    1. core-image-clutter can be built
+        Product:     oe-core
+        Author:      Ionut Chisanovici <ionutx.chisanovici@intel.com>
+        AutomatedBy: Daniel Istrate <daniel.alexandrux.istrate@intel.com>
+        """
+
+        # Build a core-image-clutter
+        bitbake('core-image-clutter')
+
+    @OETestID(1117)
+    def test_wayland_support_in_image(self):
+        """
+        Summary:     Check Wayland support in image
+        Expected:    1. Wayland image can be build
+                     2. Wayland feature can be installed
+        Product:     oe-core
+        Author:      Ionut Chisanovici <ionutx.chisanovici@intel.com>
+        AutomatedBy: Daniel Istrate <daniel.alexandrux.istrate@intel.com>
+        """
+
+        distro_features = get_bb_var('DISTRO_FEATURES')
+        if not ('opengl' in distro_features and 'wayland' in distro_features):
+            self.skipTest('neither opengl nor wayland present on DISTRO_FEATURES so core-image-weston cannot be built')
+
+        # Build a core-image-weston
+        bitbake('core-image-weston')
+
+    @OETestID(1497)
+    def test_bmap(self):
+        """
+        Summary:     Check bmap support
+        Expected:    1. core-image-minimal can be build with bmap support
+                     2. core-image-minimal is sparse
+        Product:     oe-core
+        Author:      Ed Bartosh <ed.bartosh@linux.intel.com>
+        """
+
+        features = 'IMAGE_FSTYPES += " ext4 ext4.bmap ext4.bmap.gz"'
+        self.write_config(features)
+
+        image_name = 'core-image-minimal'
+        bitbake(image_name)
+
+        deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
+        link_name = get_bb_var('IMAGE_LINK_NAME', image_name)
+        image_path = os.path.join(deploy_dir_image, "%s.ext4" % link_name)
+        bmap_path = "%s.bmap" % image_path
+        gzip_path = "%s.gz" % bmap_path
+
+        # check if result image, bmap and bmap.gz files are in deploy directory
+        self.assertTrue(os.path.exists(image_path))
+        self.assertTrue(os.path.exists(bmap_path))
+        self.assertTrue(os.path.exists(gzip_path))
+
+        # check if result image is sparse
+        image_stat = os.stat(image_path)
+        self.assertTrue(image_stat.st_size > image_stat.st_blocks * 512)
+
+        # check if the resulting gzip is valid
+        self.assertTrue(runCmd('gzip -t %s' % gzip_path))
+
+    @OETestID(1903)
+    def test_hypervisor_fmts(self):
+        """
+        Summary:     Check various hypervisor formats
+        Expected:    1. core-image-minimal can be built with vmdk, vdi and
+                        qcow2 support.
+                     2. qemu-img says each image has the expected format
+        Product:     oe-core
+        Author:      Tom Rini <trini@konsulko.com>
+        """
+
+        img_types = [ 'vmdk', 'vdi', 'qcow2' ]
+        features = ""
+        for itype in img_types:
+            features += 'IMAGE_FSTYPES += "wic.%s"\n' % itype
+        self.write_config(features)
+
+        image_name = 'core-image-minimal'
+        bitbake(image_name)
+
+        deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
+        link_name = get_bb_var('IMAGE_LINK_NAME', image_name)
+        for itype in img_types:
+            image_path = os.path.join(deploy_dir_image, "%s.wic.%s" %
+                                      (link_name, itype))
+
+            # check if result image file is in deploy directory
+            self.assertTrue(os.path.exists(image_path))
+
+            # check if result image is vmdk
+            sysroot = get_bb_var('STAGING_DIR_NATIVE', 'core-image-minimal')
+            result = runCmd('qemu-img info --output json %s' % image_path,
+                            native_sysroot=sysroot)
+            self.assertTrue(json.loads(result.output).get('format') == itype)
+
+    @OETestID(1905)
+    def test_long_chain_conversion(self):
+        """
+        Summary:     Check for chaining many CONVERSION_CMDs together
+        Expected:    1. core-image-minimal can be built with
+                        ext4.bmap.gz.bz2.lzo.xz.u-boot and also create a
+                        sha256sum
+                     2. The above image has a valid sha256sum
+        Product:     oe-core
+        Author:      Tom Rini <trini@konsulko.com>
+        """
+
+        conv = "ext4.bmap.gz.bz2.lzo.xz.u-boot"
+        features = 'IMAGE_FSTYPES += "%s %s.sha256sum"' % (conv, conv)
+        self.write_config(features)
+
+        image_name = 'core-image-minimal'
+        bitbake(image_name)
+
+        deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
+        link_name = get_bb_var('IMAGE_LINK_NAME', image_name)
+        image_path = os.path.join(deploy_dir_image, "%s.%s" %
+                                  (link_name, conv))
+
+        # check if resulting image is in the deploy directory
+        self.assertTrue(os.path.exists(image_path))
+        self.assertTrue(os.path.exists(image_path + ".sha256sum"))
+
+        # check if the resulting sha256sum agrees
+        self.assertTrue(runCmd('cd %s;sha256sum -c %s.%s.sha256sum' %
+                               (deploy_dir_image, link_name, conv)))
+
+    @OETestID(1904)
+    def test_image_fstypes(self):
+        """
+        Summary:     Check if image of supported image fstypes can be built
+        Expected:    core-image-minimal can be built for various image types
+        Product:     oe-core
+        Author:      Ed Bartosh <ed.bartosh@linux.intel.com>
+        """
+        image_name = 'core-image-minimal'
+
+        img_types = [itype for itype in get_bb_var("IMAGE_TYPES", image_name).split() \
+                         if itype not in ('container', 'elf', 'multiubi')]
+
+        config = 'IMAGE_FSTYPES += "%s"\n'\
+                 'MKUBIFS_ARGS ?= "-m 2048 -e 129024 -c 2047"\n'\
+                 'UBINIZE_ARGS ?= "-m 2048 -p 128KiB -s 512"' % ' '.join(img_types)
+
+        self.write_config(config)
+
+        bitbake(image_name)
+
+        deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
+        link_name = get_bb_var('IMAGE_LINK_NAME', image_name)
+        for itype in img_types:
+            image_path = os.path.join(deploy_dir_image, "%s.%s" % (link_name, itype))
+            # check if result image is in deploy directory
+            self.assertTrue(os.path.exists(image_path),
+                            "%s image %s doesn't exist" % (itype, image_path))
+
+    def test_useradd_static(self):
+        config = """
+USERADDEXTENSION = "useradd-staticids"
+USERADD_ERROR_DYNAMIC = "skip"
+USERADD_UID_TABLES += "files/static-passwd"
+USERADD_GID_TABLES += "files/static-group"
+"""
+        self.write_config(config)
+        bitbake("core-image-base")
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/layerappend.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/layerappend.py
similarity index 92%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/layerappend.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/layerappend.py
index 37bb32c..2fd5cdb 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/layerappend.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/layerappend.py
@@ -1,15 +1,11 @@
-import unittest
 import os
-import logging
-import re
 
-from oeqa.selftest.base import oeSelfTest
-from oeqa.selftest.buildhistory import BuildhistoryBase
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import runCmd, bitbake, get_bb_var
 import oeqa.utils.ftools as ftools
-from oeqa.utils.decorators import testcase
+from oeqa.core.decorator.oeid import OETestID
 
-class LayerAppendTests(oeSelfTest):
+class LayerAppendTests(OESelftestTestCase):
     layerconf = """
 # We have a conf and classes directory, append to BBPATH
 BBPATH .= ":${LAYERDIR}"
@@ -44,15 +40,16 @@
     append2 = """
 FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"
 
-SRC_URI_append += "file://appendtest.txt"
+SRC_URI_append = " file://appendtest.txt"
 """
     layerappend = ''
 
     def tearDownLocal(self):
         if self.layerappend:
             ftools.remove_from_file(self.builddir + "/conf/bblayers.conf", self.layerappend)
+        super(LayerAppendTests, self).tearDownLocal()
 
-    @testcase(1196)
+    @OETestID(1196)
     def test_layer_appends(self):
         corebase = get_bb_var("COREBASE")
 
@@ -96,5 +93,3 @@
         bitbake("layerappendtest")
         data = ftools.read_file(stagingdir + "/appendtest.txt")
         self.assertEqual(data, "Layer 2 test")
-
-
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/liboe.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/liboe.py
similarity index 93%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/liboe.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/liboe.py
index 0b0301d..e846092 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/liboe.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/liboe.py
@@ -1,16 +1,17 @@
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
+from oeqa.core.decorator.oeid import OETestID
 from oeqa.utils.commands import get_bb_var, get_bb_vars, bitbake, runCmd
 import oe.path
-import glob
 import os
-import os.path
 
-class LibOE(oeSelfTest):
+class LibOE(OESelftestTestCase):
 
     @classmethod
     def setUpClass(cls):
+        super(LibOE, cls).setUpClass()
         cls.tmp_dir = get_bb_var('TMPDIR')
 
+    @OETestID(1635)
     def test_copy_tree_special(self):
         """
         Summary:    oe.path.copytree() should copy files with special character
@@ -36,6 +37,7 @@
 
         oe.path.remove(testloc)
 
+    @OETestID(1636)
     def test_copy_tree_xattr(self):
         """
         Summary:    oe.path.copytree() should preserve xattr on copied files
@@ -70,6 +72,7 @@
 
         oe.path.remove(testloc)
 
+    @OETestID(1634)
     def test_copy_hardlink_tree_count(self):
         """
         Summary:    oe.path.copyhardlinktree() shouldn't miss out files
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/lic-checksum.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/lic_checksum.py
similarity index 87%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/lic-checksum.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/lic_checksum.py
index 2e81373..3740715 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/lic-checksum.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/lic_checksum.py
@@ -1,16 +1,16 @@
 import os
 import tempfile
 
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import bitbake
 from oeqa.utils import CommandError
-from oeqa.utils.decorators import testcase
+from oeqa.core.decorator.oeid import OETestID
 
-class LicenseTests(oeSelfTest):
+class LicenseTests(OESelftestTestCase):
 
     # Verify that changing a license file that has an absolute path causes
     # the license qa to fail due to a mismatched md5sum.
-    @testcase(1197)
+    @OETestID(1197)
     def test_nonmatching_checksum(self):
         bitbake_cmd = '-c populate_lic emptytest'
         error_msg = 'emptytest: The new md5 checksum is 8d777f385d3dfec8815d20f7496026dc'
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/manifest.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/manifest.py
similarity index 80%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/manifest.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/manifest.py
index fe6f949..1460719 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/manifest.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/manifest.py
@@ -1,9 +1,8 @@
-import unittest
 import os
 
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import get_bb_var, get_bb_vars, bitbake
-from oeqa.utils.decorators import testcase
+from oeqa.core.decorator.oeid import OETestID
 
 class ManifestEntry:
     '''A manifest item of a collection able to list missing packages'''
@@ -11,7 +10,7 @@
         self.file = entry
         self.missing = []
 
-class VerifyManifest(oeSelfTest):
+class VerifyManifest(OESelftestTestCase):
     '''Tests for the manifest files and contents of an image'''
 
     @classmethod
@@ -21,14 +20,14 @@
             with open(manifest, "r") as mfile:
                 for line in mfile:
                     manifest_entry = os.path.join(path, line.split()[0])
-                    self.log.debug("{}: looking for {}"\
+                    self.logger.debug("{}: looking for {}"\
                                     .format(self.classname, manifest_entry))
                     if not os.path.isfile(manifest_entry):
                         manifest_errors.append(manifest_entry)
-                        self.log.debug("{}: {} not found"\
+                        self.logger.debug("{}: {} not found"\
                                     .format(self.classname, manifest_entry))
         except OSError as e:
-            self.log.debug("{}: checking of {} failed"\
+            self.logger.debug("{}: checking of {} failed"\
                     .format(self.classname, manifest))
             raise e
 
@@ -40,7 +39,7 @@
         target == self.buildtarget if target == None else target
         directory = get_bb_var(bb_var, target);
         if not directory or not os.path.isdir(directory):
-            self.log.debug("{}: {} points to {} when target = {}"\
+            self.logger.debug("{}: {} points to {} when target = {}"\
                     .format(self.classname, bb_var, directory, target))
             raise OSError
         return directory
@@ -48,18 +47,19 @@
     @classmethod
     def setUpClass(self):
 
+        super(VerifyManifest, self).setUpClass()
         self.buildtarget = 'core-image-minimal'
         self.classname = 'VerifyManifest'
 
-        self.log.info("{}: doing bitbake {} as a prerequisite of the test"\
+        self.logger.info("{}: doing bitbake {} as a prerequisite of the test"\
                 .format(self.classname, self.buildtarget))
         if bitbake(self.buildtarget).status:
-            self.log.debug("{} Failed to setup {}"\
+            self.logger.debug("{} Failed to setup {}"\
                     .format(self.classname, self.buildtarget))
-            unittest.SkipTest("{}: Cannot setup testing scenario"\
+            self.skipTest("{}: Cannot setup testing scenario"\
                     .format(self.classname))
 
-    @testcase(1380)
+    @OETestID(1380)
     def test_SDK_manifest_entries(self):
         '''Verifying the SDK manifest entries exist, this may take a build'''
 
@@ -67,12 +67,12 @@
         # to do an additional setup for the sdk
         sdktask = '-c populate_sdk'
         bbargs = sdktask + ' ' + self.buildtarget
-        self.log.debug("{}: doing bitbake {} as a prerequisite of the test"\
+        self.logger.debug("{}: doing bitbake {} as a prerequisite of the test"\
                 .format(self.classname, bbargs))
         if bitbake(bbargs).status:
-            self.log.debug("{} Failed to bitbake {}"\
+            self.logger.debug("{} Failed to bitbake {}"\
                     .format(self.classname, bbargs))
-            unittest.SkipTest("{}: Cannot setup testing scenario"\
+            self.skipTest("{}: Cannot setup testing scenario"\
                     .format(self.classname))
 
 
@@ -91,7 +91,7 @@
                         k)
                 mpath[k] = os.path.join(mdir, mfilename[k])
                 if not os.path.isfile(mpath[k]):
-                    self.log.debug("{}: {} does not exist".format(
+                    self.logger.debug("{}: {} does not exist".format(
                         self.classname, mpath[k]))
                     raise IOError
                 m_entry[k] = ManifestEntry(mpath[k])
@@ -101,11 +101,11 @@
                 reverse_dir[k] = os.path.join(pkgdata_dir[k],
                         'runtime-reverse')
                 if not os.path.exists(reverse_dir[k]):
-                    self.log.debug("{}: {} does not exist".format(
+                    self.logger.debug("{}: {} does not exist".format(
                         self.classname, reverse_dir[k]))
                     raise IOError
         except OSError:
-            raise unittest.SkipTest("{}: Error in obtaining manifest dirs"\
+            raise self.skipTest("{}: Error in obtaining manifest dirs"\
                 .format(self.classname))
         except IOError:
             msg = "{}: Error cannot find manifests in the specified dir:\n{}"\
@@ -113,7 +113,7 @@
             self.fail(msg)
 
         for k in d_target.keys():
-            self.log.debug("{}: Check manifest {}".format(
+            self.logger.debug("{}: Check manifest {}".format(
                 self.classname, m_entry[k].file))
 
             m_entry[k].missing = self.check_manifest_entries(\
@@ -122,11 +122,11 @@
                 msg = '{}: {} Error has the following missing entries'\
                         .format(self.classname, m_entry[k].file)
                 logmsg = msg+':\n'+'\n'.join(m_entry[k].missing)
-                self.log.debug(logmsg)
-                self.log.info(msg)
+                self.logger.debug(logmsg)
+                self.logger.info(msg)
                 self.fail(logmsg)
 
-    @testcase(1381)
+    @OETestID(1381)
     def test_image_manifest_entries(self):
         '''Verifying the image manifest entries exist'''
 
@@ -146,14 +146,14 @@
             revdir = os.path.join(pkgdata_dir, 'runtime-reverse')
             if not os.path.exists(revdir): raise IOError
         except OSError:
-            raise unittest.SkipTest("{}: Error in obtaining manifest dirs"\
+            raise self.skipTest("{}: Error in obtaining manifest dirs"\
                 .format(self.classname))
         except IOError:
             msg = "{}: Error cannot find manifests in dir:\n{}"\
                     .format(self.classname, mdir)
             self.fail(msg)
 
-        self.log.debug("{}: Check manifest {}"\
+        self.logger.debug("{}: Check manifest {}"\
                             .format(self.classname, m_entry.file))
         m_entry.missing = self.check_manifest_entries(\
                                                     m_entry.file, revdir)
@@ -161,6 +161,6 @@
             msg = '{}: {} Error has the following missing entries'\
                     .format(self.classname, m_entry.file)
             logmsg = msg+':\n'+'\n'.join(m_entry.missing)
-            self.log.debug(logmsg)
-            self.log.info(msg)
+            self.logger.debug(logmsg)
+            self.logger.info(msg)
             self.fail(logmsg)
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/__init__.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/__init__.py
similarity index 100%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/__init__.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/__init__.py
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/buildhistory.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/buildhistory.py
similarity index 83%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/buildhistory.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/buildhistory.py
index 5ed4b02..08675fd 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/buildhistory.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/buildhistory.py
@@ -1,18 +1,22 @@
 import os
-import unittest
+from oeqa.selftest.case import OESelftestTestCase
 import tempfile
-from git import Repo
 from oeqa.utils.commands import get_bb_var
-from oe.buildhistory_analysis import blob_to_dict, compare_dict_blobs
+from oeqa.core.decorator.oeid import OETestID
 
-class TestBlobParsing(unittest.TestCase):
+class TestBlobParsing(OESelftestTestCase):
 
     def setUp(self):
         import time
         self.repo_path = tempfile.mkdtemp(prefix='selftest-buildhistory',
             dir=get_bb_var('TOPDIR'))
 
-        self.repo = Repo.init(self.repo_path)
+        try:
+            from git import Repo
+            self.repo = Repo.init(self.repo_path)
+        except ImportError:
+            self.skipTest('Python module GitPython is not present')
+
         self.test_file = "test"
         self.var_map = {}
 
@@ -36,10 +40,12 @@
         self.repo.git.add("--all")
         self.repo.git.commit(message=msg)
 
+    @OETestID(1859)
     def test_blob_to_dict(self):
         """
         Test convertion of git blobs to dictionary
         """
+        from oe.buildhistory_analysis import blob_to_dict
         valuesmap = { "foo" : "1", "bar" : "2" }
         self.commit_vars(to_add = valuesmap)
 
@@ -47,10 +53,13 @@
         self.assertEqual(valuesmap, blob_to_dict(blob),
             "commit was not translated correctly to dictionary")
 
+    @OETestID(1860)
     def test_compare_dict_blobs(self):
         """
         Test comparisson of dictionaries extracted from git blobs
         """
+        from oe.buildhistory_analysis import compare_dict_blobs
+
         changesmap = { "foo-2" : ("2", "8"), "bar" : ("","4"), "bar-2" : ("","5")}
 
         self.commit_vars(to_add = { "foo" : "1", "foo-2" : "2", "foo-3" : "3" })
@@ -65,10 +74,12 @@
         var_changes = { x.fieldname : (x.oldvalue, x.newvalue) for x in change_records}
         self.assertEqual(changesmap, var_changes, "Changes not reported correctly")
 
+    @OETestID(1861)
     def test_compare_dict_blobs_default(self):
         """
         Test default values for comparisson of git blob dictionaries
         """
+        from oe.buildhistory_analysis import compare_dict_blobs
         defaultmap = { x : ("default", "1")  for x in ["PKG", "PKGE", "PKGV", "PKGR"]}
 
         self.commit_vars(to_add = { "foo" : "1" })
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/elf.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/elf.py
similarity index 94%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/elf.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/elf.py
index 1f59037..74ee6a1 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/elf.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/elf.py
@@ -1,7 +1,7 @@
-import unittest
+from unittest.case import TestCase
 import oe.qa
 
-class TestElf(unittest.TestCase):
+class TestElf(TestCase):
     def test_machine_name(self):
         """
         Test elf_machine_to_string()
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/license.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/license.py
new file mode 100644
index 0000000..d7f91fb
--- /dev/null
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/license.py
@@ -0,0 +1,99 @@
+from unittest.case import TestCase
+import oe.license
+
+class SeenVisitor(oe.license.LicenseVisitor):
+    def __init__(self):
+        self.seen = []
+        oe.license.LicenseVisitor.__init__(self)
+
+    def visit_Str(self, node):
+        self.seen.append(node.s)
+
+class TestSingleLicense(TestCase):
+    licenses = [
+        "GPLv2",
+        "LGPL-2.0",
+        "Artistic",
+        "MIT",
+        "GPLv3+",
+        "FOO_BAR",
+    ]
+    invalid_licenses = ["GPL/BSD"]
+
+    @staticmethod
+    def parse(licensestr):
+        visitor = SeenVisitor()
+        visitor.visit_string(licensestr)
+        return visitor.seen
+
+    def test_single_licenses(self):
+        for license in self.licenses:
+            licenses = self.parse(license)
+            self.assertListEqual(licenses, [license])
+
+    def test_invalid_licenses(self):
+        for license in self.invalid_licenses:
+            with self.assertRaises(oe.license.InvalidLicense) as cm:
+                self.parse(license)
+            self.assertEqual(cm.exception.license, license)
+
+class TestSimpleCombinations(TestCase):
+    tests = {
+        "FOO&BAR": ["FOO", "BAR"],
+        "BAZ & MOO": ["BAZ", "MOO"],
+        "ALPHA|BETA": ["ALPHA"],
+        "BAZ&MOO|FOO": ["FOO"],
+        "FOO&BAR|BAZ": ["FOO", "BAR"],
+    }
+    preferred = ["ALPHA", "FOO", "BAR"]
+
+    def test_tests(self):
+        def choose(a, b):
+            if all(lic in self.preferred for lic in b):
+                return b
+            else:
+                return a
+
+        for license, expected in self.tests.items():
+            licenses = oe.license.flattened_licenses(license, choose)
+            self.assertListEqual(licenses, expected)
+
+class TestComplexCombinations(TestSimpleCombinations):
+    tests = {
+        "FOO & (BAR | BAZ)&MOO": ["FOO", "BAR", "MOO"],
+        "(ALPHA|(BETA&THETA)|OMEGA)&DELTA": ["OMEGA", "DELTA"],
+        "((ALPHA|BETA)&FOO)|BAZ": ["BETA", "FOO"],
+        "(GPL-2.0|Proprietary)&BSD-4-clause&MIT": ["GPL-2.0", "BSD-4-clause", "MIT"],
+    }
+    preferred = ["BAR", "OMEGA", "BETA", "GPL-2.0"]
+
+class TestIsIncluded(TestCase):
+    tests = {
+        ("FOO | BAR", None, None):
+            [True, ["FOO"]],
+        ("FOO | BAR", None, "FOO"):
+            [True, ["BAR"]],
+        ("FOO | BAR", "BAR", None):
+            [True, ["BAR"]],
+        ("FOO | BAR & FOOBAR", "*BAR", None):
+            [True, ["BAR", "FOOBAR"]],
+        ("FOO | BAR & FOOBAR", None, "FOO*"):
+            [False, ["FOOBAR"]],
+        ("(FOO | BAR) & FOOBAR | BARFOO", None, "FOO"):
+            [True, ["BAR", "FOOBAR"]],
+        ("(FOO | BAR) & FOOBAR | BAZ & MOO & BARFOO", None, "FOO"):
+            [True, ["BAZ", "MOO", "BARFOO"]],
+        ("GPL-3.0 & GPL-2.0 & LGPL-2.1 | Proprietary", None, None):
+            [True, ["GPL-3.0", "GPL-2.0", "LGPL-2.1"]],
+        ("GPL-3.0 & GPL-2.0 & LGPL-2.1 | Proprietary", None, "GPL-3.0"):
+            [True, ["Proprietary"]],
+        ("GPL-3.0 & GPL-2.0 & LGPL-2.1 | Proprietary", None, "GPL-3.0 Proprietary"):
+            [False, ["GPL-3.0"]]
+    }
+
+    def test_tests(self):
+        for args, expected in self.tests.items():
+            is_included, licenses = oe.license.is_included(
+                args[0], (args[1] or '').split(), (args[2] or '').split())
+            self.assertEqual(is_included, expected[0])
+            self.assertListEqual(licenses, expected[1])
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/path.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/path.py
similarity index 97%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/path.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/path.py
index 44d0681..75a27c0 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/path.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/path.py
@@ -1,11 +1,11 @@
-import unittest
+from unittest.case import TestCase
 import oe, oe.path
 import tempfile
 import os
 import errno
 import shutil
 
-class TestRealPath(unittest.TestCase):
+class TestRealPath(TestCase):
     DIRS = [ "a", "b", "etc", "sbin", "usr", "usr/bin", "usr/binX", "usr/sbin", "usr/include", "usr/include/gdbm" ]
     FILES = [ "etc/passwd", "b/file" ]
     LINKS = [
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/types.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/types.py
similarity index 95%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/types.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/types.py
index 4fe2746..6b53aa6 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/types.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/types.py
@@ -1,7 +1,7 @@
-import unittest
+from unittest.case import TestCase
 from oe.maketype import create
 
-class TestBooleanType(unittest.TestCase):
+class TestBooleanType(TestCase):
     def test_invalid(self):
         self.assertRaises(ValueError, create, '', 'boolean')
         self.assertRaises(ValueError, create, 'foo', 'boolean')
@@ -31,7 +31,7 @@
         self.assertEqual(create('y', 'boolean'), True)
         self.assertNotEqual(create('y', 'boolean'), False)
 
-class TestList(unittest.TestCase):
+class TestList(TestCase):
     def assertListEqual(self, value, valid, sep=None):
         obj = create(value, 'list', separator=sep)
         self.assertEqual(obj, valid)
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/utils.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/utils.py
similarity index 93%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/utils.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/utils.py
index 7deb10f..9fb6c15 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/utils.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oelib/utils.py
@@ -1,7 +1,7 @@
-import unittest
+from unittest.case import TestCase
 from oe.utils import packages_filter_out_system, trim_version
 
-class TestPackagesFilterOutSystem(unittest.TestCase):
+class TestPackagesFilterOutSystem(TestCase):
     def test_filter(self):
         """
         Test that oe.utils.packages_filter_out_system works.
@@ -31,7 +31,7 @@
         self.assertEqual(pkgs, ["foo-data"])
 
 
-class TestTrimVersion(unittest.TestCase):
+class TestTrimVersion(TestCase):
     def test_version_exception(self):
         with self.assertRaises(TypeError):
             trim_version(None, 2)
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oescripts.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oescripts.py
similarity index 72%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/oescripts.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oescripts.py
index 29547f5..1ee7537 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oescripts.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/oescripts.py
@@ -1,18 +1,11 @@
-import datetime
-import unittest
-import os
-import re
-import shutil
-
-import oeqa.utils.ftools as ftools
-from oeqa.selftest.base import oeSelfTest
-from oeqa.selftest.buildhistory import BuildhistoryBase
+from oeqa.selftest.case import OESelftestTestCase
+from oeqa.selftest.cases.buildhistory import BuildhistoryBase
 from oeqa.utils.commands import Command, runCmd, bitbake, get_bb_var, get_test_layer
-from oeqa.utils.decorators import testcase
+from oeqa.core.decorator.oeid import OETestID
 
 class BuildhistoryDiffTests(BuildhistoryBase):
 
-    @testcase(295)
+    @OETestID(295)
     def test_buildhistory_diff(self):
         target = 'xcursor-transparent-theme'
         self.run_buildhistory_operation(target, target_config="PR = \"r1\"", change_bh_location=True)
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/package.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/package.py
new file mode 100644
index 0000000..169698f
--- /dev/null
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/package.py
@@ -0,0 +1,86 @@
+from oeqa.selftest.case import OESelftestTestCase
+from oeqa.core.decorator.oeid import OETestID
+from oeqa.utils.commands import bitbake, get_bb_vars
+import subprocess, os
+import oe.path
+
+class VersionOrdering(OESelftestTestCase):
+    # version1, version2, sort order
+    tests = (
+        ("1.0", "1.0", 0),
+        ("1.0", "2.0", -1),
+        ("2.0", "1.0", 1),
+        ("2.0-rc", "2.0", 1),
+        ("2.0~rc", "2.0", -1),
+        ("1.2rc2", "1.2.0", -1)
+        )
+
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+
+        # Build the tools we need and populate a sysroot
+        bitbake("dpkg-native opkg-native rpm-native python3-native")
+        bitbake("build-sysroots -c build_native_sysroot")
+
+        # Get the paths so we can point into the sysroot correctly
+        vars = get_bb_vars(["STAGING_DIR", "BUILD_ARCH", "bindir_native", "libdir_native"])
+        cls.staging = oe.path.join(vars["STAGING_DIR"], vars["BUILD_ARCH"])
+        cls.bindir = oe.path.join(cls.staging, vars["bindir_native"])
+        cls.libdir = oe.path.join(cls.staging, vars["libdir_native"])
+
+    def setUp(self):
+        # Just for convenience
+        self.staging = type(self).staging
+        self.bindir = type(self).bindir
+        self.libdir = type(self).libdir
+
+    @OETestID(1880)
+    def test_dpkg(self):
+        for ver1, ver2, sort in self.tests:
+            op = { -1: "<<", 0: "=", 1: ">>" }[sort]
+            status = subprocess.call((oe.path.join(self.bindir, "dpkg"), "--compare-versions", ver1, op, ver2))
+            self.assertEqual(status, 0, "%s %s %s failed" % (ver1, op, ver2))
+
+            # Now do it again but with incorrect operations
+            op = { -1: ">>", 0: ">>", 1: "<<" }[sort]
+            status = subprocess.call((oe.path.join(self.bindir, "dpkg"), "--compare-versions", ver1, op, ver2))
+            self.assertNotEqual(status, 0, "%s %s %s failed" % (ver1, op, ver2))
+
+            # Now do it again but with incorrect operations
+            op = { -1: "=", 0: "<<", 1: "=" }[sort]
+            status = subprocess.call((oe.path.join(self.bindir, "dpkg"), "--compare-versions", ver1, op, ver2))
+            self.assertNotEqual(status, 0, "%s %s %s failed" % (ver1, op, ver2))
+
+    @OETestID(1881)
+    def test_opkg(self):
+        for ver1, ver2, sort in self.tests:
+            op = { -1: "<<", 0: "=", 1: ">>" }[sort]
+            status = subprocess.call((oe.path.join(self.bindir, "opkg"), "compare-versions", ver1, op, ver2))
+            self.assertEqual(status, 0, "%s %s %s failed" % (ver1, op, ver2))
+
+            # Now do it again but with incorrect operations
+            op = { -1: ">>", 0: ">>", 1: "<<" }[sort]
+            status = subprocess.call((oe.path.join(self.bindir, "opkg"), "compare-versions", ver1, op, ver2))
+            self.assertNotEqual(status, 0, "%s %s %s failed" % (ver1, op, ver2))
+
+            # Now do it again but with incorrect operations
+            op = { -1: "=", 0: "<<", 1: "=" }[sort]
+            status = subprocess.call((oe.path.join(self.bindir, "opkg"), "compare-versions", ver1, op, ver2))
+            self.assertNotEqual(status, 0, "%s %s %s failed" % (ver1, op, ver2))
+
+    @OETestID(1882)
+    def test_rpm(self):
+        # Need to tell the Python bindings where to find its configuration
+        env = os.environ.copy()
+        env["RPM_CONFIGDIR"] = oe.path.join(self.libdir, "rpm")
+
+        for ver1, ver2, sort in self.tests:
+            # The only way to test rpm is via the Python module, so we need to
+            # execute python3-native.  labelCompare returns -1/0/1 (like strcmp)
+            # so add 100 and use that as the exit code.
+            command = (oe.path.join(self.bindir, "python3-native", "python3"), "-c",
+                       "import sys, rpm; v1=(None, \"%s\", None); v2=(None, \"%s\", None); sys.exit(rpm.labelCompare(v1, v2) + 100)" % (ver1, ver2))
+            status = subprocess.call(command, env=env)
+            self.assertIn(status, (99, 100, 101))
+            self.assertEqual(status - 100, sort, "%s %s (%d) failed" % (ver1, ver2, sort))
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/pkgdata.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/pkgdata.py
similarity index 96%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/pkgdata.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/pkgdata.py
index d69c3c8..0b4caf1 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/pkgdata.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/pkgdata.py
@@ -1,24 +1,21 @@
-import unittest
 import os
 import tempfile
-import logging
 import fnmatch
 
-import oeqa.utils.ftools as ftools
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars
-from oeqa.utils.decorators import testcase
+from oeqa.core.decorator.oeid import OETestID
 
-class OePkgdataUtilTests(oeSelfTest):
+class OePkgdataUtilTests(OESelftestTestCase):
 
     @classmethod
     def setUpClass(cls):
+        super(OePkgdataUtilTests, cls).setUpClass()
         # Ensure we have the right data in pkgdata
-        logger = logging.getLogger("selftest")
-        logger.info('Running bitbake to generate pkgdata')
+        cls.logger.info('Running bitbake to generate pkgdata')
         bitbake('busybox zlib m4')
 
-    @testcase(1203)
+    @OETestID(1203)
     def test_lookup_pkg(self):
         # Forward tests
         result = runCmd('oe-pkgdata-util lookup-pkg "zlib busybox"')
@@ -37,7 +34,7 @@
         self.assertEqual(result.status, 1, "Status different than 1. output: %s" % result.output)
         self.assertEqual(result.output, 'ERROR: The following packages could not be found: nonexistentpkg')
 
-    @testcase(1205)
+    @OETestID(1205)
     def test_read_value(self):
         result = runCmd('oe-pkgdata-util read-value PN libz1')
         self.assertEqual(result.output, 'zlib')
@@ -47,7 +44,7 @@
         pkgsize = int(result.output.strip())
         self.assertGreater(pkgsize, 1, "Size should be greater than 1. %s" % result.output)
 
-    @testcase(1198)
+    @OETestID(1198)
     def test_find_path(self):
         result = runCmd('oe-pkgdata-util find-path /lib/libz.so.1')
         self.assertEqual(result.output, 'zlib: /lib/libz.so.1')
@@ -57,7 +54,7 @@
         self.assertEqual(result.status, 1, "Status different than 1. output: %s" % result.output)
         self.assertEqual(result.output, 'ERROR: Unable to find any package producing path /not/exist')
 
-    @testcase(1204)
+    @OETestID(1204)
     def test_lookup_recipe(self):
         result = runCmd('oe-pkgdata-util lookup-recipe "libz-staticdev busybox"')
         self.assertEqual(result.output, 'zlib\nbusybox')
@@ -67,7 +64,7 @@
         self.assertEqual(result.status, 1, "Status different than 1. output: %s" % result.output)
         self.assertEqual(result.output, 'ERROR: The following packages could not be found: nonexistentpkg')
 
-    @testcase(1202)
+    @OETestID(1202)
     def test_list_pkgs(self):
         # No arguments
         result = runCmd('oe-pkgdata-util list-pkgs')
@@ -111,7 +108,7 @@
         pkglist = sorted(result.output.split())
         self.assertEqual(pkglist, ['libz-dbg', 'libz-dev', 'libz-doc'], "Packages listed: %s" % result.output)
 
-    @testcase(1201)
+    @OETestID(1201)
     def test_list_pkg_files(self):
         def splitoutput(output):
             files = {}
@@ -201,7 +198,7 @@
         self.assertIn(os.path.join(mandir, 'man3/zlib.3'), files['libz-doc'])
         self.assertIn(os.path.join(libdir, 'libz.a'), files['libz-staticdev'])
 
-    @testcase(1200)
+    @OETestID(1200)
     def test_glob(self):
         tempdir = tempfile.mkdtemp(prefix='pkgdataqa')
         self.track_for_cleanup(tempdir)
@@ -221,7 +218,7 @@
         self.assertNotIn('libz-dev', resultlist)
         self.assertNotIn('libz-dbg', resultlist)
 
-    @testcase(1206)
+    @OETestID(1206)
     def test_specify_pkgdatadir(self):
         result = runCmd('oe-pkgdata-util -p %s lookup-pkg zlib' % get_bb_var('PKGDATA_DIR'))
         self.assertEqual(result.output, 'libz1')
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/prservice.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/prservice.py
similarity index 92%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/prservice.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/prservice.py
index 34d4197..479e520 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/prservice.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/prservice.py
@@ -1,20 +1,19 @@
-import unittest
 import os
-import logging
 import re
 import shutil
 import datetime
 
 import oeqa.utils.ftools as ftools
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import runCmd, bitbake, get_bb_var
-from oeqa.utils.decorators import testcase
+from oeqa.core.decorator.oeid import OETestID
 from oeqa.utils.network import get_free_port
 
-class BitbakePrTests(oeSelfTest):
+class BitbakePrTests(OESelftestTestCase):
 
     @classmethod
     def setUpClass(cls):
+        super(BitbakePrTests, cls).setUpClass()
         cls.pkgdata_dir = get_bb_var('PKGDATA_DIR')
 
     def get_pr_version(self, package_name):
@@ -43,7 +42,6 @@
         res = bitbake(package_name, ignore_status=True)
         self.delete_recipeinc(package_name)
         self.assertEqual(res.status, 0, msg=res.output)
-        self.assertTrue("NOTE: Started PRServer with DBfile" in res.output, msg=res.output)
 
     def config_pr_tests(self, package_name, package_type='rpm', pr_socket='localhost:0'):
         config_package_data = 'PACKAGE_CLASSES = "package_%s"' % package_type
@@ -74,6 +72,7 @@
         exported_db_path = os.path.join(self.builddir, 'export.inc')
         export_result = runCmd("bitbake-prserv-tool export %s" % exported_db_path, ignore_status=True)
         self.assertEqual(export_result.status, 0, msg="PR Service database export failed: %s" % export_result.output)
+        self.assertTrue(os.path.exists(exported_db_path))
 
         if replace_current_db:
             current_db_path = os.path.join(get_bb_var('PERSISTENT_DIR'), 'prserv.sqlite3')
@@ -89,39 +88,39 @@
 
         self.assertTrue(pr_2 - pr_1 == 1, "Step between same pkg. revision is greater than 1")
 
-    @testcase(930)
+    @OETestID(930)
     def test_import_export_replace_db(self):
         self.run_test_pr_export_import('m4')
 
-    @testcase(931)
+    @OETestID(931)
     def test_import_export_override_db(self):
         self.run_test_pr_export_import('m4', replace_current_db=False)
 
-    @testcase(932)
+    @OETestID(932)
     def test_pr_service_rpm_arch_dep(self):
         self.run_test_pr_service('m4', 'rpm', 'do_package')
 
-    @testcase(934)
+    @OETestID(934)
     def test_pr_service_deb_arch_dep(self):
         self.run_test_pr_service('m4', 'deb', 'do_package')
 
-    @testcase(933)
+    @OETestID(933)
     def test_pr_service_ipk_arch_dep(self):
         self.run_test_pr_service('m4', 'ipk', 'do_package')
 
-    @testcase(935)
+    @OETestID(935)
     def test_pr_service_rpm_arch_indep(self):
         self.run_test_pr_service('xcursor-transparent-theme', 'rpm', 'do_package')
 
-    @testcase(937)
+    @OETestID(937)
     def test_pr_service_deb_arch_indep(self):
         self.run_test_pr_service('xcursor-transparent-theme', 'deb', 'do_package')
 
-    @testcase(936)
+    @OETestID(936)
     def test_pr_service_ipk_arch_indep(self):
         self.run_test_pr_service('xcursor-transparent-theme', 'ipk', 'do_package')
 
-    @testcase(1419)
+    @OETestID(1419)
     def test_stopping_prservice_message(self):
         port = get_free_port()
 
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/recipetool.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/recipetool.py
similarity index 96%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/recipetool.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/recipetool.py
index dc55a5e..754ea94 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/recipetool.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/recipetool.py
@@ -1,18 +1,15 @@
 import os
-import logging
 import shutil
 import tempfile
 import urllib.parse
 
 from oeqa.utils.commands import runCmd, bitbake, get_bb_var
 from oeqa.utils.commands import get_bb_vars, create_temp_layer
-from oeqa.utils.decorators import testcase
-from oeqa.selftest import devtool
-
+from oeqa.core.decorator.oeid import OETestID
+from oeqa.selftest.cases import devtool
 
 templayerdir = None
 
-
 def setUpModule():
     global templayerdir
     templayerdir = tempfile.mkdtemp(prefix='recipetoolqa')
@@ -28,6 +25,7 @@
 class RecipetoolBase(devtool.DevtoolBase):
 
     def setUpLocal(self):
+        super(RecipetoolBase, self).setUpLocal()
         self.templayerdir = templayerdir
         self.tempdir = tempfile.mkdtemp(prefix='recipetoolqa')
         self.track_for_cleanup(self.tempdir)
@@ -37,6 +35,7 @@
 
     def tearDownLocal(self):
         runCmd('rm -rf %s/recipes-*' % self.templayerdir)
+        super(RecipetoolBase, self).tearDownLocal()
 
     def _try_recipetool_appendcmd(self, cmd, testrecipe, expectedfiles, expectedlines=None):
         result = runCmd(cmd)
@@ -70,9 +69,9 @@
 
     @classmethod
     def setUpClass(cls):
+        super(RecipetoolTests, cls).setUpClass()
         # Ensure we have the right data in shlibs/pkgdata
-        logger = logging.getLogger("selftest")
-        logger.info('Running bitbake to generate pkgdata')
+        cls.logger.info('Running bitbake to generate pkgdata')
         bitbake('-c packagedata base-files coreutils busybox selftest-recipetool-appendfile')
         bb_vars = get_bb_vars(['COREBASE', 'BBPATH'])
         cls.corebase = bb_vars['COREBASE']
@@ -90,7 +89,7 @@
         for errorstr in checkerror:
             self.assertIn(errorstr, result.output)
 
-    @testcase(1177)
+    @OETestID(1177)
     def test_recipetool_appendfile_basic(self):
         # Basic test
         expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
@@ -98,14 +97,14 @@
         _, output = self._try_recipetool_appendfile('base-files', '/etc/motd', self.testfile, '', expectedlines, ['motd'])
         self.assertNotIn('WARNING: ', output)
 
-    @testcase(1183)
+    @OETestID(1183)
     def test_recipetool_appendfile_invalid(self):
         # Test some commands that should error
         self._try_recipetool_appendfile_fail('/etc/passwd', self.testfile, ['ERROR: /etc/passwd cannot be handled by this tool', 'useradd', 'extrausers'])
         self._try_recipetool_appendfile_fail('/etc/timestamp', self.testfile, ['ERROR: /etc/timestamp cannot be handled by this tool'])
         self._try_recipetool_appendfile_fail('/dev/console', self.testfile, ['ERROR: /dev/console cannot be handled by this tool'])
 
-    @testcase(1176)
+    @OETestID(1176)
     def test_recipetool_appendfile_alternatives(self):
         # Now try with a file we know should be an alternative
         # (this is very much a fake example, but one we know is reliably an alternative)
@@ -129,7 +128,7 @@
         result = runCmd('diff -q %s %s' % (testfile2, copiedfile), ignore_status=True)
         self.assertNotEqual(result.status, 0, 'New file should have been copied but was not %s' % result.output)
 
-    @testcase(1178)
+    @OETestID(1178)
     def test_recipetool_appendfile_binary(self):
         # Try appending a binary file
         # /bin/ls can be a symlink to /usr/bin/ls
@@ -138,7 +137,7 @@
         self.assertIn('WARNING: ', result.output)
         self.assertIn('is a binary', result.output)
 
-    @testcase(1173)
+    @OETestID(1173)
     def test_recipetool_appendfile_add(self):
         # Try arbitrary file add to a recipe
         expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
@@ -167,7 +166,7 @@
                          '}\n']
         self._try_recipetool_appendfile('netbase', '/usr/share/scriptname', testfile2, '-r netbase', expectedlines, ['testfile', testfile2name])
 
-    @testcase(1174)
+    @OETestID(1174)
     def test_recipetool_appendfile_add_bindir(self):
         # Try arbitrary file add to a recipe, this time to a location such that should be installed as executable
         expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
@@ -181,7 +180,7 @@
         _, output = self._try_recipetool_appendfile('netbase', '/usr/bin/selftest-recipetool-testbin', self.testfile, '-r netbase', expectedlines, ['testfile'])
         self.assertNotIn('WARNING: ', output)
 
-    @testcase(1175)
+    @OETestID(1175)
     def test_recipetool_appendfile_add_machine(self):
         # Try arbitrary file add to a recipe, this time to a location such that should be installed as executable
         expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
@@ -197,7 +196,7 @@
         _, output = self._try_recipetool_appendfile('netbase', '/usr/share/something', self.testfile, '-r netbase -m mymachine', expectedlines, ['mymachine/testfile'])
         self.assertNotIn('WARNING: ', output)
 
-    @testcase(1184)
+    @OETestID(1184)
     def test_recipetool_appendfile_orig(self):
         # A file that's in SRC_URI and in do_install with the same name
         expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
@@ -205,7 +204,7 @@
         _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-orig', self.testfile, '', expectedlines, ['selftest-replaceme-orig'])
         self.assertNotIn('WARNING: ', output)
 
-    @testcase(1191)
+    @OETestID(1191)
     def test_recipetool_appendfile_todir(self):
         # A file that's in SRC_URI and in do_install with destination directory rather than file
         expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
@@ -213,7 +212,7 @@
         _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-todir', self.testfile, '', expectedlines, ['selftest-replaceme-todir'])
         self.assertNotIn('WARNING: ', output)
 
-    @testcase(1187)
+    @OETestID(1187)
     def test_recipetool_appendfile_renamed(self):
         # A file that's in SRC_URI with a different name to the destination file
         expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
@@ -221,7 +220,7 @@
         _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-renamed', self.testfile, '', expectedlines, ['file1'])
         self.assertNotIn('WARNING: ', output)
 
-    @testcase(1190)
+    @OETestID(1190)
     def test_recipetool_appendfile_subdir(self):
         # A file that's in SRC_URI in a subdir
         expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
@@ -235,7 +234,7 @@
         _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-subdir', self.testfile, '', expectedlines, ['testfile'])
         self.assertNotIn('WARNING: ', output)
 
-    @testcase(1189)
+    @OETestID(1189)
     def test_recipetool_appendfile_src_glob(self):
         # A file that's in SRC_URI as a glob
         expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
@@ -249,7 +248,7 @@
         _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-src-globfile', self.testfile, '', expectedlines, ['testfile'])
         self.assertNotIn('WARNING: ', output)
 
-    @testcase(1181)
+    @OETestID(1181)
     def test_recipetool_appendfile_inst_glob(self):
         # A file that's in do_install as a glob
         expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
@@ -257,7 +256,7 @@
         _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-inst-globfile', self.testfile, '', expectedlines, ['selftest-replaceme-inst-globfile'])
         self.assertNotIn('WARNING: ', output)
 
-    @testcase(1182)
+    @OETestID(1182)
     def test_recipetool_appendfile_inst_todir_glob(self):
         # A file that's in do_install as a glob with destination as a directory
         expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
@@ -265,7 +264,7 @@
         _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-inst-todir-globfile', self.testfile, '', expectedlines, ['selftest-replaceme-inst-todir-globfile'])
         self.assertNotIn('WARNING: ', output)
 
-    @testcase(1185)
+    @OETestID(1185)
     def test_recipetool_appendfile_patch(self):
         # A file that's added by a patch in SRC_URI
         expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
@@ -284,7 +283,7 @@
         else:
             self.fail('Patch warning not found in output:\n%s' % output)
 
-    @testcase(1188)
+    @OETestID(1188)
     def test_recipetool_appendfile_script(self):
         # Now, a file that's in SRC_URI but installed by a script (so no mention in do_install)
         expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
@@ -298,7 +297,7 @@
         _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-scripted', self.testfile, '', expectedlines, ['testfile'])
         self.assertNotIn('WARNING: ', output)
 
-    @testcase(1180)
+    @OETestID(1180)
     def test_recipetool_appendfile_inst_func(self):
         # A file that's installed from a function called by do_install
         expectedlines = ['FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"\n',
@@ -306,7 +305,7 @@
         _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-inst-func', self.testfile, '', expectedlines, ['selftest-replaceme-inst-func'])
         self.assertNotIn('WARNING: ', output)
 
-    @testcase(1186)
+    @OETestID(1186)
     def test_recipetool_appendfile_postinstall(self):
         # A file that's created by a postinstall script (and explicitly mentioned in it)
         # First try without specifying recipe
@@ -322,7 +321,7 @@
                          '}\n']
         _, output = self._try_recipetool_appendfile('selftest-recipetool-appendfile', '/usr/share/selftest-replaceme-postinst', self.testfile, '-r selftest-recipetool-appendfile', expectedlines, ['testfile'])
 
-    @testcase(1179)
+    @OETestID(1179)
     def test_recipetool_appendfile_extlayer(self):
         # Try creating a bbappend in a layer that's not in bblayers.conf and has a different structure
         exttemplayerdir = os.path.join(self.tempdir, 'extlayer')
@@ -338,7 +337,7 @@
                          'metadata/recipes/recipes-test/selftest-recipetool-appendfile/selftest-recipetool-appendfile/selftest-replaceme-orig']
         self.assertEqual(sorted(createdfiles), sorted(expectedfiles))
 
-    @testcase(1192)
+    @OETestID(1192)
     def test_recipetool_appendfile_wildcard(self):
 
         def try_appendfile_wc(options):
@@ -363,7 +362,7 @@
         filename = try_appendfile_wc('-w')
         self.assertEqual(filename, recipefn.split('_')[0] + '_%.bbappend')
 
-    @testcase(1193)
+    @OETestID(1193)
     def test_recipetool_create(self):
         # Try adding a recipe
         tempsrc = os.path.join(self.tempdir, 'srctree')
@@ -380,7 +379,7 @@
         checkvars['SRC_URI[sha256sum]'] = '2e6a401cac9024db2288297e3be1a8ab60e7401ba8e91225218aaf4a27e82a07'
         self._test_recipe_contents(recipefile, checkvars, [])
 
-    @testcase(1194)
+    @OETestID(1194)
     def test_recipetool_create_git(self):
         if 'x11' not in get_bb_var('DISTRO_FEATURES'):
             self.skipTest('Test requires x11 as distro feature')
@@ -403,7 +402,7 @@
         inherits = ['autotools', 'pkgconfig']
         self._test_recipe_contents(recipefile, checkvars, inherits)
 
-    @testcase(1392)
+    @OETestID(1392)
     def test_recipetool_create_simple(self):
         # Try adding a recipe
         temprecipe = os.path.join(self.tempdir, 'recipe')
@@ -426,7 +425,7 @@
         inherits = ['autotools']
         self._test_recipe_contents(os.path.join(temprecipe, dirlist[0]), checkvars, inherits)
 
-    @testcase(1418)
+    @OETestID(1418)
     def test_recipetool_create_cmake(self):
         # Try adding a recipe
         temprecipe = os.path.join(self.tempdir, 'recipe')
@@ -444,6 +443,7 @@
         inherits = ['cmake', 'python-dir', 'gettext', 'pkgconfig']
         self._test_recipe_contents(recipefile, checkvars, inherits)
 
+    @OETestID(1638)
     def test_recipetool_create_github(self):
         # Basic test to see if github URL mangling works
         temprecipe = os.path.join(self.tempdir, 'recipe')
@@ -458,6 +458,7 @@
         inherits = ['setuptools']
         self._test_recipe_contents(recipefile, checkvars, inherits)
 
+    @OETestID(1639)
     def test_recipetool_create_github_tarball(self):
         # Basic test to ensure github URL mangling doesn't apply to release tarballs
         temprecipe = os.path.join(self.tempdir, 'recipe')
@@ -473,6 +474,7 @@
         inherits = ['setuptools']
         self._test_recipe_contents(recipefile, checkvars, inherits)
 
+    @OETestID(1637)
     def test_recipetool_create_git_http(self):
         # Basic test to check http git URL mangling works
         temprecipe = os.path.join(self.tempdir, 'recipe')
@@ -500,6 +502,7 @@
             shutil.copy(srcfile, dstfile)
             self.track_for_cleanup(dstfile)
 
+    @OETestID(1640)
     def test_recipetool_load_plugin(self):
         """Test that recipetool loads only the first found plugin in BBPATH."""
 
@@ -621,11 +624,11 @@
 
 class RecipetoolAppendsrcTests(RecipetoolAppendsrcBase):
 
-    @testcase(1273)
+    @OETestID(1273)
     def test_recipetool_appendsrcfile_basic(self):
         self._test_appendsrcfile('base-files', 'a-file')
 
-    @testcase(1274)
+    @OETestID(1274)
     def test_recipetool_appendsrcfile_basic_wildcard(self):
         testrecipe = 'base-files'
         self._test_appendsrcfile(testrecipe, 'a-file', options='-w')
@@ -633,15 +636,15 @@
         bbappendfile = self._check_bbappend(testrecipe, recipefile, self.templayerdir)
         self.assertEqual(os.path.basename(bbappendfile), '%s_%%.bbappend' % testrecipe)
 
-    @testcase(1281)
+    @OETestID(1281)
     def test_recipetool_appendsrcfile_subdir_basic(self):
         self._test_appendsrcfile('base-files', 'a-file', 'tmp')
 
-    @testcase(1282)
+    @OETestID(1282)
     def test_recipetool_appendsrcfile_subdir_basic_dirdest(self):
         self._test_appendsrcfile('base-files', destdir='tmp')
 
-    @testcase(1280)
+    @OETestID(1280)
     def test_recipetool_appendsrcfile_srcdir_basic(self):
         testrecipe = 'bash'
         bb_vars = get_bb_vars(['S', 'WORKDIR'], testrecipe)
@@ -650,14 +653,14 @@
         subdir = os.path.relpath(srcdir, workdir)
         self._test_appendsrcfile(testrecipe, 'a-file', srcdir=subdir)
 
-    @testcase(1275)
+    @OETestID(1275)
     def test_recipetool_appendsrcfile_existing_in_src_uri(self):
         testrecipe = 'base-files'
         filepath = self._get_first_file_uri(testrecipe)
         self.assertTrue(filepath, 'Unable to test, no file:// uri found in SRC_URI for %s' % testrecipe)
         self._test_appendsrcfile(testrecipe, filepath, has_src_uri=False)
 
-    @testcase(1276)
+    @OETestID(1276)
     def test_recipetool_appendsrcfile_existing_in_src_uri_diff_params(self):
         testrecipe = 'base-files'
         subdir = 'tmp'
@@ -667,7 +670,7 @@
         output = self._test_appendsrcfile(testrecipe, filepath, subdir, has_src_uri=False)
         self.assertTrue(any('with different parameters' in l for l in output))
 
-    @testcase(1277)
+    @OETestID(1277)
     def test_recipetool_appendsrcfile_replace_file_srcdir(self):
         testrecipe = 'bash'
         filepath = 'Makefile.in'
@@ -680,7 +683,7 @@
         bitbake('%s:do_unpack' % testrecipe)
         self.assertEqual(open(self.testfile, 'r').read(), open(os.path.join(srcdir, filepath), 'r').read())
 
-    @testcase(1278)
+    @OETestID(1278)
     def test_recipetool_appendsrcfiles_basic(self, destdir=None):
         newfiles = [self.testfile]
         for i in range(1, 5):
@@ -690,6 +693,6 @@
             newfiles.append(testfile)
         self._test_appendsrcfiles('gcc', newfiles, destdir=destdir, options='-W')
 
-    @testcase(1279)
+    @OETestID(1279)
     def test_recipetool_appendsrcfiles_basic_subdir(self):
         self.test_recipetool_appendsrcfiles_basic(destdir='testdir')
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runcmd.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runcmd.py
new file mode 100644
index 0000000..d76d706
--- /dev/null
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runcmd.py
@@ -0,0 +1,134 @@
+from oeqa.selftest.case import OESelftestTestCase
+from oeqa.utils.commands import runCmd
+from oeqa.utils import CommandError
+from oeqa.core.decorator.oeid import OETestID
+
+import subprocess
+import threading
+import time
+import signal
+
+class MemLogger(object):
+    def __init__(self):
+        self.info_msgs = []
+        self.error_msgs = []
+
+    def info(self, msg):
+        self.info_msgs.append(msg)
+
+    def error(self, msg):
+        self.error_msgs.append(msg)
+
+class RunCmdTests(OESelftestTestCase):
+    """ Basic tests for runCmd() utility function """
+
+    # The delta is intentionally smaller than the timeout, to detect cases where
+    # we incorrectly apply the timeout more than once.
+    TIMEOUT = 2
+    DELTA = 1
+
+    @OETestID(1916)
+    def test_result_okay(self):
+        result = runCmd("true")
+        self.assertEqual(result.status, 0)
+
+    @OETestID(1915)
+    def test_result_false(self):
+        result = runCmd("false", ignore_status=True)
+        self.assertEqual(result.status, 1)
+
+    @OETestID(1917)
+    def test_shell(self):
+        # A shell is used for all string commands.
+        result = runCmd("false; true", ignore_status=True)
+        self.assertEqual(result.status, 0)
+
+    @OETestID(1910)
+    def test_no_shell(self):
+        self.assertRaises(FileNotFoundError,
+                          runCmd, "false; true", shell=False)
+
+    @OETestID(1906)
+    def test_list_not_found(self):
+        self.assertRaises(FileNotFoundError,
+                          runCmd, ["false; true"])
+
+    @OETestID(1907)
+    def test_list_okay(self):
+        result = runCmd(["true"])
+        self.assertEqual(result.status, 0)
+
+    @OETestID(1913)
+    def test_result_assertion(self):
+        self.assertRaisesRegexp(AssertionError, "Command 'echo .* false' returned non-zero exit status 1:\nfoobar",
+                                runCmd, "echo foobar >&2; false", shell=True)
+
+    @OETestID(1914)
+    def test_result_exception(self):
+        self.assertRaisesRegexp(CommandError, "Command 'echo .* false' returned non-zero exit status 1 with output: foobar",
+                                runCmd, "echo foobar >&2; false", shell=True, assert_error=False)
+
+    @OETestID(1911)
+    def test_output(self):
+        result = runCmd("echo stdout; echo stderr >&2", shell=True)
+        self.assertEqual("stdout\nstderr", result.output)
+        self.assertEqual("", result.error)
+
+    @OETestID(1912)
+    def test_output_split(self):
+        result = runCmd("echo stdout; echo stderr >&2", shell=True, stderr=subprocess.PIPE)
+        self.assertEqual("stdout", result.output)
+        self.assertEqual("stderr", result.error)
+
+    @OETestID(1920)
+    def test_timeout(self):
+        numthreads = threading.active_count()
+        start = time.time()
+        # Killing a hanging process only works when not using a shell?!
+        result = runCmd(['sleep', '60'], timeout=self.TIMEOUT, ignore_status=True)
+        self.assertEqual(result.status, -signal.SIGTERM)
+        end = time.time()
+        self.assertLess(end - start, self.TIMEOUT + self.DELTA)
+        self.assertEqual(numthreads, threading.active_count())
+
+    @OETestID(1921)
+    def test_timeout_split(self):
+        numthreads = threading.active_count()
+        start = time.time()
+        # Killing a hanging process only works when not using a shell?!
+        result = runCmd(['sleep', '60'], timeout=self.TIMEOUT, ignore_status=True, stderr=subprocess.PIPE)
+        self.assertEqual(result.status, -signal.SIGTERM)
+        end = time.time()
+        self.assertLess(end - start, self.TIMEOUT + self.DELTA)
+        self.assertEqual(numthreads, threading.active_count())
+
+    @OETestID(1918)
+    def test_stdin(self):
+        numthreads = threading.active_count()
+        result = runCmd("cat", data=b"hello world", timeout=self.TIMEOUT)
+        self.assertEqual("hello world", result.output)
+        self.assertEqual(numthreads, threading.active_count())
+
+    @OETestID(1919)
+    def test_stdin_timeout(self):
+        numthreads = threading.active_count()
+        start = time.time()
+        result = runCmd(['sleep', '60'], data=b"hello world", timeout=self.TIMEOUT, ignore_status=True)
+        self.assertEqual(result.status, -signal.SIGTERM)
+        end = time.time()
+        self.assertLess(end - start, self.TIMEOUT + self.DELTA)
+        self.assertEqual(numthreads, threading.active_count())
+
+    @OETestID(1908)
+    def test_log(self):
+        log = MemLogger()
+        result = runCmd("echo stdout; echo stderr >&2", shell=True, output_log=log)
+        self.assertEqual(["Running: echo stdout; echo stderr >&2", "stdout", "stderr"], log.info_msgs)
+        self.assertEqual([], log.error_msgs)
+
+    @OETestID(1909)
+    def test_log_split(self):
+        log = MemLogger()
+        result = runCmd("echo stdout; echo stderr >&2", shell=True, output_log=log, stderr=subprocess.PIPE)
+        self.assertEqual(["Running: echo stdout; echo stderr >&2", "stdout"], log.info_msgs)
+        self.assertEqual(["stderr"], log.error_msgs)
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/runqemu.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runqemu.py
similarity index 84%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/runqemu.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runqemu.py
index 58c6f96..47d41f5 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/runqemu.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runqemu.py
@@ -3,28 +3,26 @@
 #
 
 import re
-import logging
 
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import bitbake, runqemu, get_bb_var
-from oeqa.utils.decorators import testcase
+from oeqa.core.decorator.oeid import OETestID
 
-class RunqemuTests(oeSelfTest):
+class RunqemuTests(OESelftestTestCase):
     """Runqemu test class"""
 
     image_is_ready = False
     deploy_dir_image = ''
+    # We only want to print runqemu stdout/stderr if there is a test case failure
+    buffer = True
 
     def setUpLocal(self):
+        super(RunqemuTests, self).setUpLocal()
         self.recipe = 'core-image-minimal'
         self.machine =  'qemux86-64'
-        self.fstypes = "ext4 iso hddimg vmdk qcow2 vdi"
+        self.fstypes = "ext4 iso hddimg wic.vmdk wic.qcow2 wic.vdi"
         self.cmd_common = "runqemu nographic"
 
-        # Avoid emit the same record multiple times.
-        mainlogger = logging.getLogger("BitBake.Main")
-        mainlogger.propagate = False
-
         self.write_config(
 """
 MACHINE = "%s"
@@ -40,14 +38,14 @@
             bitbake(self.recipe)
             RunqemuTests.image_is_ready = True
 
-    @testcase(2001)
+    @OETestID(2001)
     def test_boot_machine(self):
         """Test runqemu machine"""
         cmd = "%s %s" % (self.cmd_common, self.machine)
         with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu:
             self.assertTrue(qemu.runner.logged, "Failed: %s" % cmd)
 
-    @testcase(2002)
+    @OETestID(2002)
     def test_boot_machine_ext4(self):
         """Test runqemu machine ext4"""
         cmd = "%s %s ext4" % (self.cmd_common, self.machine)
@@ -55,45 +53,45 @@
             with open(qemu.qemurunnerlog) as f:
                 self.assertTrue('rootfs.ext4' in f.read(), "Failed: %s" % cmd)
 
-    @testcase(2003)
+    @OETestID(2003)
     def test_boot_machine_iso(self):
         """Test runqemu machine iso"""
         cmd = "%s %s iso" % (self.cmd_common, self.machine)
         with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu:
             with open(qemu.qemurunnerlog) as f:
-                self.assertTrue(' -cdrom ' in f.read(), "Failed: %s" % cmd)
+                self.assertTrue('media=cdrom' in f.read(), "Failed: %s" % cmd)
 
-    @testcase(2004)
+    @OETestID(2004)
     def test_boot_recipe_image(self):
         """Test runqemu recipe-image"""
         cmd = "%s %s" % (self.cmd_common, self.recipe)
         with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu:
             self.assertTrue(qemu.runner.logged, "Failed: %s" % cmd)
 
-    @testcase(2005)
+    @OETestID(2005)
     def test_boot_recipe_image_vmdk(self):
         """Test runqemu recipe-image vmdk"""
-        cmd = "%s %s vmdk" % (self.cmd_common, self.recipe)
+        cmd = "%s %s wic.vmdk" % (self.cmd_common, self.recipe)
         with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu:
             with open(qemu.qemurunnerlog) as f:
                 self.assertTrue('format=vmdk' in f.read(), "Failed: %s" % cmd)
 
-    @testcase(2006)
+    @OETestID(2006)
     def test_boot_recipe_image_vdi(self):
         """Test runqemu recipe-image vdi"""
-        cmd = "%s %s vdi" % (self.cmd_common, self.recipe)
+        cmd = "%s %s wic.vdi" % (self.cmd_common, self.recipe)
         with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu:
             with open(qemu.qemurunnerlog) as f:
                 self.assertTrue('format=vdi' in f.read(), "Failed: %s" % cmd)
 
-    @testcase(2007)
+    @OETestID(2007)
     def test_boot_deploy(self):
         """Test runqemu deploy_dir_image"""
         cmd = "%s %s" % (self.cmd_common, self.deploy_dir_image)
         with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu:
             self.assertTrue(qemu.runner.logged, "Failed: %s" % cmd)
 
-    @testcase(2008)
+    @OETestID(2008)
     def test_boot_deploy_hddimg(self):
         """Test runqemu deploy_dir_image hddimg"""
         cmd = "%s %s hddimg" % (self.cmd_common, self.deploy_dir_image)
@@ -101,7 +99,7 @@
             with open(qemu.qemurunnerlog) as f:
                 self.assertTrue(re.search('file=.*.hddimg', f.read()), "Failed: %s" % cmd)
 
-    @testcase(2009)
+    @OETestID(2009)
     def test_boot_machine_slirp(self):
         """Test runqemu machine slirp"""
         cmd = "%s slirp %s" % (self.cmd_common, self.machine)
@@ -109,15 +107,15 @@
             with open(qemu.qemurunnerlog) as f:
                 self.assertTrue(' -netdev user' in f.read(), "Failed: %s" % cmd)
 
-    @testcase(2009)
+    @OETestID(2009)
     def test_boot_machine_slirp_qcow2(self):
         """Test runqemu machine slirp qcow2"""
-        cmd = "%s slirp qcow2 %s" % (self.cmd_common, self.machine)
+        cmd = "%s slirp wic.qcow2 %s" % (self.cmd_common, self.machine)
         with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu:
             with open(qemu.qemurunnerlog) as f:
                 self.assertTrue('format=qcow2' in f.read(), "Failed: %s" % cmd)
 
-    @testcase(2010)
+    @OETestID(2010)
     def test_boot_qemu_boot(self):
         """Test runqemu /path/to/image.qemuboot.conf"""
         qemuboot_conf = "%s-%s.qemuboot.conf" % (self.recipe, self.machine)
@@ -128,7 +126,7 @@
         with runqemu(self.recipe, ssh=False, launch_cmd=cmd) as qemu:
             self.assertTrue(qemu.runner.logged, "Failed: %s" % cmd)
 
-    @testcase(2011)
+    @OETestID(2011)
     def test_boot_rootfs(self):
         """Test runqemu /path/to/rootfs.ext4"""
         rootfs = "%s-%s.ext4" % (self.recipe, self.machine)
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/runtime-test.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runtime_test.py
similarity index 70%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/runtime-test.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runtime_test.py
index e498d04..25270b7 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/runtime-test.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/runtime_test.py
@@ -1,15 +1,20 @@
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars, runqemu
-from oeqa.utils.decorators import testcase
+from oeqa.utils.sshcontrol import SSHControl
+from oeqa.core.decorator.oeid import OETestID
 import os
 import re
+import tempfile
+import shutil
 
-class TestExport(oeSelfTest):
+class TestExport(OESelftestTestCase):
 
     @classmethod
     def tearDownClass(cls):
         runCmd("rm -rf /tmp/sdk")
+        super(TestExport, cls).tearDownClass()
 
+    @OETestID(1499)
     def test_testexport_basic(self):
         """
         Summary: Check basic testexport functionality with only ping test enabled.
@@ -49,6 +54,7 @@
             # Verify ping test was succesful
             self.assertEqual(0, result.status, 'oe-test runtime returned a non 0 status')
 
+    @OETestID(1641)
     def test_testexport_sdk(self):
         """
         Summary: Check sdk functionality for testexport.
@@ -101,36 +107,66 @@
         self.assertEqual(0, result.status, "Couldn't run tar from SDK")
 
 
-class TestImage(oeSelfTest):
+class TestImage(OESelftestTestCase):
 
+    @OETestID(1644)
     def test_testimage_install(self):
         """
         Summary: Check install packages functionality for testimage/testexport.
         Expected: 1. Import tests from a directory other than meta.
                   2. Check install/uninstall of socat.
-                  3. Check that remote package feeds can be accessed
         Product: oe-core
         Author: Mariano Lopez <mariano.lopez@intel.com>
-        Author: Alexander Kanavin <alexander.kanavin@intel.com>
         """
         if get_bb_var('DISTRO') == 'poky-tiny':
             self.skipTest('core-image-full-cmdline not buildable for poky-tiny')
 
         features = 'INHERIT += "testimage"\n'
         features += 'TEST_SUITES = "ping ssh selftest"\n'
-        # We don't yet know what the server ip and port will be - they will be patched
-        # in at the start of the on-image test
-        features += 'PACKAGE_FEED_URIS = "http://bogus_ip:bogus_port"\n'
-        features += 'EXTRA_IMAGE_FEATURES += "package-management"\n'
-        features += 'PACKAGE_CLASSES = "package_rpm"'
         self.write_config(features)
 
         # Build core-image-sato and testimage
         bitbake('core-image-full-cmdline socat')
         bitbake('-c testimage core-image-full-cmdline')
 
-class Postinst(oeSelfTest):
-    @testcase(1540)
+    @OETestID(1883)
+    def test_testimage_dnf(self):
+        """
+        Summary: Check package feeds functionality for dnf
+        Expected: 1. Check that remote package feeds can be accessed
+        Product: oe-core
+        Author: Alexander Kanavin <alexander.kanavin@intel.com>
+        """
+        if get_bb_var('DISTRO') == 'poky-tiny':
+            self.skipTest('core-image-full-cmdline not buildable for poky-tiny')
+
+        features = 'INHERIT += "testimage"\n'
+        features += 'TEST_SUITES = "ping ssh dnf_runtime dnf.DnfBasicTest.test_dnf_help"\n'
+        # We don't yet know what the server ip and port will be - they will be patched
+        # in at the start of the on-image test
+        features += 'PACKAGE_FEED_URIS = "http://bogus_ip:bogus_port"\n'
+        features += 'EXTRA_IMAGE_FEATURES += "package-management"\n'
+        features += 'PACKAGE_CLASSES = "package_rpm"\n'
+
+        # Enable package feed signing
+        self.gpg_home = tempfile.mkdtemp(prefix="oeqa-feed-sign-")
+        signing_key_dir = os.path.join(self.testlayer_path, 'files', 'signing')
+        runCmd('gpg --batch --homedir %s --import %s' % (self.gpg_home, os.path.join(signing_key_dir, 'key.secret')))
+        features += 'INHERIT += "sign_package_feed"\n'
+        features += 'PACKAGE_FEED_GPG_NAME = "testuser"\n'
+        features += 'PACKAGE_FEED_GPG_PASSPHRASE_FILE = "%s"\n' % os.path.join(signing_key_dir, 'key.passphrase')
+        features += 'GPG_PATH = "%s"\n' % self.gpg_home
+        self.write_config(features)
+
+        # Build core-image-sato and testimage
+        bitbake('core-image-full-cmdline socat')
+        bitbake('-c testimage core-image-full-cmdline')
+
+        # remove the oeqa-feed-sign temporal directory
+        shutil.rmtree(self.gpg_home, ignore_errors=True)
+
+class Postinst(OESelftestTestCase):
+    @OETestID(1540)
     def test_verify_postinst(self):
         """
         Summary: The purpose of this test is to verify the execution order of postinst Bugzilla ID: [5319]
@@ -180,7 +216,7 @@
                         self.assertEqual(idx, len(postinst_list), "Not found all postinsts")
                         break
 
-    @testcase(1545)
+    @OETestID(1545)
     def test_postinst_rootfs_and_boot(self):
         """
         Summary:        The purpose of this test case is to verify Post-installation
@@ -202,36 +238,30 @@
         fileboot_name = "this-was-created-at-first-boot"
         rootfs_pkg = 'postinst-at-rootfs'
         boot_pkg = 'postinst-delayed-a'
-        #Step 1
-        features = 'MACHINE = "qemux86"\n'
-        features += 'CORE_IMAGE_EXTRA_INSTALL += "%s %s "\n'% (rootfs_pkg, boot_pkg)
-        features += 'IMAGE_FEATURES += "ssh-server-openssh"\n'
+
         for init_manager in ("sysvinit", "systemd"):
-            #for sysvinit no extra configuration is needed,
-            if (init_manager is "systemd"):
-                features += 'DISTRO_FEATURES_append = " systemd"\n'
-                features += 'VIRTUAL-RUNTIME_init_manager = "systemd"\n'
-                features += 'DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"\n'
-                features += 'VIRTUAL-RUNTIME_initscripts = ""\n'
-            for classes in ("package_rpm package_deb package_ipk",
-                            "package_deb package_rpm package_ipk",
-                            "package_ipk package_deb package_rpm"):
-                features += 'PACKAGE_CLASSES = "%s"\n' % classes
-                self.write_config(features)
+            for classes in ("package_rpm", "package_deb", "package_ipk"):
+                with self.subTest(init_manager=init_manager, package_class=classes):
+                    features = 'MACHINE = "qemux86"\n'
+                    features += 'CORE_IMAGE_EXTRA_INSTALL += "%s %s "\n'% (rootfs_pkg, boot_pkg)
+                    features += 'IMAGE_FEATURES += "package-management empty-root-password"\n'
+                    features += 'PACKAGE_CLASSES = "%s"\n' % classes
+                    if init_manager == "systemd":
+                        features += 'DISTRO_FEATURES_append = " systemd"\n'
+                        features += 'VIRTUAL-RUNTIME_init_manager = "systemd"\n'
+                        features += 'DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"\n'
+                        features += 'VIRTUAL-RUNTIME_initscripts = ""\n'
+                    self.write_config(features)
 
-                #Step 2
-                bitbake('core-image-minimal')
+                    bitbake('core-image-minimal')
 
-                #Step 3
-                file_rootfs_created = os.path.join(get_bb_var('IMAGE_ROOTFS',"core-image-minimal"),
-                                                   file_rootfs_name)
-                found = os.path.isfile(file_rootfs_created)
-                self.assertTrue(found, "File %s was not created at rootfs time by %s" % \
-                                (file_rootfs_name, rootfs_pkg))
+                    file_rootfs_created = os.path.join(get_bb_var('IMAGE_ROOTFS', "core-image-minimal"),
+                                                       file_rootfs_name)
+                    found = os.path.isfile(file_rootfs_created)
+                    self.assertTrue(found, "File %s was not created at rootfs time by %s" % \
+                                    (file_rootfs_name, rootfs_pkg))
 
-                #Step 4
-                testcommand = 'ls /etc/'+fileboot_name
-                with runqemu('core-image-minimal') as qemu:
-                    sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
-                    result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand))
-                    self.assertEqual(result.status, 0, 'File %s was not created at firts boot'% fileboot_name)
+                    testcommand = 'ls /etc/' + fileboot_name
+                    with runqemu('core-image-minimal') as qemu:
+                        status, output = qemu.run_serial("-f /etc/" + fileboot_name)
+                        self.assertEqual(status, 0, 'File %s was not created at first boot (%s)' % (fileboot_name, output))
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/selftest.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/selftest.py
new file mode 100644
index 0000000..4b3cb14
--- /dev/null
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/selftest.py
@@ -0,0 +1,51 @@
+import importlib
+from oeqa.utils.commands import runCmd
+import oeqa.selftest
+from oeqa.selftest.case import OESelftestTestCase
+from oeqa.core.decorator.oeid import OETestID
+
+class ExternalLayer(OESelftestTestCase):
+
+    @OETestID(1885)
+    def test_list_imported(self):
+        """
+        Summary: Checks functionality to import tests from other layers.
+        Expected: 1. File "external-layer.py" must be in
+        oeqa.selftest.__path__
+                  2. test_unconditional_pas method must exists
+                     in ImportedTests class
+        Product: oe-core
+        Author: Mariano Lopez <mariano.lopez@intel.com>
+        """
+
+        test_file = "external-layer.py"
+        test_module = "oeqa.selftest.cases.external-layer"
+        method_name = "test_unconditional_pass"
+
+        # Check if "external-layer.py" is in oeqa path
+        found_file = search_test_file(test_file)
+        self.assertTrue(found_file, msg="Can't find %s in the oeqa path" % test_file)
+
+        # Import oeqa.selftest.external-layer module and search for
+        # test_unconditional_pass method of ImportedTests class
+        found_method = search_method(test_module, method_name)
+        self.assertTrue(method_name, msg="Can't find %s method" % method_name)
+
+def search_test_file(file_name):
+    for layer_path in oeqa.selftest.__path__:
+        for _, _, files in os.walk(layer_path):
+            for f in files:
+                if f == file_name:
+                    return True
+    return False
+
+def search_method(module, method):
+    modlib = importlib.import_module(module)
+    for var in vars(modlib):
+        klass = vars(modlib)[var]
+        if isinstance(klass, type(OESelftestTestCase)) and issubclass(klass, OESelftestTestCase):
+            for m in dir(klass):
+                if m == method:
+                    return True
+    return False
+
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/signing.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/signing.py
similarity index 94%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/signing.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/signing.py
index 0ac3d1fa..b3d1a82 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/signing.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/signing.py
@@ -1,15 +1,15 @@
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars
 import os
 import glob
 import re
 import shutil
 import tempfile
-from oeqa.utils.decorators import testcase
+from oeqa.core.decorator.oeid import OETestID
 from oeqa.utils.ftools import write_file
 
 
-class Signing(oeSelfTest):
+class Signing(OESelftestTestCase):
 
     gpg_dir = ""
     pub_key_path = ""
@@ -17,19 +17,23 @@
 
     @classmethod
     def setUpClass(cls):
+        super(Signing, cls).setUpClass()
         # Check that we can find the gpg binary and fail early if we can't
         if not shutil.which("gpg"):
             raise AssertionError("This test needs GnuPG")
 
-        cls.gpg_home_dir = tempfile.TemporaryDirectory(prefix="oeqa-signing-")
-        cls.gpg_dir = cls.gpg_home_dir.name
+        cls.gpg_dir = tempfile.mkdtemp(prefix="oeqa-signing-")
 
         cls.pub_key_path = os.path.join(cls.testlayer_path, 'files', 'signing', "key.pub")
         cls.secret_key_path = os.path.join(cls.testlayer_path, 'files', 'signing', "key.secret")
 
         runCmd('gpg --batch --homedir %s --import %s %s' % (cls.gpg_dir, cls.pub_key_path, cls.secret_key_path))
 
-    @testcase(1362)
+    @classmethod
+    def tearDownClass(cls):
+        shutil.rmtree(cls.gpg_dir, ignore_errors=True)
+
+    @OETestID(1362)
     def test_signing_packages(self):
         """
         Summary:     Test that packages can be signed in the package feed
@@ -92,7 +96,7 @@
         bitbake('core-image-minimal')
 
 
-    @testcase(1382)
+    @OETestID(1382)
     def test_signing_sstate_archive(self):
         """
         Summary:     Test that sstate archives can be signed
@@ -135,9 +139,9 @@
         self.assertIn('gpg: Good signature from', ret.output, 'Package signed incorrectly.')
 
 
-class LockedSignatures(oeSelfTest):
+class LockedSignatures(OESelftestTestCase):
 
-    @testcase(1420)
+    @OETestID(1420)
     def test_locked_signatures(self):
         """
         Summary:     Test locked signature mechanism
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/sstate.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/sstate.py
similarity index 95%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/sstate.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/sstate.py
index f54bc41..bc2fdbd 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/sstate.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/sstate.py
@@ -5,13 +5,14 @@
 import shutil
 
 import oeqa.utils.ftools as ftools
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import runCmd, bitbake, get_bb_vars, get_test_layer
 
 
-class SStateBase(oeSelfTest):
+class SStateBase(OESelftestTestCase):
 
     def setUpLocal(self):
+        super(SStateBase, self).setUpLocal()
         self.temp_sstate_location = None
         needed_vars = ['SSTATE_DIR', 'NATIVELSBSTRING', 'TCLIBC', 'TUNE_ARCH',
                        'TOPDIR', 'TARGET_VENDOR', 'TARGET_OS']
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/sstatetests.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/sstatetests.py
similarity index 91%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/sstatetests.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/sstatetests.py
index e35ddff..4790088 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/sstatetests.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/sstatetests.py
@@ -1,16 +1,14 @@
-import datetime
-import unittest
 import os
-import re
 import shutil
 import glob
 import subprocess
 
-import oeqa.utils.ftools as ftools
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_test_layer
-from oeqa.selftest.sstate import SStateBase
-from oeqa.utils.decorators import testcase
+from oeqa.selftest.cases.sstate import SStateBase
+from oeqa.core.decorator.oeid import OETestID
+
+import bb.siggen
 
 class SStateTests(SStateBase):
 
@@ -39,19 +37,19 @@
         else:
             self.assertTrue(not file_tracker , msg="Found sstate files in the wrong place for: %s (found %s)" % (', '.join(map(str, targets)), str(file_tracker)))
 
-    @testcase(975)
+    @OETestID(975)
     def test_sstate_creation_distro_specific_pass(self):
         self.run_test_sstate_creation(['binutils-cross-'+ self.tune_arch, 'binutils-native'], distro_specific=True, distro_nonspecific=False, temp_sstate_location=True)
 
-    @testcase(1374)
+    @OETestID(1374)
     def test_sstate_creation_distro_specific_fail(self):
         self.run_test_sstate_creation(['binutils-cross-'+ self.tune_arch, 'binutils-native'], distro_specific=False, distro_nonspecific=True, temp_sstate_location=True, should_pass=False)
 
-    @testcase(976)
+    @OETestID(976)
     def test_sstate_creation_distro_nonspecific_pass(self):
         self.run_test_sstate_creation(['linux-libc-headers'], distro_specific=False, distro_nonspecific=True, temp_sstate_location=True)
 
-    @testcase(1375)
+    @OETestID(1375)
     def test_sstate_creation_distro_nonspecific_fail(self):
         self.run_test_sstate_creation(['linux-libc-headers'], distro_specific=True, distro_nonspecific=False, temp_sstate_location=True, should_pass=False)
 
@@ -72,17 +70,17 @@
         tgz_removed = self.search_sstate('|'.join(map(str, [s + '.*?\.tgz$' for s in targets])), distro_specific, distro_nonspecific)
         self.assertTrue(not tgz_removed, msg="do_cleansstate didn't remove .tgz sstate files for: %s (%s)" % (', '.join(map(str, targets)), str(tgz_removed)))
 
-    @testcase(977)
+    @OETestID(977)
     def test_cleansstate_task_distro_specific_nonspecific(self):
         targets = ['binutils-cross-'+ self.tune_arch, 'binutils-native']
         targets.append('linux-libc-headers')
         self.run_test_cleansstate_task(targets, distro_specific=True, distro_nonspecific=True, temp_sstate_location=True)
 
-    @testcase(1376)
+    @OETestID(1376)
     def test_cleansstate_task_distro_nonspecific(self):
         self.run_test_cleansstate_task(['linux-libc-headers'], distro_specific=False, distro_nonspecific=True, temp_sstate_location=True)
 
-    @testcase(1377)
+    @OETestID(1377)
     def test_cleansstate_task_distro_specific(self):
         targets = ['binutils-cross-'+ self.tune_arch, 'binutils-native']
         targets.append('linux-libc-headers')
@@ -121,15 +119,15 @@
         created_once = [x for x in file_tracker_2 if x not in file_tracker_1]
         self.assertTrue(created_once == [], msg="The following sstate files ware created only in the second run: %s" % ', '.join(map(str, created_once)))
 
-    @testcase(175)
+    @OETestID(175)
     def test_rebuild_distro_specific_sstate_cross_native_targets(self):
         self.run_test_rebuild_distro_specific_sstate(['binutils-cross-' + self.tune_arch, 'binutils-native'], temp_sstate_location=True)
 
-    @testcase(1372)
+    @OETestID(1372)
     def test_rebuild_distro_specific_sstate_cross_target(self):
         self.run_test_rebuild_distro_specific_sstate(['binutils-cross-' + self.tune_arch], temp_sstate_location=True)
 
-    @testcase(1373)
+    @OETestID(1373)
     def test_rebuild_distro_specific_sstate_native_target(self):
         self.run_test_rebuild_distro_specific_sstate(['binutils-native'], temp_sstate_location=True)
 
@@ -176,7 +174,7 @@
         expected_not_actual = [x for x in expected_remaining_sstate if x not in actual_remaining_sstate]
         self.assertFalse(expected_not_actual, msg="Extra files ware removed: %s" ', '.join(map(str, expected_not_actual)))
 
-    @testcase(973)
+    @OETestID(973)
     def test_sstate_cache_management_script_using_pr_1(self):
         global_config = []
         target_config = []
@@ -184,7 +182,7 @@
         target_config.append('PR = "0"')
         self.run_test_sstate_cache_management_script('m4', global_config,  target_config, ignore_patterns=['populate_lic'])
 
-    @testcase(978)
+    @OETestID(978)
     def test_sstate_cache_management_script_using_pr_2(self):
         global_config = []
         target_config = []
@@ -194,7 +192,7 @@
         target_config.append('PR = "1"')
         self.run_test_sstate_cache_management_script('m4', global_config,  target_config, ignore_patterns=['populate_lic'])
 
-    @testcase(979)
+    @OETestID(979)
     def test_sstate_cache_management_script_using_pr_3(self):
         global_config = []
         target_config = []
@@ -206,7 +204,7 @@
         target_config.append('PR = "1"')
         self.run_test_sstate_cache_management_script('m4', global_config,  target_config, ignore_patterns=['populate_lic'])
 
-    @testcase(974)
+    @OETestID(974)
     def test_sstate_cache_management_script_using_machine(self):
         global_config = []
         target_config = []
@@ -216,7 +214,7 @@
         target_config.append('')
         self.run_test_sstate_cache_management_script('m4', global_config,  target_config, ignore_patterns=['populate_lic'])
 
-    @testcase(1270)
+    @OETestID(1270)
     def test_sstate_32_64_same_hash(self):
         """
         The sstate checksums for both native and target should not vary whether
@@ -263,7 +261,7 @@
         self.assertCountEqual(files1, files2)
 
 
-    @testcase(1271)
+    @OETestID(1271)
     def test_sstate_nativelsbstring_same_hash(self):
         """
         The sstate checksums should be independent of whichever NATIVELSBSTRING is
@@ -295,7 +293,7 @@
         self.maxDiff = None
         self.assertCountEqual(files1, files2)
 
-    @testcase(1368)
+    @OETestID(1368)
     def test_sstate_allarch_samesigs(self):
         """
         The sstate checksums of allarch packages should be independent of whichever
@@ -314,6 +312,7 @@
 """
         self.sstate_allarch_samesigs(configA, configB)
 
+    @OETestID(1645)
     def test_sstate_allarch_samesigs_multilib(self):
         """
         The sstate checksums of allarch multilib packages should be independent of whichever
@@ -369,7 +368,7 @@
         self.maxDiff = None
         self.assertEqual(files1, files2)
 
-    @testcase(1369)
+    @OETestID(1369)
     def test_sstate_sametune_samesigs(self):
         """
         The sstate checksums of two identical machines (using the same tune) should be the
@@ -414,6 +413,7 @@
         self.assertCountEqual(files1, files2)
 
 
+    @OETestID(1498)
     def test_sstate_noop_samesigs(self):
         """
         The sstate checksums of two builds with these variables changed or
@@ -422,7 +422,7 @@
 
         self.write_config("""
 TMPDIR = "${TOPDIR}/tmp-sstatesamehash"
-BB_NUMBER_THREADS = "1"
+BB_NUMBER_THREADS = "${@oe.utils.cpu_count()}"
 PARALLEL_MAKE = "-j 1"
 DL_DIR = "${TOPDIR}/download1"
 TIME = "111111"
@@ -435,7 +435,7 @@
         bitbake("world meta-toolchain -S none")
         self.write_config("""
 TMPDIR = "${TOPDIR}/tmp-sstatesamehash2"
-BB_NUMBER_THREADS = "2"
+BB_NUMBER_THREADS = "${@oe.utils.cpu_count()+1}"
 PARALLEL_MAKE = "-j 2"
 DL_DIR = "${TOPDIR}/download2"
 TIME = "222222"
@@ -458,6 +458,24 @@
                     base = os.sep.join(root.rsplit(os.sep, 2)[-2:] + [name])
                     f[base] = shash
             return f
+
+        def compare_sigfiles(files, files1, files2, compare=False):
+            for k in files:
+                if k in files1 and k in files2:
+                    print("%s differs:" % k)
+                    if compare:
+                        sigdatafile1 = self.topdir + "/tmp-sstatesamehash/stamps/" + k + "." + files1[k]
+                        sigdatafile2 = self.topdir + "/tmp-sstatesamehash2/stamps/" + k + "." + files2[k]
+                        output = bb.siggen.compare_sigfiles(sigdatafile1, sigdatafile2)
+                        if output:
+                            print('\n'.join(output))
+                elif k in files1 and k not in files2:
+                    print("%s in files1" % k)
+                elif k not in files1 and k in files2:
+                    print("%s in files2" % k)
+                else:
+                    assert "shouldn't reach here"
+
         files1 = get_files(self.topdir + "/tmp-sstatesamehash/stamps/")
         files2 = get_files(self.topdir + "/tmp-sstatesamehash2/stamps/")
         # Remove items that are identical in both sets
@@ -468,16 +486,11 @@
             # No changes, so we're done
             return
 
-        for k in files1.keys() | files2.keys():
-            if k in files1 and k in files2:
-                print("%s differs:" % k)
-                print(subprocess.check_output(("bitbake-diffsigs",
-                                               self.topdir + "/tmp-sstatesamehash/stamps/" + k + "." + files1[k],
-                                               self.topdir + "/tmp-sstatesamehash2/stamps/" + k + "." + files2[k])))
-            elif k in files1 and k not in files2:
-                print("%s in files1" % k)
-            elif k not in files1 and k in files2:
-                print("%s in files2" % k)
-            else:
-                assert "shouldn't reach here"
+        files = list(files1.keys() | files2.keys())
+        # this is an expensive computation, thus just compare the first 'max_sigfiles_to_compare' k files
+        max_sigfiles_to_compare = 20
+        first, rest = files[:max_sigfiles_to_compare], files[max_sigfiles_to_compare:]
+        compare_sigfiles(first, files1.keys(), files2.keys(), compare=True)
+        compare_sigfiles(rest, files1.keys(), files2.keys(), compare=False)
+
         self.fail("sstate hashes not identical.")
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/tinfoil.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/tinfoil.py
similarity index 76%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/tinfoil.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/tinfoil.py
index 73a0c3b..f889a47 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/tinfoil.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/tinfoil.py
@@ -1,16 +1,17 @@
-import unittest
 import os
 import re
+import time
+import logging
 import bb.tinfoil
 
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import runCmd
-from oeqa.utils.decorators import testcase
+from oeqa.core.decorator.oeid import OETestID
 
-class TinfoilTests(oeSelfTest):
+class TinfoilTests(OESelftestTestCase):
     """ Basic tests for the tinfoil API """
 
-    @testcase(1568)
+    @OETestID(1568)
     def test_getvar(self):
         with bb.tinfoil.Tinfoil() as tinfoil:
             tinfoil.prepare(True)
@@ -18,7 +19,7 @@
             if not machine:
                 self.fail('Unable to get MACHINE value - returned %s' % machine)
 
-    @testcase(1569)
+    @OETestID(1569)
     def test_expand(self):
         with bb.tinfoil.Tinfoil() as tinfoil:
             tinfoil.prepare(True)
@@ -27,7 +28,7 @@
             if not pid:
                 self.fail('Unable to expand "%s" - returned %s' % (expr, pid))
 
-    @testcase(1570)
+    @OETestID(1570)
     def test_getvar_bb_origenv(self):
         with bb.tinfoil.Tinfoil() as tinfoil:
             tinfoil.prepare(True)
@@ -36,7 +37,7 @@
                 self.fail('Unable to get BB_ORIGENV value - returned %s' % origenv)
             self.assertEqual(origenv.getVar('HOME', False), os.environ['HOME'])
 
-    @testcase(1571)
+    @OETestID(1571)
     def test_parse_recipe(self):
         with bb.tinfoil.Tinfoil() as tinfoil:
             tinfoil.prepare(config_only=False, quiet=2)
@@ -47,7 +48,7 @@
             rd = tinfoil.parse_recipe_file(best[3])
             self.assertEqual(testrecipe, rd.getVar('PN'))
 
-    @testcase(1572)
+    @OETestID(1572)
     def test_parse_recipe_copy_expand(self):
         with bb.tinfoil.Tinfoil() as tinfoil:
             tinfoil.prepare(config_only=False, quiet=2)
@@ -66,7 +67,7 @@
             localdata.setVar('PN', 'hello')
             self.assertEqual('hello', localdata.getVar('BPN'))
 
-    @testcase(1573)
+    @OETestID(1573)
     def test_parse_recipe_initial_datastore(self):
         with bb.tinfoil.Tinfoil() as tinfoil:
             tinfoil.prepare(config_only=False, quiet=2)
@@ -80,7 +81,7 @@
             # Check we can get variable values
             self.assertEqual('somevalue', rd.getVar('MYVARIABLE'))
 
-    @testcase(1574)
+    @OETestID(1574)
     def test_list_recipes(self):
         with bb.tinfoil.Tinfoil() as tinfoil:
             tinfoil.prepare(config_only=False, quiet=2)
@@ -99,38 +100,43 @@
             if checkpns:
                 self.fail('Unable to find pkg_fn entries for: %s' % ', '.join(checkpns))
 
-    @testcase(1575)
+    @OETestID(1575)
     def test_wait_event(self):
         with bb.tinfoil.Tinfoil() as tinfoil:
             tinfoil.prepare(config_only=True)
-            # Need to drain events otherwise events that will be masked will still be in the queue
-            while tinfoil.wait_event(0.25):
-                pass
+
             tinfoil.set_event_mask(['bb.event.FilesMatchingFound', 'bb.command.CommandCompleted'])
+
+            # Need to drain events otherwise events that were masked may still be in the queue
+            while tinfoil.wait_event():
+                pass
+
             pattern = 'conf'
             res = tinfoil.run_command('findFilesMatchingInDir', pattern, 'conf/machine')
             self.assertTrue(res)
 
             eventreceived = False
-            waitcount = 5
-            while waitcount > 0:
+            commandcomplete = False
+            start = time.time()
+            # Wait for 5s in total so we'd detect spurious heartbeat events for example
+            while time.time() - start < 5:
                 event = tinfoil.wait_event(1)
                 if event:
                     if isinstance(event, bb.command.CommandCompleted):
-                        break
+                        commandcomplete = True
                     elif isinstance(event, bb.event.FilesMatchingFound):
                         self.assertEqual(pattern, event._pattern)
                         self.assertIn('qemuarm.conf', event._matches)
                         eventreceived = True
+                    elif isinstance(event, logging.LogRecord):
+                        continue
                     else:
                         self.fail('Unexpected event: %s' % event)
 
-                waitcount = waitcount - 1
-
-            self.assertNotEqual(waitcount, 0, 'Timed out waiting for CommandCompleted event from bitbake server')
+            self.assertTrue(commandcomplete, 'Timed out waiting for CommandCompleted event from bitbake server')
             self.assertTrue(eventreceived, 'Did not receive FilesMatchingFound event from bitbake server')
 
-    @testcase(1576)
+    @OETestID(1576)
     def test_setvariable_clean(self):
         # First check that setVariable affects the datastore
         with bb.tinfoil.Tinfoil() as tinfoil:
@@ -153,6 +159,7 @@
             value = tinfoil.run_command('getVariable', 'TESTVAR')
             self.assertEqual(value, 'specialvalue', 'Value set using config_data.setVar() is not reflected in config_data.getVar()')
 
+    @OETestID(1884)
     def test_datastore_operations(self):
         with bb.tinfoil.Tinfoil() as tinfoil:
             tinfoil.prepare(config_only=True)
@@ -188,3 +195,37 @@
             tinfoil.config_data.appendVar('OVERRIDES', ':overrideone')
             value = tinfoil.config_data.getVar('TESTVAR')
             self.assertEqual(value, 'one', 'Variable overrides not functioning correctly')
+
+    def test_variable_history(self):
+        # Basic test to ensure that variable history works when tracking=True
+        with bb.tinfoil.Tinfoil(tracking=True) as tinfoil:
+            tinfoil.prepare(config_only=False, quiet=2)
+            # Note that _tracking for any datastore we get will be
+            # false here, that's currently expected - so we can't check
+            # for that
+            history = tinfoil.config_data.varhistory.variable('DL_DIR')
+            for entry in history:
+                if entry['file'].endswith('/bitbake.conf'):
+                    if entry['op'] in ['set', 'set?']:
+                        break
+            else:
+                self.fail('Did not find history entry setting DL_DIR in bitbake.conf. History: %s' % history)
+            # Check it works for recipes as well
+            testrecipe = 'zlib'
+            rd = tinfoil.parse_recipe(testrecipe)
+            history = rd.varhistory.variable('LICENSE')
+            bbfound = -1
+            recipefound = -1
+            for i, entry in enumerate(history):
+                if entry['file'].endswith('/bitbake.conf'):
+                    if entry['detail'] == 'INVALID' and entry['op'] in ['set', 'set?']:
+                        bbfound = i
+                elif entry['file'].endswith('.bb'):
+                    if entry['op'] == 'set':
+                        recipefound = i
+            if bbfound == -1:
+                self.fail('Did not find history entry setting LICENSE in bitbake.conf parsing %s recipe. History: %s' % (testrecipe, history))
+            if recipefound == -1:
+                self.fail('Did not find history entry setting LICENSE in %s recipe. History: %s' % (testrecipe, history))
+            if bbfound > recipefound:
+                self.fail('History entry setting LICENSE in %s recipe and in bitbake.conf in wrong order. History: %s' % (testrecipe, history))
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/wic.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/wic.py
similarity index 70%
rename from import-layers/yocto-poky/meta/lib/oeqa/selftest/wic.py
rename to import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/wic.py
index 726af19..651d575 100644
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/wic.py
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/cases/wic.py
@@ -28,13 +28,13 @@
 import unittest
 
 from glob import glob
-from shutil import rmtree
+from shutil import rmtree, copy
 from functools import wraps, lru_cache
 from tempfile import NamedTemporaryFile
 
-from oeqa.selftest.base import oeSelfTest
+from oeqa.selftest.case import OESelftestTestCase
 from oeqa.utils.commands import runCmd, bitbake, get_bb_var, get_bb_vars, runqemu
-from oeqa.utils.decorators import testcase
+from oeqa.core.decorator.oeid import OETestID
 
 
 @lru_cache(maxsize=32)
@@ -61,7 +61,7 @@
     return wrapper
 
 
-class Wic(oeSelfTest):
+class Wic(OESelftestTestCase):
     """Wic test class."""
 
     resultdir = "/var/tmp/wic.oe-selftest/"
@@ -71,6 +71,7 @@
 
     def setUpLocal(self):
         """This code is executed before each test method."""
+        super(Wic, self).setUpLocal()
         if not self.native_sysroot:
             Wic.native_sysroot = get_bb_var('STAGING_DIR_NATIVE', 'wic-tools')
 
@@ -91,64 +92,65 @@
     def tearDownLocal(self):
         """Remove resultdir as it may contain images."""
         rmtree(self.resultdir, ignore_errors=True)
+        super(Wic, self).tearDownLocal()
 
-    @testcase(1552)
+    @OETestID(1552)
     def test_version(self):
         """Test wic --version"""
         self.assertEqual(0, runCmd('wic --version').status)
 
-    @testcase(1208)
+    @OETestID(1208)
     def test_help(self):
         """Test wic --help and wic -h"""
         self.assertEqual(0, runCmd('wic --help').status)
         self.assertEqual(0, runCmd('wic -h').status)
 
-    @testcase(1209)
+    @OETestID(1209)
     def test_createhelp(self):
         """Test wic create --help"""
         self.assertEqual(0, runCmd('wic create --help').status)
 
-    @testcase(1210)
+    @OETestID(1210)
     def test_listhelp(self):
         """Test wic list --help"""
         self.assertEqual(0, runCmd('wic list --help').status)
 
-    @testcase(1553)
+    @OETestID(1553)
     def test_help_create(self):
         """Test wic help create"""
         self.assertEqual(0, runCmd('wic help create').status)
 
-    @testcase(1554)
+    @OETestID(1554)
     def test_help_list(self):
         """Test wic help list"""
         self.assertEqual(0, runCmd('wic help list').status)
 
-    @testcase(1215)
+    @OETestID(1215)
     def test_help_overview(self):
         """Test wic help overview"""
         self.assertEqual(0, runCmd('wic help overview').status)
 
-    @testcase(1216)
+    @OETestID(1216)
     def test_help_plugins(self):
         """Test wic help plugins"""
         self.assertEqual(0, runCmd('wic help plugins').status)
 
-    @testcase(1217)
+    @OETestID(1217)
     def test_help_kickstart(self):
         """Test wic help kickstart"""
         self.assertEqual(0, runCmd('wic help kickstart').status)
 
-    @testcase(1555)
+    @OETestID(1555)
     def test_list_images(self):
         """Test wic list images"""
         self.assertEqual(0, runCmd('wic list images').status)
 
-    @testcase(1556)
+    @OETestID(1556)
     def test_list_source_plugins(self):
         """Test wic list source-plugins"""
         self.assertEqual(0, runCmd('wic list source-plugins').status)
 
-    @testcase(1557)
+    @OETestID(1557)
     def test_listed_images_help(self):
         """Test wic listed images help"""
         output = runCmd('wic list images').output
@@ -156,25 +158,24 @@
         for image in imagelist:
             self.assertEqual(0, runCmd('wic list %s help' % image).status)
 
-    @testcase(1213)
+    @OETestID(1213)
     def test_unsupported_subcommand(self):
         """Test unsupported subcommand"""
-        self.assertEqual(1, runCmd('wic unsupported',
-                                   ignore_status=True).status)
+        self.assertNotEqual(0, runCmd('wic unsupported', ignore_status=True).status)
 
-    @testcase(1214)
+    @OETestID(1214)
     def test_no_command(self):
         """Test wic without command"""
         self.assertEqual(1, runCmd('wic', ignore_status=True).status)
 
-    @testcase(1211)
+    @OETestID(1211)
     def test_build_image_name(self):
         """Test wic create wictestdisk --image-name=core-image-minimal"""
         cmd = "wic create wictestdisk --image-name=core-image-minimal -o %s" % self.resultdir
         self.assertEqual(0, runCmd(cmd).status)
         self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct")))
 
-    @testcase(1157)
+    @OETestID(1157)
     @only_for_arch(['i586', 'i686', 'x86_64'])
     def test_gpt_image(self):
         """Test creation of core-image-minimal with gpt table and UUID boot"""
@@ -182,12 +183,13 @@
         self.assertEqual(0, runCmd(cmd).status)
         self.assertEqual(1, len(glob(self.resultdir + "directdisk-*.direct")))
 
-    @testcase(1346)
+    @OETestID(1346)
     @only_for_arch(['i586', 'i686', 'x86_64'])
     def test_iso_image(self):
         """Test creation of hybrid iso image with legacy and EFI boot"""
         config = 'INITRAMFS_IMAGE = "core-image-minimal-initramfs"\n'\
-                 'MACHINE_FEATURES_append = " efi"\n'
+                 'MACHINE_FEATURES_append = " efi"\n'\
+                 'DEPENDS_pn-core-image-minimal += "syslinux"\n'
         self.append_config(config)
         bitbake('core-image-minimal')
         self.remove_config(config)
@@ -196,7 +198,7 @@
         self.assertEqual(1, len(glob(self.resultdir + "HYBRID_ISO_IMG-*.direct")))
         self.assertEqual(1, len(glob(self.resultdir + "HYBRID_ISO_IMG-*.iso")))
 
-    @testcase(1348)
+    @OETestID(1348)
     @only_for_arch(['i586', 'i686', 'x86_64'])
     def test_qemux86_directdisk(self):
         """Test creation of qemux-86-directdisk image"""
@@ -204,7 +206,7 @@
         self.assertEqual(0, runCmd(cmd).status)
         self.assertEqual(1, len(glob(self.resultdir + "qemux86-directdisk-*direct")))
 
-    @testcase(1350)
+    @OETestID(1350)
     @only_for_arch(['i586', 'i686', 'x86_64'])
     def test_mkefidisk(self):
         """Test creation of mkefidisk image"""
@@ -212,15 +214,19 @@
         self.assertEqual(0, runCmd(cmd).status)
         self.assertEqual(1, len(glob(self.resultdir + "mkefidisk-*direct")))
 
-    @testcase(1385)
+    @OETestID(1385)
     @only_for_arch(['i586', 'i686', 'x86_64'])
     def test_bootloader_config(self):
         """Test creation of directdisk-bootloader-config image"""
+        config = 'DEPENDS_pn-core-image-minimal += "syslinux"\n'
+        self.append_config(config)
+        bitbake('core-image-minimal')
+        self.remove_config(config)
         cmd = "wic create directdisk-bootloader-config -e core-image-minimal -o %s" % self.resultdir
         self.assertEqual(0, runCmd(cmd).status)
         self.assertEqual(1, len(glob(self.resultdir + "directdisk-bootloader-config-*direct")))
 
-    @testcase(1560)
+    @OETestID(1560)
     @only_for_arch(['i586', 'i686', 'x86_64'])
     def test_systemd_bootdisk(self):
         """Test creation of systemd-bootdisk image"""
@@ -232,7 +238,7 @@
         self.assertEqual(0, runCmd(cmd).status)
         self.assertEqual(1, len(glob(self.resultdir + "systemd-bootdisk-*direct")))
 
-    @testcase(1561)
+    @OETestID(1561)
     def test_sdimage_bootpart(self):
         """Test creation of sdimage-bootpart image"""
         cmd = "wic create sdimage-bootpart -e core-image-minimal -o %s" % self.resultdir
@@ -241,17 +247,21 @@
         self.assertEqual(0, runCmd(cmd).status)
         self.assertEqual(1, len(glob(self.resultdir + "sdimage-bootpart-*direct")))
 
-    @testcase(1562)
+    @OETestID(1562)
     @only_for_arch(['i586', 'i686', 'x86_64'])
     def test_default_output_dir(self):
         """Test default output location"""
         for fname in glob("directdisk-*.direct"):
             os.remove(fname)
+        config = 'DEPENDS_pn-core-image-minimal += "syslinux"\n'
+        self.append_config(config)
+        bitbake('core-image-minimal')
+        self.remove_config(config)
         cmd = "wic create directdisk -e core-image-minimal"
         self.assertEqual(0, runCmd(cmd).status)
         self.assertEqual(1, len(glob("directdisk-*.direct")))
 
-    @testcase(1212)
+    @OETestID(1212)
     @only_for_arch(['i586', 'i686', 'x86_64'])
     def test_build_artifacts(self):
         """Test wic create directdisk providing all artifacts."""
@@ -270,7 +280,7 @@
         self.assertEqual(0, status)
         self.assertEqual(1, len(glob(self.resultdir + "directdisk-*.direct")))
 
-    @testcase(1264)
+    @OETestID(1264)
     def test_compress_gzip(self):
         """Test compressing an image with gzip"""
         self.assertEqual(0, runCmd("wic create wictestdisk "
@@ -278,7 +288,7 @@
                                    "-c gzip -o %s" % self.resultdir).status)
         self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct.gz")))
 
-    @testcase(1265)
+    @OETestID(1265)
     def test_compress_bzip2(self):
         """Test compressing an image with bzip2"""
         self.assertEqual(0, runCmd("wic create wictestdisk "
@@ -286,7 +296,7 @@
                                    "-c bzip2 -o %s" % self.resultdir).status)
         self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct.bz2")))
 
-    @testcase(1266)
+    @OETestID(1266)
     def test_compress_xz(self):
         """Test compressing an image with xz"""
         self.assertEqual(0, runCmd("wic create wictestdisk "
@@ -294,7 +304,7 @@
                                    "--compress-with=xz -o %s" % self.resultdir).status)
         self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct.xz")))
 
-    @testcase(1267)
+    @OETestID(1267)
     def test_wrong_compressor(self):
         """Test how wic breaks if wrong compressor is provided"""
         self.assertEqual(2, runCmd("wic create wictestdisk "
@@ -302,7 +312,7 @@
                                    "-c wrong -o %s" % self.resultdir,
                                    ignore_status=True).status)
 
-    @testcase(1558)
+    @OETestID(1558)
     def test_debug_short(self):
         """Test -D option"""
         self.assertEqual(0, runCmd("wic create wictestdisk "
@@ -310,6 +320,7 @@
                                    "-D -o %s" % self.resultdir).status)
         self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct")))
 
+    @OETestID(1658)
     def test_debug_long(self):
         """Test --debug option"""
         self.assertEqual(0, runCmd("wic create wictestdisk "
@@ -317,7 +328,7 @@
                                    "--debug -o %s" % self.resultdir).status)
         self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct")))
 
-    @testcase(1563)
+    @OETestID(1563)
     def test_skip_build_check_short(self):
         """Test -s option"""
         self.assertEqual(0, runCmd("wic create wictestdisk "
@@ -325,6 +336,7 @@
                                    "-s -o %s" % self.resultdir).status)
         self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct")))
 
+    @OETestID(1671)
     def test_skip_build_check_long(self):
         """Test --skip-build-check option"""
         self.assertEqual(0, runCmd("wic create wictestdisk "
@@ -333,7 +345,7 @@
                                    "--outdir %s" % self.resultdir).status)
         self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct")))
 
-    @testcase(1564)
+    @OETestID(1564)
     def test_build_rootfs_short(self):
         """Test -f option"""
         self.assertEqual(0, runCmd("wic create wictestdisk "
@@ -341,6 +353,7 @@
                                    "-f -o %s" % self.resultdir).status)
         self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct")))
 
+    @OETestID(1656)
     def test_build_rootfs_long(self):
         """Test --build-rootfs option"""
         self.assertEqual(0, runCmd("wic create wictestdisk "
@@ -349,7 +362,7 @@
                                    "--outdir %s" % self.resultdir).status)
         self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*.direct")))
 
-    @testcase(1268)
+    @OETestID(1268)
     @only_for_arch(['i586', 'i686', 'x86_64'])
     def test_rootfs_indirect_recipes(self):
         """Test usage of rootfs plugin with rootfs recipes"""
@@ -361,7 +374,7 @@
         self.assertEqual(0, status)
         self.assertEqual(1, len(glob(self.resultdir + "directdisk-multi-rootfs*.direct")))
 
-    @testcase(1269)
+    @OETestID(1269)
     @only_for_arch(['i586', 'i686', 'x86_64'])
     def test_rootfs_artifacts(self):
         """Test usage of rootfs plugin with rootfs paths"""
@@ -382,6 +395,7 @@
         self.assertEqual(0, status)
         self.assertEqual(1, len(glob(self.resultdir + "%(wks)s-*.direct" % bbvars)))
 
+    @OETestID(1661)
     def test_exclude_path(self):
         """Test --exclude-path wks option."""
 
@@ -489,6 +503,7 @@
         finally:
             os.environ['PATH'] = oldpath
 
+    @OETestID(1662)
     def test_exclude_path_errors(self):
         """Test --exclude-path wks option error handling."""
         wks_file = 'temp.wks'
@@ -507,7 +522,7 @@
                                       % (wks_file, self.resultdir), ignore_status=True).status)
         os.remove(wks_file)
 
-    @testcase(1496)
+    @OETestID(1496)
     def test_bmap_short(self):
         """Test generation of .bmap file -m option"""
         cmd = "wic create wictestdisk -e core-image-minimal -m -o %s" % self.resultdir
@@ -516,6 +531,7 @@
         self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*direct")))
         self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*direct.bmap")))
 
+    @OETestID(1655)
     def test_bmap_long(self):
         """Test generation of .bmap file --bmap option"""
         cmd = "wic create wictestdisk -e core-image-minimal --bmap -o %s" % self.resultdir
@@ -534,7 +550,7 @@
             self.wicenv_cache[image] = os.path.join(stdir, machine, 'imgdata')
         return self.wicenv_cache[image]
 
-    @testcase(1347)
+    @OETestID(1347)
     def test_image_env(self):
         """Test generation of <image>.env files."""
         image = 'core-image-minimal'
@@ -557,29 +573,36 @@
                 self.assertTrue(var in content, "%s is not in .env file" % var)
                 self.assertTrue(content[var])
 
-    @testcase(1559)
+    @OETestID(1559)
     def test_image_vars_dir_short(self):
         """Test image vars directory selection -v option"""
         image = 'core-image-minimal'
         imgenvdir = self._get_image_env_path(image)
+        native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
 
         self.assertEqual(0, runCmd("wic create wictestdisk "
-                                   "--image-name=%s -v %s -o %s"
-                                   % (image, imgenvdir, self.resultdir)).status)
+                                   "--image-name=%s -v %s -n %s -o %s"
+                                   % (image, imgenvdir, native_sysroot,
+                                      self.resultdir)).status)
         self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*direct")))
 
+    @OETestID(1665)
     def test_image_vars_dir_long(self):
         """Test image vars directory selection --vars option"""
         image = 'core-image-minimal'
         imgenvdir = self._get_image_env_path(image)
+        native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", "wic-tools")
+
         self.assertEqual(0, runCmd("wic create wictestdisk "
                                    "--image-name=%s "
                                    "--vars %s "
+                                   "--native-sysroot %s "
                                    "--outdir %s"
-                                   % (image, imgenvdir, self.resultdir)).status)
+                                   % (image, imgenvdir, native_sysroot,
+                                      self.resultdir)).status)
         self.assertEqual(1, len(glob(self.resultdir + "wictestdisk-*direct")))
 
-    @testcase(1351)
+    @OETestID(1351)
     @only_for_arch(['i586', 'i686', 'x86_64'])
     def test_wic_image_type(self):
         """Test building wic images by bitbake"""
@@ -600,7 +623,7 @@
             self.assertTrue(os.path.islink(path))
             self.assertTrue(os.path.isfile(os.path.realpath(path)))
 
-    @testcase(1422)
+    @OETestID(1422)
     @only_for_arch(['i586', 'i686', 'x86_64'])
     def test_qemu(self):
         """Test wic-image-minimal under qemu"""
@@ -614,9 +637,10 @@
             cmd = "mount |grep '^/dev/' | cut -f1,3 -d ' '"
             status, output = qemu.run_serial(cmd)
             self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
-            self.assertEqual(output, '/dev/root /\r\n/dev/sda3 /mnt')
+            self.assertEqual(output, '/dev/root /\r\n/dev/sda1 /boot\r\n/dev/sda3 /mnt')
 
     @only_for_arch(['i586', 'i686', 'x86_64'])
+    @OETestID(1852)
     def test_qemu_efi(self):
         """Test core-image-minimal efi image under qemu"""
         config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "mkefidisk.wks"\n'
@@ -646,6 +670,7 @@
 
         return wkspath, wksname
 
+    @OETestID(1847)
     def test_fixed_size(self):
         """
         Test creation of a simple image with partition size controlled through
@@ -676,6 +701,7 @@
         self.assertEqual(1, len(partlns))
         self.assertEqual("1:0.00MiB:200MiB:200MiB:ext4::;", partlns[0])
 
+    @OETestID(1848)
     def test_fixed_size_error(self):
         """
         Test creation of a simple image with partition size controlled through
@@ -691,6 +717,7 @@
         self.assertEqual(0, len(wicout))
 
     @only_for_arch(['i586', 'i686', 'x86_64'])
+    @OETestID(1854)
     def test_rawcopy_plugin_qemu(self):
         """Test rawcopy plugin in qemu"""
         # build ext4 and wic images
@@ -706,6 +733,7 @@
             self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
             self.assertEqual(output, '2')
 
+    @OETestID(1853)
     def test_rawcopy_plugin(self):
         """Test rawcopy plugin"""
         img = 'core-image-minimal'
@@ -722,6 +750,7 @@
             out = glob(self.resultdir + "%s-*direct" % wksname)
             self.assertEqual(1, len(out))
 
+    @OETestID(1849)
     def test_fs_types(self):
         """Test filesystem types for empty and not empty partitions"""
         img = 'core-image-minimal'
@@ -741,6 +770,7 @@
             out = glob(self.resultdir + "%s-*direct" % wksname)
             self.assertEqual(1, len(out))
 
+    @OETestID(1851)
     def test_kickstart_parser(self):
         """Test wks parser options"""
         with NamedTemporaryFile("w", suffix=".wks") as wks:
@@ -753,6 +783,7 @@
             out = glob(self.resultdir + "%s-*direct" % wksname)
             self.assertEqual(1, len(out))
 
+    @OETestID(1850)
     def test_image_bootpart_globbed(self):
         """Test globbed sources with image-bootpart plugin"""
         img = "core-image-minimal"
@@ -763,6 +794,7 @@
         self.remove_config(config)
         self.assertEqual(1, len(glob(self.resultdir + "sdimage-bootpart-*direct")))
 
+    @OETestID(1855)
     def test_sparse_copy(self):
         """Test sparse_copy with FIEMAP and SEEK_HOLE filemap APIs"""
         libpath = os.path.join(get_bb_var('COREBASE'), 'scripts', 'lib', 'wic')
@@ -790,3 +822,242 @@
                 # 8 blocks is 4K (physical sector size)
                 self.assertEqual(dest_stat.st_blocks, 8)
             os.unlink(dest)
+
+    @OETestID(1857)
+    def test_wic_ls(self):
+        """Test listing image content using 'wic ls'"""
+        self.assertEqual(0, runCmd("wic create wictestdisk "
+                                   "--image-name=core-image-minimal "
+                                   "-D -o %s" % self.resultdir).status)
+        images = glob(self.resultdir + "wictestdisk-*.direct")
+        self.assertEqual(1, len(images))
+
+        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
+
+        # list partitions
+        result = runCmd("wic ls %s -n %s" % (images[0], sysroot))
+        self.assertEqual(0, result.status)
+        self.assertEqual(3, len(result.output.split('\n')))
+
+        # list directory content of the first partition
+        result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
+        self.assertEqual(0, result.status)
+        self.assertEqual(6, len(result.output.split('\n')))
+
+    @OETestID(1856)
+    def test_wic_cp(self):
+        """Test copy files and directories to the the wic image."""
+        self.assertEqual(0, runCmd("wic create wictestdisk "
+                                   "--image-name=core-image-minimal "
+                                   "-D -o %s" % self.resultdir).status)
+        images = glob(self.resultdir + "wictestdisk-*.direct")
+        self.assertEqual(1, len(images))
+
+        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
+
+        # list directory content of the first partition
+        result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
+        self.assertEqual(0, result.status)
+        self.assertEqual(6, len(result.output.split('\n')))
+
+        with NamedTemporaryFile("w", suffix=".wic-cp") as testfile:
+            testfile.write("test")
+
+            # copy file to the partition
+            result = runCmd("wic cp %s %s:1/ -n %s" % (testfile.name, images[0], sysroot))
+            self.assertEqual(0, result.status)
+
+            # check if file is there
+            result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
+            self.assertEqual(0, result.status)
+            self.assertEqual(7, len(result.output.split('\n')))
+            self.assertTrue(os.path.basename(testfile.name) in result.output)
+
+            # prepare directory
+            testdir = os.path.join(self.resultdir, 'wic-test-cp-dir')
+            testsubdir = os.path.join(testdir, 'subdir')
+            os.makedirs(os.path.join(testsubdir))
+            copy(testfile.name, testdir)
+
+            # copy directory to the partition
+            result = runCmd("wic cp %s %s:1/ -n %s" % (testdir, images[0], sysroot))
+            self.assertEqual(0, result.status)
+
+            # check if directory is there
+            result = runCmd("wic ls %s:1/ -n %s" % (images[0], sysroot))
+            self.assertEqual(0, result.status)
+            self.assertEqual(8, len(result.output.split('\n')))
+            self.assertTrue(os.path.basename(testdir) in result.output)
+
+    @OETestID(1858)
+    def test_wic_rm(self):
+        """Test removing files and directories from the the wic image."""
+        self.assertEqual(0, runCmd("wic create mkefidisk "
+                                   "--image-name=core-image-minimal "
+                                   "-D -o %s" % self.resultdir).status)
+        images = glob(self.resultdir + "mkefidisk-*.direct")
+        self.assertEqual(1, len(images))
+
+        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
+
+        # list directory content of the first partition
+        result = runCmd("wic ls %s:1 -n %s" % (images[0], sysroot))
+        self.assertEqual(0, result.status)
+        self.assertIn('\nBZIMAGE        ', result.output)
+        self.assertIn('\nEFI          <DIR>     ', result.output)
+
+        # remove file
+        result = runCmd("wic rm %s:1/bzimage -n %s" % (images[0], sysroot))
+        self.assertEqual(0, result.status)
+
+        # remove directory
+        result = runCmd("wic rm %s:1/efi -n %s" % (images[0], sysroot))
+        self.assertEqual(0, result.status)
+
+        # check if they're removed
+        result = runCmd("wic ls %s:1 -n %s" % (images[0], sysroot))
+        self.assertEqual(0, result.status)
+        self.assertNotIn('\nBZIMAGE        ', result.output)
+        self.assertNotIn('\nEFI          <DIR>     ', result.output)
+
+    @OETestID(1922)
+    def test_mkfs_extraopts(self):
+        """Test wks option --mkfs-extraopts for empty and not empty partitions"""
+        img = 'core-image-minimal'
+        with NamedTemporaryFile("w", suffix=".wks") as wks:
+            wks.writelines(
+                ['part ext2   --fstype ext2     --source rootfs --mkfs-extraopts "-D -F -i 8192"\n',
+                 "part btrfs  --fstype btrfs    --source rootfs --size 40M --mkfs-extraopts='--quiet'\n",
+                 'part squash --fstype squashfs --source rootfs --mkfs-extraopts "-no-sparse -b 4096"\n',
+                 'part emptyvfat   --fstype vfat   --size 1M --mkfs-extraopts "-S 1024 -s 64"\n',
+                 'part emptymsdos  --fstype msdos  --size 1M --mkfs-extraopts "-S 1024 -s 64"\n',
+                 'part emptyext2   --fstype ext2   --size 1M --mkfs-extraopts "-D -F -i 8192"\n',
+                 'part emptybtrfs  --fstype btrfs  --size 100M --mkfs-extraopts "--mixed -K"\n'])
+            wks.flush()
+            cmd = "wic create %s -e %s -o %s" % (wks.name, img, self.resultdir)
+            self.assertEqual(0, runCmd(cmd).status)
+            wksname = os.path.splitext(os.path.basename(wks.name))[0]
+            out = glob(self.resultdir + "%s-*direct" % wksname)
+            self.assertEqual(1, len(out))
+
+    def test_expand_mbr_image(self):
+        """Test wic write --expand command for mbr image"""
+        # build an image
+        config = 'IMAGE_FSTYPES = "wic"\nWKS_FILE = "directdisk.wks"\n'
+        self.append_config(config)
+        self.assertEqual(0, bitbake('core-image-minimal').status)
+
+        # get path to the image
+        bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'MACHINE'])
+        deploy_dir = bb_vars['DEPLOY_DIR_IMAGE']
+        machine = bb_vars['MACHINE']
+        image_path = os.path.join(deploy_dir, 'core-image-minimal-%s.wic' % machine)
+
+        self.remove_config(config)
+
+        try:
+            # expand image to 1G
+            new_image_path = None
+            with NamedTemporaryFile(mode='wb', suffix='.wic.exp',
+                                    dir=deploy_dir, delete=False) as sparse:
+                sparse.truncate(1024 ** 3)
+                new_image_path = sparse.name
+
+            sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
+            cmd = "wic write -n %s --expand 1:0 %s %s" % (sysroot, image_path, new_image_path)
+            self.assertEqual(0, runCmd(cmd).status)
+
+            # check if partitions are expanded
+            orig = runCmd("wic ls %s -n %s" % (image_path, sysroot))
+            exp = runCmd("wic ls %s -n %s" % (new_image_path, sysroot))
+            orig_sizes = [int(line.split()[3]) for line in orig.output.split('\n')[1:]]
+            exp_sizes = [int(line.split()[3]) for line in exp.output.split('\n')[1:]]
+            self.assertEqual(orig_sizes[0], exp_sizes[0]) # first partition is not resized
+            self.assertTrue(orig_sizes[1] < exp_sizes[1])
+
+            # Check if all free space is partitioned
+            result = runCmd("%s/usr/sbin/sfdisk -F %s" % (sysroot, new_image_path))
+            self.assertTrue("0 B, 0 bytes, 0 sectors" in result.output)
+
+            os.rename(image_path, image_path + '.bak')
+            os.rename(new_image_path, image_path)
+
+            # Check if it boots in qemu
+            with runqemu('core-image-minimal', ssh=False) as qemu:
+                cmd = "ls /etc/"
+                status, output = qemu.run_serial('true')
+                self.assertEqual(1, status, 'Failed to run command "%s": %s' % (cmd, output))
+        finally:
+            if os.path.exists(new_image_path):
+                os.unlink(new_image_path)
+            if os.path.exists(image_path + '.bak'):
+                os.rename(image_path + '.bak', image_path)
+
+    def test_wic_ls_ext(self):
+        """Test listing content of the ext partition using 'wic ls'"""
+        self.assertEqual(0, runCmd("wic create wictestdisk "
+                                   "--image-name=core-image-minimal "
+                                   "-D -o %s" % self.resultdir).status)
+        images = glob(self.resultdir + "wictestdisk-*.direct")
+        self.assertEqual(1, len(images))
+
+        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
+
+        # list directory content of the second ext4 partition
+        result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot))
+        self.assertEqual(0, result.status)
+        self.assertTrue(set(['bin', 'home', 'proc', 'usr', 'var', 'dev', 'lib', 'sbin']).issubset(
+                            set(line.split()[-1] for line in result.output.split('\n') if line)))
+
+    def test_wic_cp_ext(self):
+        """Test copy files and directories to the ext partition."""
+        self.assertEqual(0, runCmd("wic create wictestdisk "
+                                   "--image-name=core-image-minimal "
+                                   "-D -o %s" % self.resultdir).status)
+        images = glob(self.resultdir + "wictestdisk-*.direct")
+        self.assertEqual(1, len(images))
+
+        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
+
+        # list directory content of the ext4 partition
+        result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot))
+        self.assertEqual(0, result.status)
+        dirs = set(line.split()[-1] for line in result.output.split('\n') if line)
+        self.assertTrue(set(['bin', 'home', 'proc', 'usr', 'var', 'dev', 'lib', 'sbin']).issubset(dirs))
+
+        with NamedTemporaryFile("w", suffix=".wic-cp") as testfile:
+            testfile.write("test")
+
+            # copy file to the partition
+            result = runCmd("wic cp %s %s:2/ -n %s" % (testfile.name, images[0], sysroot))
+            self.assertEqual(0, result.status)
+
+            # check if file is there
+            result = runCmd("wic ls %s:2/ -n %s" % (images[0], sysroot))
+            self.assertEqual(0, result.status)
+            newdirs = set(line.split()[-1] for line in result.output.split('\n') if line)
+            self.assertEqual(newdirs.difference(dirs), set([os.path.basename(testfile.name)]))
+
+    def test_wic_rm_ext(self):
+        """Test removing files from the ext partition."""
+        self.assertEqual(0, runCmd("wic create mkefidisk "
+                                   "--image-name=core-image-minimal "
+                                   "-D -o %s" % self.resultdir).status)
+        images = glob(self.resultdir + "mkefidisk-*.direct")
+        self.assertEqual(1, len(images))
+
+        sysroot = get_bb_var('RECIPE_SYSROOT_NATIVE', 'wic-tools')
+
+        # list directory content of the /etc directory on ext4 partition
+        result = runCmd("wic ls %s:2/etc/ -n %s" % (images[0], sysroot))
+        self.assertEqual(0, result.status)
+        self.assertTrue('fstab' in [line.split()[-1] for line in result.output.split('\n') if line])
+
+        # remove file
+        result = runCmd("wic rm %s:2/etc/fstab -n %s" % (images[0], sysroot))
+        self.assertEqual(0, result.status)
+
+        # check if it's removed
+        result = runCmd("wic ls %s:2/etc/ -n %s" % (images[0], sysroot))
+        self.assertEqual(0, result.status)
+        self.assertTrue('fstab' not in [line.split()[-1] for line in result.output.split('\n') if line])
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/context.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/context.py
new file mode 100644
index 0000000..9e90d3c
--- /dev/null
+++ b/import-layers/yocto-poky/meta/lib/oeqa/selftest/context.py
@@ -0,0 +1,279 @@
+# Copyright (C) 2017 Intel Corporation
+# Released under the MIT license (see COPYING.MIT)
+
+import os
+import time
+import glob
+import sys
+import imp
+import signal
+from shutil import copyfile
+from random import choice
+
+import oeqa
+
+from oeqa.core.context import OETestContext, OETestContextExecutor
+from oeqa.core.exception import OEQAPreRun, OEQATestNotFound
+
+from oeqa.utils.commands import runCmd, get_bb_vars, get_test_layer
+
+class OESelftestTestContext(OETestContext):
+    def __init__(self, td=None, logger=None, machines=None, config_paths=None):
+        super(OESelftestTestContext, self).__init__(td, logger)
+
+        self.machines = machines
+        self.custommachine = None
+        self.config_paths = config_paths
+
+    def runTests(self, machine=None, skips=[]):
+        if machine:
+            self.custommachine = machine
+            if machine == 'random':
+                self.custommachine = choice(self.machines)
+            self.logger.info('Run tests with custom MACHINE set to: %s' % \
+                    self.custommachine)
+        return super(OESelftestTestContext, self).runTests(skips)
+
+    def listTests(self, display_type, machine=None):
+        return super(OESelftestTestContext, self).listTests(display_type)
+
+class OESelftestTestContextExecutor(OETestContextExecutor):
+    _context_class = OESelftestTestContext
+    _script_executor = 'oe-selftest'
+
+    name = 'oe-selftest'
+    help = 'oe-selftest test component'
+    description = 'Executes selftest tests'
+
+    def register_commands(self, logger, parser):
+        group = parser.add_mutually_exclusive_group(required=True)
+
+        group.add_argument('-a', '--run-all-tests', default=False,
+                action="store_true", dest="run_all_tests",
+                help='Run all (unhidden) tests')
+        group.add_argument('-R', '--skip-tests', required=False, action='store',
+                nargs='+', dest="skips", default=None,
+                help='Run all (unhidden) tests except the ones specified. Format should be <module>[.<class>[.<test_method>]]')
+        group.add_argument('-r', '--run-tests', required=False, action='store',
+                nargs='+', dest="run_tests", default=None,
+                help='Select what tests to run (modules, classes or test methods). Format should be: <module>.<class>.<test_method>')
+
+        group.add_argument('-m', '--list-modules', required=False,
+                action="store_true", default=False,
+                help='List all available test modules.')
+        group.add_argument('--list-classes', required=False,
+                action="store_true", default=False,
+                help='List all available test classes.')
+        group.add_argument('-l', '--list-tests', required=False,
+                action="store_true", default=False,
+                help='List all available tests.')
+
+        parser.add_argument('--machine', required=False, choices=['random', 'all'],
+                            help='Run tests on different machines (random/all).')
+        
+        parser.set_defaults(func=self.run)
+
+    def _get_available_machines(self):
+        machines = []
+
+        bbpath = self.tc_kwargs['init']['td']['BBPATH'].split(':')
+    
+        for path in bbpath:
+            found_machines = glob.glob(os.path.join(path, 'conf', 'machine', '*.conf'))
+            if found_machines:
+                for i in found_machines:
+                    # eg: '/home/<user>/poky/meta-intel/conf/machine/intel-core2-32.conf'
+                    machines.append(os.path.splitext(os.path.basename(i))[0])
+    
+        return machines
+
+    def _get_cases_paths(self, bbpath):
+        cases_paths = []
+        for layer in bbpath:
+            cases_dir = os.path.join(layer, 'lib', 'oeqa', 'selftest', 'cases')
+            if os.path.isdir(cases_dir):
+                cases_paths.append(cases_dir)
+        return cases_paths
+
+    def _process_args(self, logger, args):
+        args.output_log = '%s-results-%s.log' % (self.name,
+                time.strftime("%Y%m%d%H%M%S"))
+        args.test_data_file = None
+        args.CASES_PATHS = None
+
+        super(OESelftestTestContextExecutor, self)._process_args(logger, args)
+
+        if args.list_modules:
+            args.list_tests = 'module'
+        elif args.list_classes:
+            args.list_tests = 'class'
+        elif args.list_tests:
+            args.list_tests = 'name'
+
+        self.tc_kwargs['init']['td'] = get_bb_vars()
+        self.tc_kwargs['init']['machines'] = self._get_available_machines()
+
+        builddir = os.environ.get("BUILDDIR")
+        self.tc_kwargs['init']['config_paths'] = {}
+        self.tc_kwargs['init']['config_paths']['testlayer_path'] = \
+                get_test_layer()
+        self.tc_kwargs['init']['config_paths']['builddir'] = builddir
+        self.tc_kwargs['init']['config_paths']['localconf'] = \
+                os.path.join(builddir, "conf/local.conf")
+        self.tc_kwargs['init']['config_paths']['localconf_backup'] = \
+                os.path.join(builddir, "conf/local.conf.orig")
+        self.tc_kwargs['init']['config_paths']['localconf_class_backup'] = \
+                os.path.join(builddir, "conf/local.conf.bk")
+        self.tc_kwargs['init']['config_paths']['bblayers'] = \
+                os.path.join(builddir, "conf/bblayers.conf")
+        self.tc_kwargs['init']['config_paths']['bblayers_backup'] = \
+                os.path.join(builddir, "conf/bblayers.conf.orig")
+        self.tc_kwargs['init']['config_paths']['bblayers_class_backup'] = \
+                os.path.join(builddir, "conf/bblayers.conf.bk")
+
+        copyfile(self.tc_kwargs['init']['config_paths']['localconf'],
+                self.tc_kwargs['init']['config_paths']['localconf_backup'])
+        copyfile(self.tc_kwargs['init']['config_paths']['bblayers'], 
+                self.tc_kwargs['init']['config_paths']['bblayers_backup'])
+
+        self.tc_kwargs['run']['skips'] = args.skips
+
+    def _pre_run(self):
+        def _check_required_env_variables(vars):
+            for var in vars:
+                if not os.environ.get(var):
+                    self.tc.logger.error("%s is not set. Did you forget to source your build environment setup script?" % var)
+                    raise OEQAPreRun
+
+        def _check_presence_meta_selftest():
+            builddir = os.environ.get("BUILDDIR")
+            if os.getcwd() != builddir:
+                self.tc.logger.info("Changing cwd to %s" % builddir)
+                os.chdir(builddir)
+
+            if not "meta-selftest" in self.tc.td["BBLAYERS"]:
+                self.tc.logger.warn("meta-selftest layer not found in BBLAYERS, adding it")
+                meta_selftestdir = os.path.join(
+                    self.tc.td["BBLAYERS_FETCH_DIR"], 'meta-selftest')
+                if os.path.isdir(meta_selftestdir):
+                    runCmd("bitbake-layers add-layer %s" %meta_selftestdir)
+                    # reload data is needed because a meta-selftest layer was add
+                    self.tc.td = get_bb_vars()
+                    self.tc.config_paths['testlayer_path'] = get_test_layer()
+                else:
+                    self.tc.logger.error("could not locate meta-selftest in:\n%s" % meta_selftestdir)
+                    raise OEQAPreRun
+
+        def _add_layer_libs():
+            bbpath = self.tc.td['BBPATH'].split(':')
+            layer_libdirs = [p for p in (os.path.join(l, 'lib') \
+                    for l in bbpath) if os.path.exists(p)]
+            if layer_libdirs:
+                self.tc.logger.info("Adding layer libraries:")
+                for l in layer_libdirs:
+                    self.tc.logger.info("\t%s" % l)
+
+                sys.path.extend(layer_libdirs)
+                imp.reload(oeqa.selftest)
+
+        _check_required_env_variables(["BUILDDIR"])
+        _check_presence_meta_selftest()
+
+        if "buildhistory.bbclass" in self.tc.td["BBINCLUDED"]:
+            self.tc.logger.error("You have buildhistory enabled already and this isn't recommended for selftest, please disable it first.")
+            raise OEQAPreRun
+
+        if "PRSERV_HOST" in self.tc.td:
+            self.tc.logger.error("Please unset PRSERV_HOST in order to run oe-selftest")
+            raise OEQAPreRun
+
+        if "SANITY_TESTED_DISTROS" in self.tc.td:
+            self.tc.logger.error("Please unset SANITY_TESTED_DISTROS in order to run oe-selftest")
+            raise OEQAPreRun
+
+        _add_layer_libs()
+
+        self.tc.logger.info("Running bitbake -p")
+        runCmd("bitbake -p")
+
+    def _internal_run(self, logger, args):
+        self.module_paths = self._get_cases_paths(
+                self.tc_kwargs['init']['td']['BBPATH'].split(':'))
+
+        self.tc = self._context_class(**self.tc_kwargs['init'])
+        try:
+            self.tc.loadTests(self.module_paths, **self.tc_kwargs['load'])
+        except OEQATestNotFound as ex:
+            logger.error(ex)
+            sys.exit(1)
+
+        if args.list_tests:
+            rc = self.tc.listTests(args.list_tests, **self.tc_kwargs['list'])
+        else:
+            self._pre_run()
+            rc = self.tc.runTests(**self.tc_kwargs['run'])
+            rc.logDetails()
+            rc.logSummary(self.name)
+
+        return rc
+
+    def _signal_clean_handler(self, signum, frame):
+        sys.exit(1)
+    
+    def run(self, logger, args):
+        self._process_args(logger, args)
+
+        signal.signal(signal.SIGTERM, self._signal_clean_handler)
+
+        rc = None
+        try:
+            if args.machine:
+                logger.info('Custom machine mode enabled. MACHINE set to %s' %
+                        args.machine)
+
+                if args.machine == 'all':
+                    results = []
+                    for m in self.tc_kwargs['init']['machines']:
+                        self.tc_kwargs['run']['machine'] = m
+                        results.append(self._internal_run(logger, args))
+
+                        # XXX: the oe-selftest script only needs to know if one
+                        # machine run fails
+                        for r in results:
+                            rc = r
+                            if not r.wasSuccessful():
+                                break
+
+                else:
+                    self.tc_kwargs['run']['machine'] = args.machine
+                    return self._internal_run(logger, args)
+
+            else:
+                self.tc_kwargs['run']['machine'] = args.machine
+                rc = self._internal_run(logger, args)
+        finally:
+            config_paths = self.tc_kwargs['init']['config_paths']
+            if os.path.exists(config_paths['localconf_backup']):
+                copyfile(config_paths['localconf_backup'],
+                        config_paths['localconf'])
+                os.remove(config_paths['localconf_backup'])
+
+            if os.path.exists(config_paths['bblayers_backup']):
+                copyfile(config_paths['bblayers_backup'], 
+                        config_paths['bblayers'])
+                os.remove(config_paths['bblayers_backup'])
+
+            if os.path.exists(config_paths['localconf_class_backup']):
+                os.remove(config_paths['localconf_class_backup'])
+            if os.path.exists(config_paths['bblayers_class_backup']):
+                os.remove(config_paths['bblayers_class_backup'])
+
+            output_link = os.path.join(os.path.dirname(args.output_log),
+                    "%s-results.log" % self.name)
+            if os.path.exists(output_link):
+                os.remove(output_link)
+            os.symlink(args.output_log, output_link)
+
+        return rc
+
+_executor_class = OESelftestTestContextExecutor
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/imagefeatures.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/imagefeatures.py
deleted file mode 100644
index 76896c7..0000000
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/imagefeatures.py
+++ /dev/null
@@ -1,127 +0,0 @@
-from oeqa.selftest.base import oeSelfTest
-from oeqa.utils.commands import runCmd, bitbake, get_bb_var, runqemu
-from oeqa.utils.decorators import testcase
-from oeqa.utils.sshcontrol import SSHControl
-import os
-import sys
-import logging
-
-class ImageFeatures(oeSelfTest):
-
-    test_user = 'tester'
-    root_user = 'root'
-
-    @testcase(1107)
-    def test_non_root_user_can_connect_via_ssh_without_password(self):
-        """
-        Summary: Check if non root user can connect via ssh without password
-        Expected: 1. Connection to the image via ssh using root user without providing a password should be allowed.
-                  2. Connection to the image via ssh using tester user without providing a password should be allowed.
-        Product: oe-core
-        Author: Ionut Chisanovici <ionutx.chisanovici@intel.com>
-        AutomatedBy: Daniel Istrate <daniel.alexandrux.istrate@intel.com>
-        """
-
-        features = 'EXTRA_IMAGE_FEATURES = "ssh-server-openssh empty-root-password allow-empty-password"\n'
-        features += 'INHERIT += "extrausers"\n'
-        features += 'EXTRA_USERS_PARAMS = "useradd -p \'\' {}; usermod -s /bin/sh {};"'.format(self.test_user, self.test_user)
-        self.write_config(features)
-
-        # Build a core-image-minimal
-        bitbake('core-image-minimal')
-
-        with runqemu("core-image-minimal") as qemu:
-            # Attempt to ssh with each user into qemu with empty password
-            for user in [self.root_user, self.test_user]:
-                ssh = SSHControl(ip=qemu.ip, logfile=qemu.sshlog, user=user)
-                status, output = ssh.run("true")
-                self.assertEqual(status, 0, 'ssh to user %s failed with %s' % (user, output))
-
-    @testcase(1115)
-    def test_all_users_can_connect_via_ssh_without_password(self):
-        """
-        Summary:     Check if all users can connect via ssh without password
-        Expected: 1. Connection to the image via ssh using root user without providing a password should NOT be allowed.
-                  2. Connection to the image via ssh using tester user without providing a password should be allowed.
-        Product:     oe-core
-        Author:      Ionut Chisanovici <ionutx.chisanovici@intel.com>
-        AutomatedBy: Daniel Istrate <daniel.alexandrux.istrate@intel.com>
-        """
-
-        features = 'EXTRA_IMAGE_FEATURES = "ssh-server-openssh allow-empty-password"\n'
-        features += 'INHERIT += "extrausers"\n'
-        features += 'EXTRA_USERS_PARAMS = "useradd -p \'\' {}; usermod -s /bin/sh {};"'.format(self.test_user, self.test_user)
-        self.write_config(features)
-
-        # Build a core-image-minimal
-        bitbake('core-image-minimal')
-
-        with runqemu("core-image-minimal") as qemu:
-            # Attempt to ssh with each user into qemu with empty password
-            for user in [self.root_user, self.test_user]:
-                ssh = SSHControl(ip=qemu.ip, logfile=qemu.sshlog, user=user)
-                status, output = ssh.run("true")
-                if user == 'root':
-                    self.assertNotEqual(status, 0, 'ssh to user root was allowed when it should not have been')
-                else:
-                    self.assertEqual(status, 0, 'ssh to user tester failed with %s' % output)
-
-
-    @testcase(1116)
-    def test_clutter_image_can_be_built(self):
-        """
-        Summary:     Check if clutter image can be built
-        Expected:    1. core-image-clutter can be built
-        Product:     oe-core
-        Author:      Ionut Chisanovici <ionutx.chisanovici@intel.com>
-        AutomatedBy: Daniel Istrate <daniel.alexandrux.istrate@intel.com>
-        """
-
-        # Build a core-image-clutter
-        bitbake('core-image-clutter')
-
-    @testcase(1117)
-    def test_wayland_support_in_image(self):
-        """
-        Summary:     Check Wayland support in image
-        Expected:    1. Wayland image can be build
-                     2. Wayland feature can be installed
-        Product:     oe-core
-        Author:      Ionut Chisanovici <ionutx.chisanovici@intel.com>
-        AutomatedBy: Daniel Istrate <daniel.alexandrux.istrate@intel.com>
-        """
-
-        distro_features = get_bb_var('DISTRO_FEATURES')
-        if not ('opengl' in distro_features and 'wayland' in distro_features):
-            self.skipTest('neither opengl nor wayland present on DISTRO_FEATURES so core-image-weston cannot be built')
-
-        # Build a core-image-weston
-        bitbake('core-image-weston')
-
-    def test_bmap(self):
-        """
-        Summary:     Check bmap support
-        Expected:    1. core-image-minimal can be build with bmap support
-                     2. core-image-minimal is sparse
-        Product:     oe-core
-        Author:      Ed Bartosh <ed.bartosh@linux.intel.com>
-        """
-
-        features = 'IMAGE_FSTYPES += " ext4 ext4.bmap"'
-        self.write_config(features)
-
-        image_name = 'core-image-minimal'
-        bitbake(image_name)
-
-        deploy_dir_image = get_bb_var('DEPLOY_DIR_IMAGE')
-        link_name = get_bb_var('IMAGE_LINK_NAME', image_name)
-        image_path = os.path.join(deploy_dir_image, "%s.ext4" % link_name)
-        bmap_path = "%s.bmap" % image_path
-
-        # check if result image and bmap file are in deploy directory
-        self.assertTrue(os.path.exists(image_path))
-        self.assertTrue(os.path.exists(bmap_path))
-
-        # check if result image is sparse
-        image_stat = os.stat(image_path)
-        self.assertTrue(image_stat.st_size > image_stat.st_blocks * 512)
diff --git a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/license.py b/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/license.py
deleted file mode 100644
index c388886..0000000
--- a/import-layers/yocto-poky/meta/lib/oeqa/selftest/oelib/license.py
+++ /dev/null
@@ -1,68 +0,0 @@
-import unittest
-import oe.license
-
-class SeenVisitor(oe.license.LicenseVisitor):
-    def __init__(self):
-        self.seen = []
-        oe.license.LicenseVisitor.__init__(self)
-
-    def visit_Str(self, node):
-        self.seen.append(node.s)
-
-class TestSingleLicense(unittest.TestCase):
-    licenses = [
-        "GPLv2",
-        "LGPL-2.0",
-        "Artistic",
-        "MIT",
-        "GPLv3+",
-        "FOO_BAR",
-    ]
-    invalid_licenses = ["GPL/BSD"]
-
-    @staticmethod
-    def parse(licensestr):
-        visitor = SeenVisitor()
-        visitor.visit_string(licensestr)
-        return visitor.seen
-
-    def test_single_licenses(self):
-        for license in self.licenses:
-            licenses = self.parse(license)
-            self.assertListEqual(licenses, [license])
-
-    def test_invalid_licenses(self):
-        for license in self.invalid_licenses:
-            with self.assertRaises(oe.license.InvalidLicense) as cm:
-                self.parse(license)
-            self.assertEqual(cm.exception.license, license)
-
-class TestSimpleCombinations(unittest.TestCase):
-    tests = {
-        "FOO&BAR": ["FOO", "BAR"],
-        "BAZ & MOO": ["BAZ", "MOO"],
-        "ALPHA|BETA": ["ALPHA"],
-        "BAZ&MOO|FOO": ["FOO"],
-        "FOO&BAR|BAZ": ["FOO", "BAR"],
-    }
-    preferred = ["ALPHA", "FOO", "BAR"]
-
-    def test_tests(self):
-        def choose(a, b):
-            if all(lic in self.preferred for lic in b):
-                return b
-            else:
-                return a
-
-        for license, expected in self.tests.items():
-            licenses = oe.license.flattened_licenses(license, choose)
-            self.assertListEqual(licenses, expected)
-
-class TestComplexCombinations(TestSimpleCombinations):
-    tests = {
-        "FOO & (BAR | BAZ)&MOO": ["FOO", "BAR", "MOO"],
-        "(ALPHA|(BETA&THETA)|OMEGA)&DELTA": ["OMEGA", "DELTA"],
-        "((ALPHA|BETA)&FOO)|BAZ": ["BETA", "FOO"],
-        "(GPL-2.0|Proprietary)&BSD-4-clause&MIT": ["GPL-2.0", "BSD-4-clause", "MIT"],
-    }
-    preferred = ["BAR", "OMEGA", "BETA", "GPL-2.0"]