Blob Blame History Raw
From 3a513b4d7f14406d94614643327ce2d8ab35f7eb Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Thu, 14 Mar 2019 09:34:02 +0100
Subject: [PATCH 01/10] Add a decorator for "tagging" tests

Tests function (or classes) can be tagged with one or more tags.
This provides easier way to skip slow/unstable/dangerous tests
and/or or some subset of the tests (e.g. run only unstable tests
to check if the test is still failing).
---
 tests/utils.py | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/tests/utils.py b/tests/utils.py
index a9eb430..82b5494 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -10,6 +10,7 @@ import unittest
 import time
 import sys
 from contextlib import contextmanager
+from enum import Enum
 from itertools import chain
 
 from gi.repository import GLib
@@ -342,6 +343,31 @@ def unstable_test(test):
     return decorated_test
 
 
+class TestTags(Enum):
+    SLOW = 1        # slow tests
+    UNSTABLE = 2    # randomly failing tests
+    UNSAFE = 3      # tests that change system configuration
+    CORE = 4        # tests covering core functionality
+    NOSTORAGE = 5   # tests that don't work with storage
+    EXTRADEPS = 6   # tests that require special configuration and/or device to run
+    REGRESSION = 7  # regression tests
+
+
+def tag_test(*tags):
+    def decorator(func):
+        func.slow = TestTags.SLOW in tags
+        func.unstable = TestTags.UNSTABLE in tags
+        func.unsafe = TestTags.UNSAFE in tags
+        func.core = TestTags.CORE in tags
+        func.nostorage = TestTags.NOSTORAGE in tags
+        func.extradeps = TestTags.EXTRADEPS in tags
+        func.regression = TestTags.REGRESSION in tags
+
+        return func
+
+    return decorator
+
+
 def run(cmd_string):
     """
     Run the a command with file descriptors closed as lvm is trying to
-- 
2.20.1


From f3648fed9c98e779c9f265262a4a77a905689fe7 Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Thu, 14 Mar 2019 09:37:18 +0100
Subject: [PATCH 02/10] Use test tags for skipping tests

This loads all individual test functions from the suite and checks
the tags to decide whether the test should be skippend or not.
We are abusing some unittest internal API to do this, but all
private functions works with Python 2 and Python 3 and general
looks like "stable enough" API.
---
 tests/run_tests.py | 130 ++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 124 insertions(+), 6 deletions(-)

diff --git a/tests/run_tests.py b/tests/run_tests.py
index 6f74430..5301d07 100644
--- a/tests/run_tests.py
+++ b/tests/run_tests.py
@@ -16,6 +16,82 @@ LIBDIRS = 'src/utils/.libs:src/plugins/.libs:src/plugins/fs/.libs:src/lib/.libs'
 GIDIR = 'src/lib'
 
 
+def _get_tests_from_suite(suite, tests):
+    """ Extract tests from the test suite """
+    # 'tests' we get from 'unittest.defaultTestLoader.discover' are "wrapped"
+    # in multiple 'unittest.suite.TestSuite' classes/lists so we need to "unpack"
+    # the indivudual test cases
+    for test in suite:
+        if isinstance(test, unittest.suite.TestSuite):
+            _get_tests_from_suite(test, tests)
+
+        if isinstance(test, unittest.TestCase):
+            tests.append(test)
+
+    return tests
+
+
+def _get_test_tags(test):
+    """ Get test tags for single test case """
+
+    tags = []
+
+    # test failed to load, usually some ImportError or something really broken
+    # in the test file, just return empty list and let it fail
+    # with python2 the loader will raise an exception directly without returning
+    # a "fake" FailedTest test case
+    if six.PY3 and isinstance(test, unittest.loader._FailedTest):
+        return tags
+
+    test_fn = getattr(test, test._testMethodName)
+
+    # it is possible to either tag a test funcion or the class so we need to
+    # check both for the tag
+    if getattr(test_fn, "slow", False) or getattr(test_fn.__self__, "slow", False):
+        tags.append(TestTags.SLOW)
+    if getattr(test_fn, "unstable", False) or getattr(test_fn.__self__, "unstable", False):
+        tags.append(TestTags.UNSTABLE)
+    if getattr(test_fn, "unsafe", False) or getattr(test_fn.__self__, "unsafe", False):
+        tags.append(TestTags.UNSAFE)
+    if getattr(test_fn, "core", False) or getattr(test_fn.__self__, "core", False):
+        tags.append(TestTags.CORE)
+    if getattr(test_fn, "nostorage", False) or getattr(test_fn.__self__, "nostorage", False):
+        tags.append(TestTags.NOSTORAGE)
+    if getattr(test_fn, "extradeps", False) or getattr(test_fn.__self__, "extradeps", False):
+        tags.append(TestTags.EXTRADEPS)
+    if getattr(test_fn, "regression", False) or getattr(test_fn.__self__, "regression", False):
+        tags.append(TestTags.REGRESSION)
+
+    return tags
+
+
+def _print_skip_message(test, skip_tag):
+
+    # test.id() looks like 'crypto_test.CryptoTestResize.test_luks2_resize'
+    # and we want to print 'test_luks2_resize (crypto_test.CryptoTestResize)'
+    test_desc = test.id().split(".")
+    test_name = test_desc[-1]
+    test_module = ".".join(test_desc[:-1])
+
+    if skip_tag == TestTags.SLOW:
+        reason = "skipping slow tests"
+    elif skip_tag == TestTags.UNSTABLE:
+        reason = "skipping unstable tests"
+    elif skip_tag == TestTags.UNSAFE:
+        reason = "skipping test that modifies system configuration"
+    elif skip_tag == TestTags.EXTRADEPS:
+        reason = "skipping test that requires special configuration"
+    elif skip_tag == TestTags.CORE:
+        reason = "skipping non-core test"
+    else:
+        reason = "unknown reason"  # just to be sure there is some default value
+
+    if test._testMethodDoc:
+        print("%s (%s)\n%s ... skipped '%s'" % (test_name, test_module, test._testMethodDoc, reason))
+    else:
+        print("%s (%s) ... skipped '%s'" % (test_name, test_module, reason))
+
+
 if __name__ == '__main__':
 
     testdir = os.path.abspath(os.path.dirname(__file__))
@@ -45,6 +121,9 @@ if __name__ == '__main__':
     argparser.add_argument('-j', '--jenkins', dest='jenkins',
                            help='run also tests that should run only in a CI environment',
                            action='store_true')
+    argparser.add_argument('-c', '--core', dest='core',
+                           help='run tests that cover basic functionality of the library and regression tests',
+                           action='store_true')
     argparser.add_argument('-s', '--stop', dest='stop',
                            help='stop executing after first failed test',
                            action='store_true')
@@ -57,21 +136,60 @@ if __name__ == '__main__':
     if args.jenkins:
         os.environ['JENKINS_HOME'] = ''
 
+    # read the environmental variables for backwards compatibility
+    if 'JENKINS_HOME' in os.environ:
+        args.jenkins = True
+    if 'SKIP_SLOW' in os.environ:
+        args.fast = True
+    if 'FEELINGLUCKY' in os.environ:
+        args.lucky = True
+
     sys.path.append(testdir)
     sys.path.append(projdir)
     sys.path.append(os.path.join(projdir, 'src/python'))
 
     start_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
 
+    loader = unittest.defaultTestLoader
     suite = unittest.TestSuite()
+
     if args.testname:
-        loader = unittest.TestLoader()
-        tests = loader.loadTestsFromNames(args.testname)
-        suite.addTests(tests)
+        test_cases = loader.loadTestsFromNames(args.testname)
     else:
-        loader = unittest.TestLoader()
-        tests = loader.discover(start_dir=testdir, pattern='*_test*.py')
-        suite.addTests(tests)
+        test_cases = loader.discover(start_dir=testdir, pattern='*_test*.py')
+
+    # extract list of test classes so we can check/run them manually one by one
+    tests = []
+    tests = _get_tests_from_suite(test_cases, tests)
+
+    # for some reason overrides_hack will fail if we import this at the start
+    # of the file
+    from utils import TestTags
+
+    for test in tests:
+        # get tags and (possibly) skip the test
+        tags = _get_test_tags(test)
+
+        if TestTags.SLOW in tags and args.fast:
+            _print_skip_message(test, TestTags.SLOW)
+            continue
+        if TestTags.UNSTABLE in tags and not args.lucky:
+            _print_skip_message(test, TestTags.UNSTABLE)
+            continue
+        if TestTags.UNSAFE in tags or TestTags.EXTRADEPS in tags and not args.jenkins:
+            _print_skip_message(test, TestTags.UNSAFE)
+            continue
+        if TestTags.EXTRADEPS in tags and not args.jenkins:
+            _print_skip_message(test, TestTags.EXTRADEPS)
+            continue
+
+        if args.core and TestTags.CORE not in tags and TestTags.REGRESSION not in tags:
+            _print_skip_message(test, TestTags.CORE)
+            continue
+
+        # finally add the test to the suite
+        suite.addTest(test)
+
     result = unittest.TextTestRunner(verbosity=2, failfast=args.stop).run(suite)
 
     # dump cropped journal to log file
-- 
2.20.1


From f4dd0bfff61d117096c1802ab793b3ab9a31dae3 Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Thu, 14 Mar 2019 09:43:37 +0100
Subject: [PATCH 03/10] Use the new test tags in tests

---
 tests/btrfs_test.py     | 14 ++++++--
 tests/crypto_test.py    | 73 +++++++++++++++++++++--------------------
 tests/dm_test.py        |  5 ++-
 tests/fs_test.py        | 18 +++++++---
 tests/kbd_test.py       | 24 +++++++-------
 tests/library_test.py   | 13 +++++---
 tests/loop_test.py      |  5 ++-
 tests/lvm_dbus_tests.py | 44 ++++++++++++++++---------
 tests/lvm_test.py       | 44 ++++++++++++++++---------
 tests/mdraid_test.py    | 38 ++++++++++++---------
 tests/mpath_test.py     |  5 ++-
 tests/nvdimm_test.py    |  9 +++--
 tests/overrides_test.py |  5 +++
 tests/part_test.py      | 11 ++++++-
 tests/s390_test.py      |  6 +++-
 tests/swap_test.py      |  7 +++-
 tests/utils_test.py     | 10 ++++--
 tests/vdo_test.py       | 13 ++++++--
 18 files changed, 227 insertions(+), 117 deletions(-)

diff --git a/tests/btrfs_test.py b/tests/btrfs_test.py
index ac1594f..eab25db 100644
--- a/tests/btrfs_test.py
+++ b/tests/btrfs_test.py
@@ -10,7 +10,7 @@ from distutils.version import LooseVersion
 from distutils.spawn import find_executable
 
 import overrides_hack
-from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path, skip_on, mount, umount, run_command
+from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path, mount, umount, run_command, TestTags, tag_test
 from gi.repository import GLib, BlockDev
 
 TEST_MNT = "/tmp/libblockdev_test_mnt"
@@ -74,6 +74,7 @@ class BtrfsTestCase(unittest.TestCase):
         return LooseVersion(m.groups()[0])
 
 class BtrfsTestCreateQuerySimple(BtrfsTestCase):
+    @tag_test(TestTags.CORE)
     def test_create_and_query_volume(self):
         """Verify that btrfs volume creation and querying works"""
 
@@ -180,6 +181,7 @@ class BtrfsTestAddRemoveDevice(BtrfsTestCase):
         self.assertEqual(len(devs), 1)
 
 class BtrfsTestCreateDeleteSubvolume(BtrfsTestCase):
+    @tag_test(TestTags.CORE)
     def test_create_delete_subvolume(self):
         """Verify that it is possible to create/delete subvolume"""
 
@@ -306,6 +308,7 @@ class BtrfsTestSetDefaultSubvolumeID(BtrfsTestCase):
         self.assertEqual(ret, 5)
 
 class BtrfsTestListDevices(BtrfsTestCase):
+    @tag_test(TestTags.CORE)
     def test_list_devices(self):
         """Verify that it is possible to get info about devices"""
 
@@ -324,6 +327,7 @@ class BtrfsTestListDevices(BtrfsTestCase):
         self.assertTrue(devs[1].used >= 0)
 
 class BtrfsTestListSubvolumes(BtrfsTestCase):
+    @tag_test(TestTags.CORE)
     def test_list_subvolumes(self):
         """Verify that it is possible to get info about subvolumes"""
 
@@ -344,6 +348,7 @@ class BtrfsTestListSubvolumes(BtrfsTestCase):
         self.assertEqual(subvols[0].path, "subvol1")
 
 class BtrfsTestFilesystemInfo(BtrfsTestCase):
+    @tag_test(TestTags.CORE)
     def test_filesystem_info(self):
         """Verify that it is possible to get filesystem info"""
 
@@ -376,6 +381,7 @@ class BtrfsTestFilesystemInfoNoLabel(BtrfsTestCase):
         self.assertTrue(info.used >= 0)
 
 class BtrfsTestMkfs(BtrfsTestCase):
+    @tag_test(TestTags.CORE)
     def test_mkfs(self):
         """Verify that it is possible to create a btrfs filesystem"""
 
@@ -489,7 +495,6 @@ class BtrfsTooSmallTestCase (BtrfsTestCase):
             pass
         os.unlink(self.dev_file2)
 
-    @skip_on("fedora", "25", reason="Min sizes for Btrfs are different on F25")
     def test_create_too_small(self):
         """Verify that an attempt to create BTRFS on a too small device fails"""
 
@@ -527,7 +532,6 @@ class BtrfsJustBigEnoughTestCase (BtrfsTestCase):
             pass
         os.unlink(self.dev_file2)
 
-    @skip_on("fedora", "25", reason="Min sizes for Btrfs are different on F25")
     def test_create_just_enough(self):
         """Verify that creating BTRFS on a just big enough devices works"""
 
@@ -541,6 +545,7 @@ class FakeBtrfsUtilsTestCase(BtrfsTestCase):
     def setUp(self):
         pass
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_list_subvols_weird_docker_data(self):
         """Verify that list_subvolumes works as expected on weird data from one Docker use case"""
 
@@ -562,6 +567,7 @@ class BTRFSUnloadTest(BtrfsTestCase):
         # tests
         self.addCleanup(BlockDev.reinit, self.requested_plugins, True, None)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_low_version(self):
         """Verify that checking the minimum BTRFS version works as expected"""
 
@@ -579,6 +585,7 @@ class BTRFSUnloadTest(BtrfsTestCase):
         self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
         self.assertIn("btrfs", BlockDev.get_available_plugin_names())
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_new_version_format(self):
         """Verify that checking the minimum BTRFS version works as expected with the new format"""
 
@@ -594,6 +601,7 @@ class BTRFSUnloadTest(BtrfsTestCase):
         BlockDev.reinit(self.requested_plugins, True, None)
         self.assertIn("btrfs", BlockDev.get_available_plugin_names())
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_no_btrfs(self):
         """Verify that checking btrfs tool availability works as expected"""
 
diff --git a/tests/crypto_test.py b/tests/crypto_test.py
index 7320e74..b062505 100644
--- a/tests/crypto_test.py
+++ b/tests/crypto_test.py
@@ -9,7 +9,7 @@ import locale
 import re
 import tarfile
 
-from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, skip_on, get_avail_locales, requires_locales, run_command, read_file
+from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, skip_on, get_avail_locales, requires_locales, run_command, read_file, TestTags, tag_test
 from gi.repository import BlockDev, GLib
 
 PASSWD = "myshinylittlepassword"
@@ -92,6 +92,7 @@ class CryptoTestGenerateBackupPassphrase(CryptoTestCase):
         # we don't need block devices for this test
         pass
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_generate_backup_passhprase(self):
         """Verify that backup passphrase generation works as expected"""
 
@@ -101,7 +102,7 @@ class CryptoTestGenerateBackupPassphrase(CryptoTestCase):
             six.assertRegex(self, bp, exp)
 
 class CryptoTestFormat(CryptoTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW, TestTags.CORE)
     def test_luks_format(self):
         """Verify that formating device as LUKS works"""
 
@@ -121,7 +122,7 @@ class CryptoTestFormat(CryptoTestCase):
         succ = BlockDev.crypto_luks_format_blob(self.loop_dev, "aes-cbc-essiv:sha256", 0, [ord(c) for c in PASSWD], 0)
         self.assertTrue(succ)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW, TestTags.CORE)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_luks2_format(self):
         """Verify that formating device as LUKS 2 works"""
@@ -222,7 +223,7 @@ class CryptoTestFormat(CryptoTestCase):
         self.assertEqual(int(m.group(1)), 5)
 
 class CryptoTestResize(CryptoTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_luks_resize(self):
         """Verify that resizing LUKS device works"""
 
@@ -244,7 +245,7 @@ class CryptoTestResize(CryptoTestCase):
         succ = BlockDev.crypto_luks_close("libblockdevTestLUKS")
         self.assertTrue(succ)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_luks2_resize(self):
         """Verify that resizing LUKS 2 device works"""
@@ -304,11 +305,11 @@ class CryptoTestOpenClose(CryptoTestCase):
         succ = BlockDev.crypto_luks_close("libblockdevTestLUKS")
         self.assertTrue(succ)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW, TestTags.CORE)
     def test_luks_open_close(self):
         self._luks_open_close(self._luks_format)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW, TestTags.CORE)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_luks2_open_close(self):
         self._luks_open_close(self._luks2_format)
@@ -329,11 +330,11 @@ class CryptoTestAddKey(CryptoTestCase):
         succ = BlockDev.crypto_luks_add_key_blob(self.loop_dev, [ord(c) for c in PASSWD2], [ord(c) for c in PASSWD3])
         self.assertTrue(succ)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_luks_add_key(self):
         self._add_key(self._luks_format)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_luks2_add_key(self):
         self._add_key(self._luks2_format)
@@ -360,11 +361,11 @@ class CryptoTestRemoveKey(CryptoTestCase):
         succ = BlockDev.crypto_luks_remove_key_blob(self.loop_dev, [ord(c) for c in PASSWD2])
         self.assertTrue(succ)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_luks_remove_key(self):
         self._remove_key(self._luks_format)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_luks2_remove_key(self):
         self._remove_key(self._luks2_format)
@@ -380,7 +381,7 @@ class CryptoTestErrorLocale(CryptoTestCase):
         if self._orig_loc:
             locale.setlocale(locale.LC_ALL, self._orig_loc)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     @requires_locales({"cs_CZ.UTF-8"})
     def test_error_locale_key(self):
         """Verify that the error msg is locale agnostic"""
@@ -407,11 +408,11 @@ class CryptoTestChangeKey(CryptoTestCase):
         succ = BlockDev.crypto_luks_change_key_blob(self.loop_dev, [ord(c) for c in PASSWD2], [ord(c) for c in PASSWD3])
         self.assertTrue(succ)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_luks_change_key(self):
         self._change_key(self._luks_format)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_luks2_change_key(self):
         self._change_key(self._luks2_format)
@@ -432,11 +433,11 @@ class CryptoTestIsLuks(CryptoTestCase):
         is_luks = BlockDev.crypto_device_is_luks(self.loop_dev2)
         self.assertFalse(is_luks)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_is_luks(self):
         self._is_luks(self._luks_format)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_is_luks2(self):
         self._is_luks(self._luks2_format)
@@ -468,11 +469,11 @@ class CryptoTestLuksStatus(CryptoTestCase):
         with self.assertRaises(GLib.GError):
             BlockDev.crypto_luks_status("libblockdevTestLUKS")
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_luks_status(self):
         self._luks_status(self._luks_format)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_luks2_status(self):
         self._luks_status(self._luks2_format)
@@ -490,18 +491,18 @@ class CryptoTestGetUUID(CryptoTestCase):
         with self.assertRaises(GLib.GError):
             uuid = BlockDev.crypto_luks_uuid(self.loop_dev2)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_luks_get_uuid(self):
         self._get_uuid(self._luks_format)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_luks2_get_uuid(self):
         self._get_uuid(self._luks2_format)
 
 class CryptoTestGetMetadataSize(CryptoTestCase):
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_luks2_get_metadata_size(self):
         """Verify that getting LUKS 2 device metadata size works"""
@@ -521,7 +522,7 @@ class CryptoTestGetMetadataSize(CryptoTestCase):
         offset = int(m.group(1))
         self.assertEquals(meta_size, offset, "LUKS 2 metadata sizes differ")
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_luks_get_metadata_size(self):
         """Verify that getting LUKS device metadata size works"""
 
@@ -570,11 +571,11 @@ class CryptoTestLuksOpenRW(CryptoTestCase):
         succ = BlockDev.crypto_luks_close("libblockdevTestLUKS")
         self.assertTrue(succ)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_luks_open_rw(self):
         self._luks_open_rw(self._luks_format)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_luks2_open_rw(self):
         self._luks_open_rw(self._luks2_format)
@@ -609,7 +610,7 @@ class CryptoTestEscrow(CryptoTestCase):
             '-a', '-o', self.public_cert])
         self.addCleanup(os.unlink, self.public_cert)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     @skip_on(("centos", "enterprise_linux"), "7", reason="volume_key asks for password in non-interactive mode on this release")
     @skip_on("debian", reason="volume_key asks for password in non-interactive mode on this release")
     def test_escrow_packet(self):
@@ -654,7 +655,7 @@ class CryptoTestEscrow(CryptoTestCase):
         succ = BlockDev.crypto_luks_open(self.loop_dev, 'libblockdevTestLUKS', PASSWD3, None)
         self.assertTrue(succ)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_backup_passphrase(self):
         """Verify that a backup passphrase can be created for a device"""
         succ = BlockDev.crypto_luks_format(self.loop_dev, None, 0, PASSWD, None, 0)
@@ -735,12 +736,12 @@ class CryptoTestSuspendResume(CryptoTestCase):
         succ = BlockDev.crypto_luks_close("libblockdevTestLUKS")
         self.assertTrue(succ)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_luks_suspend_resume(self):
         """Verify that suspending/resuming LUKS device works"""
         self._luks_suspend_resume(self._luks_format)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_luks2_suspend_resume(self):
         """Verify that suspending/resuming LUKS 2 device works"""
@@ -781,12 +782,12 @@ class CryptoTestKillSlot(CryptoTestCase):
         succ = BlockDev.crypto_luks_close("libblockdevTestLUKS")
         self.assertTrue(succ)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_luks_kill_slot(self):
         """Verify that killing a key slot on LUKS device works"""
         self._luks_kill_slot(self._luks_format)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_luks2_kill_slot(self):
         """Verify that killing a key slot on LUKS 2 device works"""
@@ -836,19 +837,19 @@ class CryptoTestHeaderBackupRestore(CryptoTestCase):
         succ = BlockDev.crypto_luks_close("libblockdevTestLUKS")
         self.assertTrue(succ)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_luks_header_backup_restore(self):
         """Verify that header backup/restore with LUKS works"""
         self._luks_header_backup_restore(self._luks_format)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_luks2_header_backup_restore(self):
         """Verify that header backup/restore with LUKS2 works"""
         self._luks_header_backup_restore(self._luks2_format)
 
 class CryptoTestInfo(CryptoTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW, TestTags.CORE)
     def test_luks_format(self):
         """Verify that we can get information about a LUKS device"""
 
@@ -872,7 +873,7 @@ class CryptoTestInfo(CryptoTestCase):
         succ = BlockDev.crypto_luks_close("libblockdevTestLUKS")
         self.assertTrue(succ)
 
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW, TestTags.CORE)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_luks2_format(self):
         """Verify that we can get information about a LUKS 2 device"""
@@ -903,7 +904,7 @@ class CryptoTestInfo(CryptoTestCase):
         self.assertTrue(succ)
 
 class CryptoTestIntegrity(CryptoTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     @unittest.skipUnless(HAVE_LUKS2, "LUKS 2 not supported")
     def test_luks2_integrity(self):
         """Verify that we can get create a LUKS 2 device with integrity"""
@@ -983,6 +984,7 @@ class CryptoTestTrueCrypt(CryptoTestCase):
         if not succ:
             raise RuntimeError("Failed to tear down loop device used for testing")
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_truecrypt_open_close(self):
         """Verify that opening/closing TrueCrypt device works"""
 
@@ -1003,6 +1005,7 @@ class CryptoTestTrueCrypt(CryptoTestCase):
         self.assertTrue(succ)
         self.assertFalse(os.path.exists("/dev/mapper/libblockdevTestTC"))
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_veracrypt_open_close(self):
         """Verify that opening/closing VeraCrypt device works"""
 
diff --git a/tests/dm_test.py b/tests/dm_test.py
index 0dd1861..936e305 100644
--- a/tests/dm_test.py
+++ b/tests/dm_test.py
@@ -2,7 +2,7 @@ import unittest
 import os
 import overrides_hack
 
-from utils import run, create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path
+from utils import run, create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path, TestTags, tag_test
 from gi.repository import BlockDev, GLib
 
 
@@ -65,6 +65,7 @@ class DevMapperGetSubsystemFromName(DevMapperTestCase):
         self.assertEqual(subsystem, "CRYPT")
 
 class DevMapperCreateRemoveLinear(DevMapperTestCase):
+    @tag_test(TestTags.CORE)
     def test_create_remove_linear(self):
         """Verify that it is possible to create new linear mapping and remove it"""
 
@@ -120,6 +121,7 @@ class DMUnloadTest(DevMapperTestCase):
         # tests
         self.addCleanup(BlockDev.reinit, self.requested_plugins, True, None)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_low_version(self):
         """Verify that checking the minimum dmsetup version works as expected"""
 
@@ -137,6 +139,7 @@ class DMUnloadTest(DevMapperTestCase):
         self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
         self.assertIn("dm", BlockDev.get_available_plugin_names())
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_no_dm(self):
         """Verify that checking dmsetup tool availability works as expected"""
 
diff --git a/tests/fs_test.py b/tests/fs_test.py
index d3f3353..adcf312 100644
--- a/tests/fs_test.py
+++ b/tests/fs_test.py
@@ -5,7 +5,7 @@ import subprocess
 import tempfile
 from contextlib import contextmanager
 import utils
-from utils import run, create_sparse_tempfile, mount, umount, unstable_test
+from utils import run, create_sparse_tempfile, mount, umount, TestTags, tag_test
 import six
 import overrides_hack
 
@@ -97,6 +97,7 @@ class FSTestCase(unittest.TestCase):
             self.fail("Failed to set %s read-write" % device)
 
 class TestGenericWipe(FSTestCase):
+    @tag_test(TestTags.CORE)
     def test_generic_wipe(self):
         """Verify that generic signature wipe works as expected"""
 
@@ -210,16 +211,19 @@ class ExtTestMkfs(FSTestCase):
 
         BlockDev.fs_wipe(self.loop_dev, True)
 
+    @tag_test(TestTags.CORE)
     def test_ext2_mkfs(self):
         """Verify that it is possible to create a new ext2 file system"""
         self._test_ext_mkfs(mkfs_function=BlockDev.fs_ext2_mkfs,
                             ext_version="ext2")
 
+    @tag_test(TestTags.CORE)
     def test_ext3_mkfs(self):
         """Verify that it is possible to create a new ext3 file system"""
         self._test_ext_mkfs(mkfs_function=BlockDev.fs_ext3_mkfs,
                             ext_version="ext3")
 
+    @tag_test(TestTags.CORE)
     def test_ext4_mkfs(self):
         """Verify that it is possible to create a new ext4 file system"""
         self._test_ext_mkfs(mkfs_function=BlockDev.fs_ext4_mkfs,
@@ -385,16 +389,19 @@ class ExtGetInfo(FSTestCase):
             self.assertTrue(fi.uuid)
             self.assertTrue(fi.state, "clean")
 
+    @tag_test(TestTags.CORE)
     def test_ext2_get_info(self):
         """Verify that it is possible to get info about an ext2 file system"""
         self._test_ext_get_info(mkfs_function=BlockDev.fs_ext2_mkfs,
                                 info_function=BlockDev.fs_ext2_get_info)
 
+    @tag_test(TestTags.CORE)
     def test_ext3_get_info(self):
         """Verify that it is possible to get info about an ext3 file system"""
         self._test_ext_get_info(mkfs_function=BlockDev.fs_ext3_mkfs,
                                 info_function=BlockDev.fs_ext3_get_info)
 
+    @tag_test(TestTags.CORE)
     def test_ext4_get_info(self):
         """Verify that it is possible to get info about an ext4 file system"""
         self._test_ext_get_info(mkfs_function=BlockDev.fs_ext4_mkfs,
@@ -511,6 +518,7 @@ class ExtResize(FSTestCase):
                               resize_function=BlockDev.fs_ext4_resize)
 
 class XfsTestMkfs(FSTestCase):
+    @tag_test(TestTags.CORE)
     def test_xfs_mkfs(self):
         """Verify that it is possible to create a new xfs file system"""
 
@@ -597,6 +605,7 @@ class XfsTestRepair(FSTestCase):
         self.assertTrue(succ)
 
 class XfsGetInfo(FSTestCase):
+    @tag_test(TestTags.CORE)
     def test_xfs_get_info(self):
         """Verify that it is possible to get info about an xfs file system"""
 
@@ -970,6 +979,7 @@ class MountTest(FSTestCase):
         if ret != 0:
             self.fail("Failed to remove user user '%s': %s" % (self.username, err))
 
+    @tag_test(TestTags.CORE)
     def test_mount(self):
         """ Test basic mounting and unmounting """
 
@@ -1053,7 +1063,7 @@ class MountTest(FSTestCase):
             BlockDev.fs_mount(loop_dev, tmp_dir, None, "rw", None)
         self.assertFalse(os.path.ismount(tmp_dir))
 
-    @unittest.skipUnless("JENKINS_HOME" in os.environ, "skipping test that modifies system configuration")
+    @tag_test(TestTags.UNSAFE)
     def test_mount_fstab(self):
         """ Test mounting and unmounting devices in /etc/fstab """
         # this test will change /etc/fstab, we want to revert the changes when it finishes
@@ -1088,7 +1098,7 @@ class MountTest(FSTestCase):
         self.assertTrue(succ)
         self.assertFalse(os.path.ismount(tmp))
 
-    @unittest.skipUnless("JENKINS_HOME" in os.environ, "skipping test that modifies system configuration")
+    @tag_test(TestTags.UNSAFE)
     def test_mount_fstab_user(self):
         """ Test mounting and unmounting devices in /etc/fstab as non-root user """
         # this test will change /etc/fstab, we want to revert the changes when it finishes
@@ -1360,7 +1370,7 @@ class GenericResize(FSTestCase):
                                   fs_info_func=info_prepare,
                                   info_size_func=expected_size)
 
-    @unstable_test
+    @tag_test(TestTags.UNSTABLE)
     def test_vfat_generic_resize(self):
         """Test generic resize function with a vfat file system"""
         self._test_generic_resize(mkfs_function=BlockDev.fs_vfat_mkfs)
diff --git a/tests/kbd_test.py b/tests/kbd_test.py
index b6cfb3c..5e872c4 100644
--- a/tests/kbd_test.py
+++ b/tests/kbd_test.py
@@ -4,7 +4,7 @@ import re
 import time
 from contextlib import contextmanager
 from distutils.version import LooseVersion
-from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, wipe_all, fake_path, read_file, skip_on, unstable_test
+from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, wipe_all, fake_path, read_file, skip_on, TestTags, tag_test
 from bytesize import bytesize
 import overrides_hack
 
@@ -66,7 +66,7 @@ class KbdZRAMTestCase(unittest.TestCase):
 
 class KbdZRAMDevicesTestCase(KbdZRAMTestCase):
     @unittest.skipUnless(_can_load_zram(), "cannot load the 'zram' module")
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_create_destroy_devices(self):
         # the easiest case
         with _track_module_load(self, "zram", "_loaded_zram_module"):
@@ -113,7 +113,7 @@ class KbdZRAMDevicesTestCase(KbdZRAMTestCase):
             time.sleep(1)
 
     @unittest.skipUnless(_can_load_zram(), "cannot load the 'zram' module")
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_zram_add_remove_device(self):
         """Verify that it is possible to add and remove a zram device"""
 
@@ -268,6 +268,7 @@ class KbdBcacheNodevTestCase(unittest.TestCase):
             BlockDev.reinit(cls.requested_plugins, True, None)
 
     @skip_on(("centos", "enterprise_linux"))
+    @tag_test(TestTags.NOSTORAGE)
     def test_bcache_mode_str_bijection(self):
         """Verify that it's possible to transform between cache modes and their string representations"""
 
@@ -333,7 +334,7 @@ class KbdBcacheTestCase(unittest.TestCase):
 
 class KbdTestBcacheCreate(KbdBcacheTestCase):
     @skip_on(("centos", "enterprise_linux"))
-    @unstable_test
+    @tag_test(TestTags.UNSTABLE)
     def test_bcache_create_destroy(self):
         """Verify that it's possible to create and destroy a bcache device"""
 
@@ -352,7 +353,7 @@ class KbdTestBcacheCreate(KbdBcacheTestCase):
         wipe_all(self.loop_dev, self.loop_dev2)
 
     @skip_on(("centos", "enterprise_linux"))
-    @unstable_test
+    @tag_test(TestTags.UNSTABLE)
     def test_bcache_create_destroy_full_path(self):
         """Verify that it's possible to create and destroy a bcache device with full device path"""
 
@@ -372,7 +373,7 @@ class KbdTestBcacheCreate(KbdBcacheTestCase):
 
 class KbdTestBcacheAttachDetach(KbdBcacheTestCase):
     @skip_on(("centos", "enterprise_linux"))
-    @unstable_test
+    @tag_test(TestTags.UNSTABLE)
     def test_bcache_attach_detach(self):
         """Verify that it's possible to detach/attach a cache from/to a bcache device"""
 
@@ -398,7 +399,7 @@ class KbdTestBcacheAttachDetach(KbdBcacheTestCase):
         wipe_all(self.loop_dev, self.loop_dev2)
 
     @skip_on(("centos", "enterprise_linux"))
-    @unstable_test
+    @tag_test(TestTags.UNSTABLE)
     def test_bcache_attach_detach_full_path(self):
         """Verify that it's possible to detach/attach a cache from/to a bcache device with full device path"""
 
@@ -424,7 +425,7 @@ class KbdTestBcacheAttachDetach(KbdBcacheTestCase):
         wipe_all(self.loop_dev, self.loop_dev2)
 
     @skip_on(("centos", "enterprise_linux"))
-    @unstable_test
+    @tag_test(TestTags.UNSTABLE)
     def test_bcache_detach_destroy(self):
         """Verify that it's possible to destroy a bcache device with no cache attached"""
 
@@ -448,7 +449,7 @@ class KbdTestBcacheAttachDetach(KbdBcacheTestCase):
 
 class KbdTestBcacheGetSetMode(KbdBcacheTestCase):
     @skip_on(("centos", "enterprise_linux"))
-    @unstable_test
+    @tag_test(TestTags.UNSTABLE)
     def test_bcache_get_set_mode(self):
         """Verify that it is possible to get and set Bcache mode"""
 
@@ -505,7 +506,7 @@ class KbdTestBcacheStatusTest(KbdBcacheTestCase):
         return sum(int(read_file(os.path.realpath(c) + '/../size')) for c in caches)
 
     @skip_on(("centos", "enterprise_linux"))
-    @unstable_test
+    @tag_test(TestTags.UNSTABLE)
     def test_bcache_status(self):
         succ, dev = BlockDev.kbd_bcache_create(self.loop_dev, self.loop_dev2, None)
         self.assertTrue(succ)
@@ -538,7 +539,7 @@ class KbdTestBcacheStatusTest(KbdBcacheTestCase):
 
 class KbdTestBcacheBackingCacheDevTest(KbdBcacheTestCase):
     @skip_on(("centos", "enterprise_linux"))
-    @unstable_test
+    @tag_test(TestTags.UNSTABLE)
     def test_bcache_backing_cache_dev(self):
         """Verify that is is possible to get the backing and cache devices for a Bcache"""
 
@@ -566,6 +567,7 @@ class KbdUnloadTest(KbdBcacheTestCase):
         self.addCleanup(BlockDev.reinit, self.requested_plugins, True, None)
 
     @skip_on(("centos", "enterprise_linux"))
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_no_bcache_progs(self):
         """Verify that checking the availability of make-bcache works as expected"""
 
diff --git a/tests/library_test.py b/tests/library_test.py
index 159031a..fa33b53 100644
--- a/tests/library_test.py
+++ b/tests/library_test.py
@@ -2,7 +2,7 @@ import os
 import unittest
 import re
 import overrides_hack
-from utils import fake_path
+from utils import fake_path, TestTags, tag_test
 
 from gi.repository import GLib, BlockDev
 
@@ -40,7 +40,7 @@ class LibraryOpsTestCase(unittest.TestCase):
         BlockDev.reinit(self.requested_plugins, True, None)
 
     # recompiles the LVM plugin
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW, TestTags.CORE)
     def test_reload(self):
         """Verify that reloading plugins works as expected"""
 
@@ -72,7 +72,7 @@ class LibraryOpsTestCase(unittest.TestCase):
         self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
 
     # recompiles the LVM plugin
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_force_plugin(self):
         """Verify that forcing plugin to be used works as expected"""
 
@@ -118,7 +118,7 @@ class LibraryOpsTestCase(unittest.TestCase):
         self.assertEqual(BlockDev.lvm_get_max_lv_size(), orig_max_size)
 
     # recompiles the LVM plugin
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_plugin_priority(self):
         """Verify that preferring plugin to be used works as expected"""
 
@@ -181,7 +181,7 @@ class LibraryOpsTestCase(unittest.TestCase):
         os.system ("rm -f src/plugins/.libs/libbd_lvm2.so")
 
     # recompiles the LVM plugin
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_plugin_fallback(self):
         """Verify that fallback when loading plugins works as expected"""
 
@@ -250,6 +250,7 @@ class LibraryOpsTestCase(unittest.TestCase):
 
         self.log += msg + "\n"
 
+    @tag_test(TestTags.CORE)
     def test_logging_setup(self):
         """Verify that setting up logging works as expected"""
 
@@ -280,6 +281,7 @@ class LibraryOpsTestCase(unittest.TestCase):
         self.assertIn("stderr[%s]:" % task_id2, self.log)
         self.assertIn("...done [%s] (exit code: 0)" % task_id2, self.log)
 
+    @tag_test(TestTags.CORE)
     def test_require_plugins(self):
         """Verify that loading only required plugins works as expected"""
 
@@ -290,6 +292,7 @@ class LibraryOpsTestCase(unittest.TestCase):
         self.assertEqual(BlockDev.get_available_plugin_names(), ["swap"])
         self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
 
+    @tag_test(TestTags.CORE)
     def test_not_implemented(self):
         """Verify that unloaded/unimplemented functions report errors"""
 
diff --git a/tests/loop_test.py b/tests/loop_test.py
index 9e9d9ac..5aaf928 100644
--- a/tests/loop_test.py
+++ b/tests/loop_test.py
@@ -3,7 +3,7 @@ import unittest
 import time
 import overrides_hack
 
-from utils import create_sparse_tempfile
+from utils import create_sparse_tempfile, TestTags, tag_test
 from gi.repository import BlockDev, GLib
 
 
@@ -31,6 +31,7 @@ class LoopTestCase(unittest.TestCase):
         os.unlink(self.dev_file)
 
 class LoopTestSetupBasic(LoopTestCase):
+    @tag_test(TestTags.CORE)
     def testLoop_setup_teardown_basic(self):
         """Verify that basic loop_setup and loop_teardown work as expected"""
 
@@ -97,6 +98,7 @@ class LoopTestSetupReadOnly(LoopTestCase):
 # XXX: any sane way how to test part_probe=True/False?
 
 class LoopTestGetLoopName(LoopTestCase):
+    @tag_test(TestTags.CORE)
     def testLoop_get_loop_name(self):
         """Verify that loop_get_loop_name works as expected"""
 
@@ -107,6 +109,7 @@ class LoopTestGetLoopName(LoopTestCase):
         self.assertEqual(ret_loop, self.loop)
 
 class LoopTestGetBackingFile(LoopTestCase):
+    @tag_test(TestTags.CORE)
     def testLoop_get_backing_file(self):
         """Verify that loop_get_backing_file works as expected"""
 
diff --git a/tests/lvm_dbus_tests.py b/tests/lvm_dbus_tests.py
index 7c3b4cc..625a392 100644
--- a/tests/lvm_dbus_tests.py
+++ b/tests/lvm_dbus_tests.py
@@ -8,7 +8,7 @@ import re
 import subprocess
 from itertools import chain
 
-from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, skip_on, run_command
+from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, run_command, TestTags, tag_test
 from gi.repository import BlockDev, GLib
 
 import dbus
@@ -38,6 +38,7 @@ class LvmNoDevTestCase(LVMTestCase):
         super(LvmNoDevTestCase, self).__init__(*args, **kwargs)
         self._log = ""
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_is_supported_pe_size(self):
         """Verify that lvm_is_supported_pe_size works as expected"""
 
@@ -53,12 +54,14 @@ class LvmNoDevTestCase(LVMTestCase):
         self.assertFalse(BlockDev.lvm_is_supported_pe_size(65535))
         self.assertFalse(BlockDev.lvm_is_supported_pe_size(32 * 1024**3))
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_get_supported_pe_sizes(self):
         """Verify that supported PE sizes are really supported"""
 
         for size in BlockDev.lvm_get_supported_pe_sizes():
             self.assertTrue(BlockDev.lvm_is_supported_pe_size(size))
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_get_max_lv_size(self):
         """Verify that max LV size is correctly determined"""
 
@@ -71,6 +74,7 @@ class LvmNoDevTestCase(LVMTestCase):
 
         self.assertEqual(BlockDev.lvm_get_max_lv_size(), expected)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_round_size_to_pe(self):
         """Verify that round_size_to_pe works as expected"""
 
@@ -95,6 +99,7 @@ class LvmNoDevTestCase(LVMTestCase):
         self.assertEqual(BlockDev.lvm_round_size_to_pe(biggest_multiple - (2 * 4 * 1024**2) + 1, 4 * 1024**2, False),
                          biggest_multiple - (2 * 4 * 1024**2))
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_get_lv_physical_size(self):
         """Verify that get_lv_physical_size works as expected"""
 
@@ -108,6 +113,7 @@ class LvmNoDevTestCase(LVMTestCase):
         self.assertEqual(BlockDev.lvm_get_lv_physical_size(11 * 1024**2, 4 * 1024**2),
                          12 * 1024**2)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_get_thpool_padding(self):
         """Verify that get_thpool_padding works as expected"""
 
@@ -121,6 +127,7 @@ class LvmNoDevTestCase(LVMTestCase):
         self.assertEqual(BlockDev.lvm_get_thpool_padding(11 * 1024**2, 4 * 1024**2, True),
                          expected_padding)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_get_thpool_meta_size(self):
         """Verify that getting recommended thin pool metadata size works as expected"""
 
@@ -139,6 +146,7 @@ class LvmNoDevTestCase(LVMTestCase):
         self.assertEqual(BlockDev.lvm_get_thpool_meta_size (100 * 1024**2, 128 * 1024, 100),
                          BlockDev.LVM_MIN_THPOOL_MD_SIZE)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_is_valid_thpool_md_size(self):
         """Verify that is_valid_thpool_md_size works as expected"""
 
@@ -149,6 +157,7 @@ class LvmNoDevTestCase(LVMTestCase):
         self.assertFalse(BlockDev.lvm_is_valid_thpool_md_size(1 * 1024**2))
         self.assertFalse(BlockDev.lvm_is_valid_thpool_md_size(17 * 1024**3))
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_is_valid_thpool_chunk_size(self):
         """Verify that is_valid_thpool_chunk_size works as expected"""
 
@@ -167,6 +176,7 @@ class LvmNoDevTestCase(LVMTestCase):
     def _store_log(self, lvl, msg):
         self._log += str((lvl, msg))
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_get_set_global_config(self):
         """Verify that getting and setting global config works as expected"""
 
@@ -207,6 +217,7 @@ class LvmNoDevTestCase(LVMTestCase):
         succ = BlockDev.lvm_set_global_config(None)
         self.assertTrue(succ)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_cache_get_default_md_size(self):
         """Verify that default cache metadata size is calculated properly"""
 
@@ -215,6 +226,7 @@ class LvmNoDevTestCase(LVMTestCase):
         self.assertEqual(BlockDev.lvm_cache_get_default_md_size(80 * 1024**3), (80 * 1024**3) // 1000)
         self.assertEqual(BlockDev.lvm_cache_get_default_md_size(6 * 1024**3), 8 * 1024**2)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_cache_mode_bijection(self):
         """Verify that cache modes and their string representations map to each other"""
 
@@ -275,6 +287,7 @@ class LvmPVonlyTestCase(LVMTestCase):
 
 @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmTestPVcreateRemove(LvmPVonlyTestCase):
+    @tag_test(TestTags.CORE)
     def test_pvcreate_and_pvremove(self):
         """Verify that it's possible to create and destroy a PV"""
 
@@ -380,6 +393,7 @@ class LvmPVVGTestCase(LvmPVonlyTestCase):
 
 @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmTestVGcreateRemove(LvmPVVGTestCase):
+    @tag_test(TestTags.CORE)
     def test_vgcreate_vgremove(self):
         """Verify that it is possible to create and destroy a VG"""
 
@@ -406,6 +420,7 @@ class LvmTestVGcreateRemove(LvmPVVGTestCase):
         with self.assertRaises(GLib.GError):
             BlockDev.lvm_vgremove("testVG", None)
 
+@unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmTestVGrename(LvmPVVGTestCase):
     def test_vgrename(self):
         """Verify that it is possible to rename a VG"""
@@ -465,7 +480,6 @@ class LvmTestVGactivateDeactivate(LvmPVVGTestCase):
 
 @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmTestVGextendReduce(LvmPVVGTestCase):
-    @skip_on("fedora", "27", reason="LVM is broken in many ways on rawhide")
     def test_vgextend_vgreduce(self):
         """Verify that it is possible to extend/reduce a VG"""
 
@@ -571,6 +585,7 @@ class LvmPVVGLVTestCase(LvmPVVGTestCase):
 
 @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmTestLVcreateRemove(LvmPVVGLVTestCase):
+    @tag_test(TestTags.CORE)
     def test_lvcreate_lvremove(self):
         """Verify that it's possible to create/destroy an LV"""
 
@@ -619,6 +634,7 @@ class LvmTestLVcreateRemove(LvmPVVGLVTestCase):
         with self.assertRaises(GLib.GError):
             BlockDev.lvm_lvremove("testVG", "testLV", True, None)
 
+@unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmTestLVcreateWithExtra(LvmPVVGLVTestCase):
     def __init__(self, *args, **kwargs):
         LvmPVVGLVTestCase.__init__(self, *args, **kwargs)
@@ -669,7 +685,6 @@ class LvmTestLVcreateWithExtra(LvmPVVGLVTestCase):
 
 @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmTestLVcreateType(LvmPVVGLVTestCase):
-    @skip_on("fedora", "27", reason="LVM is broken in many ways on rawhide")
     def test_lvcreate_type(self):
         """Verify it's possible to create LVs with various types"""
 
@@ -842,7 +857,7 @@ class LvmTestLVrename(LvmPVVGLVTestCase):
 
 @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmTestLVsnapshots(LvmPVVGLVTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_snapshotcreate_lvorigin_snapshotmerge(self):
         """Verify that LV snapshot support works"""
 
@@ -957,6 +972,7 @@ class LvmTestLVsAll(LvmPVVGthpoolTestCase):
 
 @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmTestThpoolCreate(LvmPVVGthpoolTestCase):
+    @tag_test(TestTags.CORE)
     def test_thpoolcreate(self):
         """Verify that it is possible to create a thin pool"""
 
@@ -1056,6 +1072,7 @@ class LvmPVVGLVthLVTestCase(LvmPVVGthpoolTestCase):
 
 @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmTestThLVcreate(LvmPVVGLVthLVTestCase):
+    @tag_test(TestTags.CORE)
     def test_thlvcreate_thpoolname(self):
         """Verify that it is possible to create a thin LV and get its pool name"""
 
@@ -1142,8 +1159,7 @@ class LvmPVVGLVcachePoolTestCase(LvmPVVGLVTestCase):
 
 @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmPVVGLVcachePoolCreateRemoveTestCase(LvmPVVGLVcachePoolTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
-    @skip_on("fedora", "27", reason="LVM is broken in many ways on rawhide")
+    @tag_test(TestTags.SLOW)
     def test_cache_pool_create_remove(self):
         """Verify that is it possible to create and remove a cache pool"""
 
@@ -1169,8 +1185,7 @@ class LvmPVVGLVcachePoolCreateRemoveTestCase(LvmPVVGLVcachePoolTestCase):
 
 @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmTestCachePoolConvert(LvmPVVGLVcachePoolTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
-    @skip_on("fedora", "27", reason="LVM is broken in many ways on rawhide")
+    @tag_test(TestTags.SLOW)
     def test_cache_pool_convert(self):
         """Verify that it is possible to create a cache pool by conversion"""
 
@@ -1193,8 +1208,7 @@ class LvmTestCachePoolConvert(LvmPVVGLVcachePoolTestCase):
 
 @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmPVVGLVcachePoolAttachDetachTestCase(LvmPVVGLVcachePoolTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
-    @skip_on("fedora", "27", reason="LVM is broken in many ways on rawhide")
+    @tag_test(TestTags.SLOW)
     def test_cache_pool_attach_detach(self):
         """Verify that is it possible to attach and detach a cache pool"""
 
@@ -1235,8 +1249,7 @@ class LvmPVVGLVcachePoolAttachDetachTestCase(LvmPVVGLVcachePoolTestCase):
 
 @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmPVVGcachedLVTestCase(LvmPVVGLVTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
-    @skip_on("fedora", "27", reason="LVM is broken in many ways on rawhide")
+    @tag_test(TestTags.SLOW)
     def test_create_cached_lv(self):
         """Verify that it is possible to create a cached LV in a single step"""
 
@@ -1256,8 +1269,7 @@ class LvmPVVGcachedLVTestCase(LvmPVVGLVTestCase):
 
 @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmPVVGcachedLVpoolTestCase(LvmPVVGLVTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
-    @skip_on("fedora", "27", reason="LVM is broken in many ways on rawhide")
+    @tag_test(TestTags.SLOW)
     def test_cache_get_pool_name(self):
         """Verify that it is possible to get the name of the cache pool"""
 
@@ -1283,8 +1295,7 @@ class LvmPVVGcachedLVpoolTestCase(LvmPVVGLVTestCase):
 
 @unittest.skipUnless(lvm_dbus_running, "LVM DBus not running")
 class LvmPVVGcachedLVstatsTestCase(LvmPVVGLVTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
-    @skip_on("fedora", "27", reason="LVM is broken in many ways on rawhide")
+    @tag_test(TestTags.SLOW)
     def test_cache_get_stats(self):
         """Verify that it is possible to get stats for a cached LV"""
 
@@ -1323,6 +1334,7 @@ class LVMTechTest(LVMTestCase):
         self.addCleanup(BlockDev.switch_init_checks, True)
         self.addCleanup(BlockDev.reinit, [self.ps, self.ps2], True, None)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_tech_available(self):
         """Verify that checking lvm dbus availability by technology works as expected"""
 
diff --git a/tests/lvm_test.py b/tests/lvm_test.py
index 4f1640f..28a4b05 100644
--- a/tests/lvm_test.py
+++ b/tests/lvm_test.py
@@ -7,7 +7,7 @@ import six
 import re
 import subprocess
 
-from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path, skip_on
+from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path, skip_on, TestTags, tag_test
 from gi.repository import BlockDev, GLib
 
 
@@ -27,6 +27,7 @@ class LvmNoDevTestCase(LVMTestCase):
         super(LvmNoDevTestCase, self).__init__(*args, **kwargs)
         self._log = ""
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_is_supported_pe_size(self):
         """Verify that lvm_is_supported_pe_size works as expected"""
 
@@ -42,12 +43,14 @@ class LvmNoDevTestCase(LVMTestCase):
         self.assertFalse(BlockDev.lvm_is_supported_pe_size(65535))
         self.assertFalse(BlockDev.lvm_is_supported_pe_size(32 * 1024**3))
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_get_supported_pe_sizes(self):
         """Verify that supported PE sizes are really supported"""
 
         for size in BlockDev.lvm_get_supported_pe_sizes():
             self.assertTrue(BlockDev.lvm_is_supported_pe_size(size))
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_get_max_lv_size(self):
         """Verify that max LV size is correctly determined"""
 
@@ -60,6 +63,7 @@ class LvmNoDevTestCase(LVMTestCase):
 
         self.assertEqual(BlockDev.lvm_get_max_lv_size(), expected)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_round_size_to_pe(self):
         """Verify that round_size_to_pe works as expected"""
 
@@ -84,6 +88,7 @@ class LvmNoDevTestCase(LVMTestCase):
         self.assertEqual(BlockDev.lvm_round_size_to_pe(biggest_multiple - (2 * 4 * 1024**2) + 1, 4 * 1024**2, False),
                          biggest_multiple - (2 * 4 * 1024**2))
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_get_lv_physical_size(self):
         """Verify that get_lv_physical_size works as expected"""
 
@@ -97,6 +102,7 @@ class LvmNoDevTestCase(LVMTestCase):
         self.assertEqual(BlockDev.lvm_get_lv_physical_size(11 * 1024**2, 4 * 1024**2),
                          12 * 1024**2)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_get_thpool_padding(self):
         """Verify that get_thpool_padding works as expected"""
 
@@ -110,6 +116,7 @@ class LvmNoDevTestCase(LVMTestCase):
         self.assertEqual(BlockDev.lvm_get_thpool_padding(11 * 1024**2, 4 * 1024**2, True),
                          expected_padding)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_get_thpool_meta_size(self):
         """Verify that getting recommended thin pool metadata size works as expected"""
 
@@ -128,6 +135,7 @@ class LvmNoDevTestCase(LVMTestCase):
         self.assertEqual(BlockDev.lvm_get_thpool_meta_size (100 * 1024**2, 128 * 1024, 100),
                          BlockDev.LVM_MIN_THPOOL_MD_SIZE)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_is_valid_thpool_md_size(self):
         """Verify that is_valid_thpool_md_size works as expected"""
 
@@ -138,6 +146,7 @@ class LvmNoDevTestCase(LVMTestCase):
         self.assertFalse(BlockDev.lvm_is_valid_thpool_md_size(1 * 1024**2))
         self.assertFalse(BlockDev.lvm_is_valid_thpool_md_size(17 * 1024**3))
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_is_valid_thpool_chunk_size(self):
         """Verify that is_valid_thpool_chunk_size works as expected"""
 
@@ -156,6 +165,7 @@ class LvmNoDevTestCase(LVMTestCase):
     def _store_log(self, lvl, msg):
         self._log += str((lvl, msg))
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_get_set_global_config(self):
         """Verify that getting and setting global config works as expected"""
 
@@ -192,6 +202,7 @@ class LvmNoDevTestCase(LVMTestCase):
         succ = BlockDev.lvm_set_global_config(None)
         self.assertTrue(succ)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_cache_get_default_md_size(self):
         """Verify that default cache metadata size is calculated properly"""
 
@@ -200,6 +211,7 @@ class LvmNoDevTestCase(LVMTestCase):
         self.assertEqual(BlockDev.lvm_cache_get_default_md_size(80 * 1024**3), (80 * 1024**3) // 1000)
         self.assertEqual(BlockDev.lvm_cache_get_default_md_size(6 * 1024**3), 8 * 1024**2)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_cache_mode_bijection(self):
         """Verify that cache modes and their string representations map to each other"""
 
@@ -258,6 +270,7 @@ class LvmPVonlyTestCase(LVMTestCase):
         os.unlink(self.dev_file2)
 
 class LvmTestPVcreateRemove(LvmPVonlyTestCase):
+    @tag_test(TestTags.CORE)
     def test_pvcreate_and_pvremove(self):
         """Verify that it's possible to create and destroy a PV"""
 
@@ -357,7 +370,8 @@ class LvmPVVGTestCase(LvmPVonlyTestCase):
         LvmPVonlyTestCase._clean_up(self)
 
 class LvmTestVGcreateRemove(LvmPVVGTestCase):
-    @skip_on("debian", skip_on_arch="i686", reason="vgremove is broken on 32bit Debian")
+    @skip_on("debian", skip_on_version="9", skip_on_arch="i686", reason="vgremove is broken on 32bit Debian stable")
+    @tag_test(TestTags.CORE)
     def test_vgcreate_vgremove(self):
         """Verify that it is possible to create and destroy a VG"""
 
@@ -543,6 +557,7 @@ class LvmPVVGLVTestCase(LvmPVVGTestCase):
         LvmPVVGTestCase._clean_up(self)
 
 class LvmTestLVcreateRemove(LvmPVVGLVTestCase):
+    @tag_test(TestTags.CORE)
     def test_lvcreate_lvremove(self):
         """Verify that it's possible to create/destroy an LV"""
 
@@ -810,7 +825,7 @@ class LvmTestLVrename(LvmPVVGLVTestCase):
             BlockDev.lvm_lvrename("testVG", "testLV", "testLV", None)
 
 class LvmTestLVsnapshots(LvmPVVGLVTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_snapshotcreate_lvorigin_snapshotmerge(self):
         """Verify that LV snapshot support works"""
 
@@ -920,6 +935,7 @@ class LvmTestLVsAll(LvmPVVGthpoolTestCase):
         self.assertGreater(len(lvs), 3)
 
 class LvmTestThpoolCreate(LvmPVVGthpoolTestCase):
+    @tag_test(TestTags.CORE)
     def test_thpoolcreate(self):
         """Verify that it is possible to create a thin pool"""
 
@@ -1016,6 +1032,7 @@ class LvmPVVGLVthLVTestCase(LvmPVVGthpoolTestCase):
         LvmPVVGthpoolTestCase._clean_up(self)
 
 class LvmTestThLVcreate(LvmPVVGLVthLVTestCase):
+    @tag_test(TestTags.CORE)
     def test_thlvcreate_thpoolname(self):
         """Verify that it is possible to create a thin LV and get its pool name"""
 
@@ -1098,8 +1115,7 @@ class LvmPVVGLVcachePoolTestCase(LvmPVVGLVTestCase):
         LvmPVVGLVTestCase._clean_up(self)
 
 class LvmPVVGLVcachePoolCreateRemoveTestCase(LvmPVVGLVcachePoolTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
-    @skip_on("fedora", "27", reason="LVM is broken in many ways on rawhide")
+    @tag_test(TestTags.SLOW)
     @skip_on(("centos", "enterprise_linux"), "7")
     def test_cache_pool_create_remove(self):
         """Verify that is it possible to create and remove a cache pool"""
@@ -1125,8 +1141,7 @@ class LvmPVVGLVcachePoolCreateRemoveTestCase(LvmPVVGLVcachePoolTestCase):
         self.assertTrue(succ)
 
 class LvmTestCachePoolConvert(LvmPVVGLVcachePoolTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
-    @skip_on("fedora", "27", reason="LVM is broken in many ways on rawhide")
+    @tag_test(TestTags.SLOW)
     def test_cache_pool_convert(self):
         """Verify that it is possible to create a cache pool by conversion"""
 
@@ -1149,8 +1164,7 @@ class LvmTestCachePoolConvert(LvmPVVGLVcachePoolTestCase):
 
 
 class LvmPVVGLVcachePoolAttachDetachTestCase(LvmPVVGLVcachePoolTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
-    @skip_on("fedora", "27", reason="LVM is broken in many ways on rawhide")
+    @tag_test(TestTags.SLOW)
     def test_cache_pool_attach_detach(self):
         """Verify that is it possible to attach and detach a cache pool"""
 
@@ -1190,8 +1204,7 @@ class LvmPVVGLVcachePoolAttachDetachTestCase(LvmPVVGLVcachePoolTestCase):
         self.assertTrue(any(info.lv_name == "testCache" for info in lvs))
 
 class LvmPVVGcachedLVTestCase(LvmPVVGLVTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
-    @skip_on("fedora", "27", reason="LVM is broken in many ways on rawhide")
+    @tag_test(TestTags.SLOW)
     def test_create_cached_lv(self):
         """Verify that it is possible to create a cached LV in a single step"""
 
@@ -1210,8 +1223,7 @@ class LvmPVVGcachedLVTestCase(LvmPVVGLVTestCase):
         self.assertTrue(succ)
 
 class LvmPVVGcachedLVpoolTestCase(LvmPVVGLVTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
-    @skip_on("fedora", "27", reason="LVM is broken in many ways on rawhide")
+    @tag_test(TestTags.SLOW)
     def test_cache_get_pool_name(self):
         """Verify that it is possible to get the name of the cache pool"""
 
@@ -1236,8 +1248,7 @@ class LvmPVVGcachedLVpoolTestCase(LvmPVVGLVTestCase):
         self.assertEqual(BlockDev.lvm_cache_pool_name("testVG", "testLV"), "testCache")
 
 class LvmPVVGcachedLVstatsTestCase(LvmPVVGLVTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
-    @skip_on("fedora", "27", reason="LVM is broken in many ways on rawhide")
+    @tag_test(TestTags.SLOW)
     def test_cache_get_stats(self):
         """Verify that it is possible to get stats for a cached LV"""
 
@@ -1271,6 +1282,7 @@ class LVMUnloadTest(LVMTestCase):
         # tests
         self.addCleanup(BlockDev.reinit, self.requested_plugins, True, None)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_low_version(self):
         """Verify that checking the minimum LVM version works as expected"""
 
@@ -1288,6 +1300,7 @@ class LVMUnloadTest(LVMTestCase):
         self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
         self.assertIn("lvm", BlockDev.get_available_plugin_names())
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_no_lvm(self):
         """Verify that checking lvm tool availability works as expected"""
 
@@ -1315,6 +1328,7 @@ class LVMTechTest(LVMTestCase):
         self.addCleanup(BlockDev.switch_init_checks, True)
         self.addCleanup(BlockDev.reinit, self.requested_plugins, True, None)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_tech_available(self):
         """Verify that checking lvm tool availability by technology works as expected"""
 
diff --git a/tests/mdraid_test.py b/tests/mdraid_test.py
index ea182b2..ea489db 100644
--- a/tests/mdraid_test.py
+++ b/tests/mdraid_test.py
@@ -6,7 +6,7 @@ from contextlib import contextmanager
 import overrides_hack
 import six
 
-from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path
+from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path, skip_on, TestTags, tag_test
 from gi.repository import BlockDev, GLib
 
 
@@ -46,6 +46,7 @@ class MDNoDevTestCase(MDTest):
         else:
             BlockDev.reinit(cls.requested_plugins, True, None)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_get_superblock_size(self):
         """Verify that superblock size si calculated properly"""
 
@@ -67,6 +68,7 @@ class MDNoDevTestCase(MDTest):
         self.assertEqual(BlockDev.md_get_superblock_size(257 * 1024**2, version="unknown version"),
                          2 * 1024**2)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_canonicalize_uuid(self):
         """Verify that UUID canonicalization works as expected"""
 
@@ -76,6 +78,7 @@ class MDNoDevTestCase(MDTest):
         with six.assertRaisesRegex(self, GLib.GError, r'malformed or invalid'):
             BlockDev.md_canonicalize_uuid("malformed-uuid-example")
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_get_md_uuid(self):
         """Verify that getting UUID in MD RAID format works as expected"""
 
@@ -162,7 +165,7 @@ class MDTestCase(MDTest):
 
 
 class MDTestCreateDeactivateDestroy(MDTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW, TestTags.CORE)
     def test_create_deactivate_destroy(self):
         """Verify that it is possible to create, deactivate and destroy an MD RAID"""
 
@@ -192,7 +195,7 @@ class MDTestCreateDeactivateDestroy(MDTestCase):
         self.assertTrue(succ)
 
 class MDTestCreateWithChunkSize(MDTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_create_with_chunk_size(self):
         """Verify that it is possible to create and MD RAID with specific chunk size """
 
@@ -216,7 +219,7 @@ class MDTestCreateWithChunkSize(MDTestCase):
         self.assertTrue(succ)
 
 class MDTestActivateDeactivate(MDTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW, TestTags.CORE)
     def test_activate_deactivate(self):
         """Verify that it is possible to activate and deactivate an MD RAID"""
 
@@ -255,7 +258,7 @@ class MDTestActivateDeactivate(MDTestCase):
             self.assertTrue(succ)
 
 class MDTestActivateWithUUID(MDTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_activate_with_uuid(self):
         """Verify that it is possible to activate an MD RAID with UUID"""
 
@@ -277,7 +280,7 @@ class MDTestActivateWithUUID(MDTestCase):
             succ = BlockDev.md_activate("bd_test_md", [self.loop_dev, self.loop_dev2, self.loop_dev3], md_info.uuid)
 
 class MDTestActivateByUUID(MDTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_activate_by_uuid(self):
         """Verify that it is possible to activate an MD RAID by UUID"""
 
@@ -309,7 +312,7 @@ class MDTestActivateByUUID(MDTestCase):
 
 
 class MDTestNominateDenominate(MDTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_nominate_denominate(self):
         """Verify that it is possible to nominate and denominate an MD RAID device"""
 
@@ -342,9 +345,7 @@ class MDTestNominateDenominate(MDTestCase):
 class MDTestNominateDenominateActive(MDTestCase):
     # slow and leaking an MD array because with a nominated spare device, it
     # cannot be deactivated in the end (don't ask me why)
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
-    @unittest.skipIf("JENKINS_HOME" in os.environ, "skipping leaky test in jenkins")
-    @unittest.skipUnless("FEELINGLUCKY" in os.environ, "skipping, not feeling lucky")
+    @tag_test(TestTags.SLOW, TestTags.UNSAFE, TestTags.UNSTABLE)
     def test_nominate_denominate_active(self):
         """Verify that nominate and denominate deivice works as expected on (de)activated MD RAID"""
 
@@ -371,7 +372,8 @@ class MDTestNominateDenominateActive(MDTestCase):
             self.assertTrue(succ)
 
 class MDTestAddRemove(MDTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
+    @skip_on("debian", reason="Removing spare disks from an array is broken on Debian")
     def test_add_remove(self):
         """Verify that it is possible to add a device to and remove from an MD RAID"""
 
@@ -431,7 +433,7 @@ class MDTestAddRemove(MDTestCase):
 
 class MDTestExamineDetail(MDTestCase):
     # sleeps to let MD RAID sync things
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_examine_detail(self):
         """Verify that it is possible to get info about an MD RAID"""
 
@@ -487,7 +489,7 @@ class MDTestExamineDetail(MDTestCase):
         self.assertTrue(de_data)
 
 class MDTestNameNodeBijection(MDTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_name_node_bijection(self):
         """Verify that MD RAID node and name match each other"""
 
@@ -518,7 +520,7 @@ class MDTestNameNodeBijection(MDTestCase):
         self.assertTrue(succ)
 
 class MDTestSetBitmapLocation(MDTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_set_bitmap_location(self):
         """Verify we can change bitmap location for an existing MD array"""
 
@@ -567,7 +569,7 @@ class MDTestSetBitmapLocation(MDTestCase):
 
 
 class MDTestRequestSyncAction(MDTestCase):
-    @unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
+    @tag_test(TestTags.SLOW)
     def test_request_sync_action(self):
         """Verify we can request sync action on an existing MD array"""
 
@@ -587,6 +589,7 @@ class MDTestRequestSyncAction(MDTestCase):
 
 class FakeMDADMutilTest(MDTest):
     # no setUp nor tearDown needed, we are gonna use fake utils
+    @tag_test(TestTags.NOSTORAGE)
     def test_fw_raid_uppercase_examine(self):
         """Verify that md_examine works with output using "RAID" instead of "Raid" and other quirks """
 
@@ -598,6 +601,7 @@ class FakeMDADMutilTest(MDTest):
         self.assertEqual(ex_data.uuid, "b42756a2-37e4-3e47-674b-d1dd6e822145")
         self.assertEqual(ex_data.device, None)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_no_metadata_examine(self):
         """Verify that md_examine works as expected with no metadata spec"""
 
@@ -607,6 +611,7 @@ class FakeMDADMutilTest(MDTest):
 
         self.assertIs(ex_data.metadata, None)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_fw_raid_migrating(self):
         """Verify that md_examine works when array is migrating ("foo <-- bar" values in output) """
 
@@ -615,6 +620,7 @@ class FakeMDADMutilTest(MDTest):
 
         self.assertEqual(ex_data.chunk_size, 128 * 1024)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_mdadm_name_extra_info(self):
         """Verify that md_examine and md_detail work with extra MD RAID name info"""
 
@@ -632,6 +638,7 @@ class MDUnloadTest(MDTestCase):
         # tests
         self.addCleanup(BlockDev.reinit, self.requested_plugins, True, None)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_low_version(self):
         """Verify that checking the minimum mdsetup version works as expected"""
 
@@ -649,6 +656,7 @@ class MDUnloadTest(MDTestCase):
         self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
         self.assertIn("mdraid", BlockDev.get_available_plugin_names())
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_no_md(self):
         """Verify that checking mdsetup tool availability works as expected"""
 
diff --git a/tests/mpath_test.py b/tests/mpath_test.py
index e9d1e42..acd3053 100644
--- a/tests/mpath_test.py
+++ b/tests/mpath_test.py
@@ -2,7 +2,7 @@ import unittest
 import os
 import overrides_hack
 
-from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path, skip_on, get_version
+from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path, skip_on, get_version, TestTags, tag_test
 from gi.repository import BlockDev, GLib
 
 class MpathTest(unittest.TestCase):
@@ -56,6 +56,7 @@ class MpathUnloadTest(MpathTest):
         # tests
         self.addCleanup(BlockDev.reinit, self.requested_plugins, True, None)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_low_version(self):
         """Verify that checking the minimum dmsetup version works as expected"""
 
@@ -74,6 +75,7 @@ class MpathUnloadTest(MpathTest):
         self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
         self.assertIn("mpath", BlockDev.get_available_plugin_names())
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_no_multipath(self):
         """Verify that checking multipath tool availability works as expected"""
 
@@ -91,6 +93,7 @@ class MpathUnloadTest(MpathTest):
         self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
         self.assertIn("mpath", BlockDev.get_available_plugin_names())
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_no_mpathconf(self):
         """Verify that checking mpathconf tool availability works as expected"""
 
diff --git a/tests/nvdimm_test.py b/tests/nvdimm_test.py
index a4e6854..1c2fb3c 100644
--- a/tests/nvdimm_test.py
+++ b/tests/nvdimm_test.py
@@ -6,7 +6,7 @@ import overrides_hack
 
 from distutils.version import LooseVersion
 
-from utils import run_command, read_file, skip_on, fake_path
+from utils import run_command, read_file, skip_on, fake_path, TestTags, tag_test
 from gi.repository import BlockDev, GLib
 
 
@@ -80,6 +80,7 @@ class NVDIMMNamespaceTestCase(NVDIMMTestCase):
             # even for modes where sector size doesn't make sense
             self.assertEqual(bd_info.sector_size, 512)
 
+    @tag_test(TestTags.EXTRADEPS, TestTags.CORE)
     def test_namespace_info(self):
         # get info about our 'testing' namespace
         info = BlockDev.nvdimm_namespace_info(self.sys_info["dev"])
@@ -96,14 +97,15 @@ class NVDIMMNamespaceTestCase(NVDIMMTestCase):
         info = BlockDev.nvdimm_namespace_info("definitely-not-a-namespace")
         self.assertIsNone(info)
 
+    @tag_test(TestTags.EXTRADEPS, TestTags.CORE)
     def test_list_namespaces(self):
         bd_namespaces = BlockDev.nvdimm_list_namespaces()
         self.assertEqual(len(bd_namespaces), 1)
 
         self._check_namespace_info(bd_namespaces[0])
 
-    @unittest.skipUnless("JENKINS_HOME" in os.environ, "skipping test that modifies system configuration")
     @skip_on("fedora", "29", reason="Disabling is broken on rawhide and makes the 'fake' NVDIMM unusable.")
+    @tag_test(TestTags.EXTRADEPS, TestTags.UNSAFE)
     def test_enable_disable(self):
         # non-existing/unknow namespace
         with self.assertRaises(GLib.GError):
@@ -130,8 +132,8 @@ class NVDIMMNamespaceTestCase(NVDIMMTestCase):
         info = BlockDev.nvdimm_namespace_info(self.sys_info["dev"])
         self.assertTrue(info.enabled)
 
-    @unittest.skipUnless("JENKINS_HOME" in os.environ, "skipping test that modifies system configuration")
     @skip_on("fedora", "29", reason="Disabling is broken on rawhide and makes the 'fake' NVDIMM unusable.")
+    @tag_test(TestTags.EXTRADEPS, TestTags.UNSAFE)
     def test_namespace_reconfigure(self):
         # active namespace -- reconfigure doesn't work without force
         with self.assertRaises(GLib.GError):
@@ -188,6 +190,7 @@ class NVDIMMUnloadTest(NVDIMMTestCase):
         # tests
         self.addCleanup(BlockDev.reinit, self.requested_plugins, True, None)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_no_ndctl(self):
         """Verify that checking ndctl tool availability works as expected"""
 
diff --git a/tests/overrides_test.py b/tests/overrides_test.py
index 53c65b5..8e7f5a5 100644
--- a/tests/overrides_test.py
+++ b/tests/overrides_test.py
@@ -3,6 +3,8 @@ import math
 import overrides_hack
 from gi.repository import BlockDev
 
+from utils import TestTags, tag_test
+
 
 class OverridesTest(unittest.TestCase):
     # all plugins except for 'btrfs', 'fs' and 'mpath' -- these don't have all
@@ -19,6 +21,7 @@ class OverridesTest(unittest.TestCase):
             BlockDev.reinit(cls.requested_plugins, True, None)
 
 class OverridesTestCase(OverridesTest):
+    @tag_test(TestTags.NOSTORAGE, TestTags.CORE)
     def test_error_proxy(self):
         """Verify that the error proxy works as expected"""
 
@@ -68,6 +71,7 @@ class OverridesUnloadTestCase(OverridesTest):
         # tests
         self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
 
+    @tag_test(TestTags.NOSTORAGE, TestTags.CORE)
     def test_xrules(self):
         """Verify that regexp-based transformation rules work as expected"""
 
@@ -81,6 +85,7 @@ class OverridesUnloadTestCase(OverridesTest):
         # load the plugins back
         self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
 
+    @tag_test(TestTags.NOSTORAGE, TestTags.CORE)
     def test_exception_inheritance(self):
         # unload all plugins first
         self.assertTrue(BlockDev.reinit([], True, None))
diff --git a/tests/part_test.py b/tests/part_test.py
index adbaa9a..9e58cc6 100644
--- a/tests/part_test.py
+++ b/tests/part_test.py
@@ -1,6 +1,6 @@
 import unittest
 import os
-from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, skip_on
+from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, skip_on, TestTags, tag_test
 import overrides_hack
 
 from gi.repository import BlockDev, GLib
@@ -46,6 +46,7 @@ class PartTestCase(unittest.TestCase):
         os.unlink(self.dev_file2)
 
 class PartCreateTableCase(PartTestCase):
+    @tag_test(TestTags.CORE)
     def test_create_table(self):
         """Verify that it is possible to create a new partition table"""
 
@@ -78,6 +79,7 @@ class PartCreateTableCase(PartTestCase):
 
 
 class PartGetDiskSpecCase(PartTestCase):
+    @tag_test(TestTags.CORE)
     def test_get_disk_spec(self):
         """Verify that it is possible to get information about disk"""
 
@@ -115,6 +117,7 @@ class PartGetDiskSpecCase(PartTestCase):
         self.assertEqual(ps.flags, 0)
 
 class PartCreatePartCase(PartTestCase):
+    @tag_test(TestTags.CORE)
     def test_create_part_simple(self):
         """Verify that it is possible to create a parition"""
 
@@ -221,6 +224,7 @@ class PartCreatePartCase(PartTestCase):
         self.assertEqual(ps.flags, ps3.flags)
 
 class PartCreatePartFullCase(PartTestCase):
+    @tag_test(TestTags.CORE)
     def test_full_device_partition(self):
         # we first need a partition table
         succ = BlockDev.part_create_table (self.loop_dev, BlockDev.PartTableType.GPT, True)
@@ -362,6 +366,7 @@ class PartCreatePartFullCase(PartTestCase):
             BlockDev.part_create_part (self.loop_dev, BlockDev.PartTypeReq.EXTENDED, ps4.start + ps4.size + 1,
                                        10 * 1024**2, BlockDev.PartAlign.OPTIMAL)
 
+    @tag_test(TestTags.CORE)
     def test_create_part_with_extended_logical(self):
         """Verify that partition creation works as expected with primary, extended and logical parts"""
 
@@ -502,6 +507,7 @@ class PartCreatePartFullCase(PartTestCase):
             BlockDev.part_create_part (self.loop_dev, BlockDev.PartTypeReq.LOGICAL, ps3.start + ps3.size + 1,
                                          10 * 1024**2, BlockDev.PartAlign.OPTIMAL)
 
+    @tag_test(TestTags.CORE)
     def test_create_part_next(self):
         """Verify that partition creation works as expected with the NEXT (auto) type"""
 
@@ -589,6 +595,7 @@ class PartCreatePartFullCase(PartTestCase):
             BlockDev.part_create_part (self.loop_dev, BlockDev.PartTypeReq.EXTENDED, ps4.start + ps4.size + 1,
                                        10 * 1024**2, BlockDev.PartAlign.OPTIMAL)
 
+    @tag_test(TestTags.CORE)
     def test_create_part_next_gpt(self):
         """Verify that partition creation works as expected with the NEXT (auto) type on GPT"""
 
@@ -664,6 +671,7 @@ class PartGetDiskPartsCase(PartTestCase):
 class PartGetDiskFreeRegions(PartTestCase):
     @skip_on(("centos", "enterprise_linux"), "7", reason="libparted provides weird values here")
     @skip_on("debian", reason="libparted provides weird values here")
+    @tag_test(TestTags.CORE)
     def test_get_disk_free_regions(self):
         """Verify that it is possible to get info about free regions on a disk"""
 
@@ -1057,6 +1065,7 @@ class PartCreateResizePartCase(PartTestCase):
         self.assertGreaterEqual(ps.size, initial_size) # at least the requested size
 
 class PartCreateDeletePartCase(PartTestCase):
+    @tag_test(TestTags.CORE)
     def test_create_delete_part_simple(self):
         """Verify that it is possible to create and delete a parition"""
 
diff --git a/tests/s390_test.py b/tests/s390_test.py
index 98c6b1b..da23a55 100644
--- a/tests/s390_test.py
+++ b/tests/s390_test.py
@@ -2,7 +2,7 @@ import unittest
 import os
 import overrides_hack
 
-from utils import fake_path
+from utils import fake_path, TestTags, tag_test
 from gi.repository import BlockDev, GLib
 
 @unittest.skipUnless(os.uname()[4].startswith('s390'), "s390x architecture required")
@@ -18,6 +18,7 @@ class S390TestCase(unittest.TestCase):
         else:
             BlockDev.reinit(cls.requested_plugins, True, None)
 
+    @tag_test(TestTags.EXTRADEPS, TestTags.NOSTORAGE)
     def test_device_input(self):
         """Verify that s390_sanitize_dev_input works as expected"""
         dev = "1234"
@@ -42,6 +43,7 @@ class S390TestCase(unittest.TestCase):
         dev = "0.0.abcdefgh"
         self.assertEqual(BlockDev.s390_sanitize_dev_input(dev), dev)
 
+    @tag_test(TestTags.EXTRADEPS, TestTags.NOSTORAGE)
     def test_wwpn_input(self):
         """Verify that s390_zfcp_sanitize_wwpn_input works as expected"""
         # missing "0x" from beginning of wwpn; this should be added by fx
@@ -56,6 +58,7 @@ class S390TestCase(unittest.TestCase):
         with self.assertRaises(GLib.GError):
             BlockDev.s390_zfcp_sanitize_wwpn_input(wwpn)
 
+    @tag_test(TestTags.EXTRADEPS, TestTags.NOSTORAGE)
     def test_lun_input(self):
         """Verify that s390_zfcp_sanitize_lun_input works as expected"""
         # user does not prepend lun with "0x"; this should get added
@@ -91,6 +94,7 @@ class S390UnloadTest(unittest.TestCase):
         else:
             BlockDev.reinit(cls.requested_plugins, True, None)
 
+    @tag_test(TestTags.EXTRADEPS, TestTags.NOSTORAGE)
     def test_check_no_dasdfmt(self):
         """Verify that checking dasdfmt tool availability works as expected"""
 
diff --git a/tests/swap_test.py b/tests/swap_test.py
index 05d0c19..66b5eb2 100644
--- a/tests/swap_test.py
+++ b/tests/swap_test.py
@@ -2,7 +2,7 @@ import unittest
 import os
 import overrides_hack
 
-from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path, run_command
+from utils import create_sparse_tempfile, create_lio_device, delete_lio_device, fake_utils, fake_path, run_command, run, TestTags, tag_test
 from gi.repository import BlockDev, GLib
 
 
@@ -38,6 +38,7 @@ class SwapTestCase(SwapTest):
             pass
         os.unlink(self.dev_file)
 
+    @tag_test(TestTags.CORE)
     def test_all(self):
         """Verify that swap_* functions work as expected"""
 
@@ -103,6 +104,7 @@ class SwapUnloadTest(SwapTest):
         # tests
         self.addCleanup(BlockDev.reinit, self.requested_plugins, True, None)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_low_version(self):
         """Verify that checking the minimum swap utils versions works as expected"""
 
@@ -120,6 +122,7 @@ class SwapUnloadTest(SwapTest):
         self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
         self.assertIn("swap", BlockDev.get_available_plugin_names())
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_no_mkswap(self):
         """Verify that checking mkswap and swaplabel tools availability
            works as expected
@@ -146,6 +149,7 @@ class SwapUnloadTest(SwapTest):
         self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
         self.assertIn("swap", BlockDev.get_available_plugin_names())
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_no_mkswap_runtime(self):
         """Verify that runtime checking mkswap tool availability works as expected"""
 
@@ -172,6 +176,7 @@ class SwapTechAvailable(SwapTest):
         self.addCleanup(BlockDev.switch_init_checks, True)
         self.addCleanup(BlockDev.reinit, self.requested_plugins, True, None)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_tech_available(self):
         """Verify that runtime checking mkswap and swaplabel tools availability
            works as expected
diff --git a/tests/utils_test.py b/tests/utils_test.py
index 02b0203..66d8a32 100644
--- a/tests/utils_test.py
+++ b/tests/utils_test.py
@@ -2,7 +2,7 @@ import unittest
 import re
 import os
 import overrides_hack
-from utils import fake_utils, create_sparse_tempfile, create_lio_device, delete_lio_device, run_command
+from utils import fake_utils, create_sparse_tempfile, create_lio_device, delete_lio_device, run_command, TestTags, tag_test
 
 from gi.repository import BlockDev, GLib
 
@@ -25,6 +25,7 @@ class UtilsExecProgressTest(UtilsTestCase):
         self.assertTrue(isinstance(completion, int))
         self.log.append(completion)
 
+    @tag_test(TestTags.NOSTORAGE, TestTags.CORE)
     def test_initialization(self):
         """ Verify that progress report can (de)initialized"""
 
@@ -54,6 +55,7 @@ class UtilsExecLoggingTest(UtilsTestCase):
 
         self.log += msg + "\n"
 
+    @tag_test(TestTags.NOSTORAGE, TestTags.CORE)
     def test_logging(self):
         """Verify that setting up and using exec logging works as expected"""
 
@@ -91,6 +93,7 @@ class UtilsExecLoggingTest(UtilsTestCase):
         self.assertTrue(succ)
         self.assertEqual(old_log, self.log)
 
+    @tag_test(TestTags.NOSTORAGE, TestTags.CORE)
     def test_version_cmp(self):
         """Verify that version comparison works as expected"""
 
@@ -124,6 +127,7 @@ class UtilsExecLoggingTest(UtilsTestCase):
         self.assertEqual(BlockDev.utils_version_cmp("1.1.1", "1.1.1-1"), -1)
         self.assertEqual(BlockDev.utils_version_cmp("1.1.2", "1.2"), -1)
 
+    @tag_test(TestTags.NOSTORAGE, TestTags.CORE)
     def test_util_version(self):
         """Verify that checking utility availability works as expected"""
 
@@ -167,6 +171,7 @@ class UtilsExecLoggingTest(UtilsTestCase):
             self.assertTrue(BlockDev.utils_check_util_version("libblockdev-fake-util-fail", "1.1", "version", "Version:\\s(.*)"))
 
 class UtilsDevUtilsTestCase(UtilsTestCase):
+    @tag_test(TestTags.NOSTORAGE, TestTags.CORE)
     def test_resolve_device(self):
         """Verify that resolving device spec works as expected"""
 
@@ -199,6 +204,7 @@ class UtilsDevUtilsTestCase(UtilsTestCase):
         self.assertEqual(BlockDev.utils_resolve_device(dev_link[5:]), dev)
 
 class UtilsDevUtilsTestCase(UtilsTestCase):
+    @tag_test(TestTags.NOSTORAGE, TestTags.CORE)
     def test_resolve_device(self):
         """Verify that resolving device spec works as expected"""
 
@@ -248,7 +254,7 @@ class UtilsDevUtilsSymlinksTestCase(UtilsTestCase):
             pass
         os.unlink(self.dev_file)
 
-
+    @tag_test(TestTags.CORE)
     def test_get_device_symlinks(self):
         """Verify that getting device symlinks works as expected"""
 
diff --git a/tests/vdo_test.py b/tests/vdo_test.py
index be8103a..f20ccd5 100644
--- a/tests/vdo_test.py
+++ b/tests/vdo_test.py
@@ -6,7 +6,7 @@ import unittest
 import overrides_hack
 import six
 
-from utils import run_command, read_file, skip_on, fake_path, create_sparse_tempfile, create_lio_device, delete_lio_device
+from utils import run_command, read_file, skip_on, fake_path, create_sparse_tempfile, create_lio_device, delete_lio_device, TestTags, tag_test
 from gi.repository import BlockDev, GLib
 from bytesize import bytesize
 from distutils.spawn import find_executable
@@ -48,7 +48,6 @@ class VDOTestCase(unittest.TestCase):
         os.unlink(self.dev_file)
 
 
-@unittest.skipIf("SKIP_SLOW" in os.environ, "skipping slow tests")
 class VDOTest(VDOTestCase):
 
     vdo_name = "bd-test-vdo"
@@ -56,6 +55,7 @@ class VDOTest(VDOTestCase):
     def _remove_vdo(self, name):
         run_command("vdo remove --force -n %s" % name)
 
+    @tag_test(TestTags.SLOW, TestTags.CORE)
     def test_create_remove(self):
         """Verify that it is possible to create and remove a VDO volume"""
 
@@ -85,6 +85,7 @@ class VDOTest(VDOTestCase):
 
         self.assertFalse(os.path.exists("/dev/mapper/%s" % self.vdo_name))
 
+    @tag_test(TestTags.SLOW)
     def test_enable_disable_compression(self):
         """Verify that it is possible to enable/disable compression on an existing VDO volume"""
 
@@ -110,6 +111,7 @@ class VDOTest(VDOTestCase):
         info = BlockDev.vdo_info(self.vdo_name)
         self.assertTrue(info.compression)
 
+    @tag_test(TestTags.SLOW)
     def test_enable_disable_deduplication(self):
         """Verify that it is possible to enable/disable deduplication on an existing VDO volume"""
 
@@ -135,6 +137,7 @@ class VDOTest(VDOTestCase):
         info = BlockDev.vdo_info(self.vdo_name)
         self.assertTrue(info.deduplication)
 
+    @tag_test(TestTags.SLOW)
     def test_activate_deactivate(self):
         """Verify that it is possible to activate/deactivate an existing VDO volume"""
 
@@ -172,6 +175,7 @@ class VDOTest(VDOTestCase):
 
         self.assertTrue(os.path.exists("/dev/mapper/%s" % self.vdo_name))
 
+    @tag_test(TestTags.SLOW)
     def test_change_write_policy(self):
 
         ret = BlockDev.vdo_create(self.vdo_name, self.loop_dev, 3 * self.loop_size, 0,
@@ -203,6 +207,7 @@ class VDOTest(VDOTestCase):
 
         return info["VDOs"][name]
 
+    @tag_test(TestTags.SLOW, TestTags.CORE)
     def test_get_info(self):
         """Verify that it is possible to get information about an existing VDO volume"""
 
@@ -229,6 +234,7 @@ class VDOTest(VDOTestCase):
         self.assertEqual(bd_info.physical_size, bytesize.Size(sys_info["Physical size"]))
         self.assertEqual(bd_info.logical_size, bytesize.Size(sys_info["Logical size"]))
 
+    @tag_test(TestTags.SLOW)
     def test_grow_logical(self):
         """Verify that it is possible to grow logical size of an existing VDO volume"""
 
@@ -249,6 +255,7 @@ class VDOTest(VDOTestCase):
 
         self.assertEqual(info.logical_size, new_size)
 
+    @tag_test(TestTags.SLOW, TestTags.UNSTABLE)
     def test_grow_physical(self):
         """Verify that it is possible to grow physical size of an existing VDO volume"""
 
@@ -284,6 +291,7 @@ class VDOTest(VDOTestCase):
         self.assertEqual(info_before.logical_size, info_after.logical_size)
         self.assertGreater(info_after.physical_size, info_before.physical_size)
 
+    @tag_test(TestTags.SLOW)
     def test_statistics(self):
         """Verify that it is possible to retrieve statistics of an existing VDO volume"""
 
@@ -311,6 +319,7 @@ class VDOUnloadTest(VDOTestCase):
         # tests
         self.addCleanup(BlockDev.reinit, self.requested_plugins, True, None)
 
+    @tag_test(TestTags.NOSTORAGE)
     def test_check_no_vdo(self):
         """Verify that checking vdo tool availability works as expected"""
 
-- 
2.20.1


From c709805db97621889c4354f9771db47916dbc2e5 Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Thu, 4 Apr 2019 10:39:21 +0200
Subject: [PATCH 04/10] Remove duplicate test case

UtilsDevUtilsTestCase was defined twice, probably because of some
copy paste or merging mistake.
---
 tests/utils_test.py | 32 --------------------------------
 1 file changed, 32 deletions(-)

diff --git a/tests/utils_test.py b/tests/utils_test.py
index 66d8a32..e268409 100644
--- a/tests/utils_test.py
+++ b/tests/utils_test.py
@@ -170,38 +170,6 @@ class UtilsExecLoggingTest(UtilsTestCase):
             # exit code != 0
             self.assertTrue(BlockDev.utils_check_util_version("libblockdev-fake-util-fail", "1.1", "version", "Version:\\s(.*)"))
 
-class UtilsDevUtilsTestCase(UtilsTestCase):
-    @tag_test(TestTags.NOSTORAGE, TestTags.CORE)
-    def test_resolve_device(self):
-        """Verify that resolving device spec works as expected"""
-
-        with self.assertRaises(GLib.GError):
-            BlockDev.utils_resolve_device("no_such_device")
-
-        dev = "/dev/libblockdev-test-dev"
-        with open(dev, "w"):
-            pass
-        self.addCleanup(os.unlink, dev)
-
-        # full path, no symlink, should just return the same
-        self.assertEqual(BlockDev.utils_resolve_device(dev), dev)
-
-        # just the name of the device, should return the full path
-        self.assertEqual(BlockDev.utils_resolve_device(dev[5:]), dev)
-
-        dev_dir = "/dev/libblockdev-test-dir"
-        os.mkdir(dev_dir)
-        self.addCleanup(os.rmdir, dev_dir)
-
-        dev_link = dev_dir + "/test-dev-link"
-        os.symlink("../" + dev[5:], dev_link)
-        self.addCleanup(os.unlink, dev_link)
-
-        # should resolve the symlink
-        self.assertEqual(BlockDev.utils_resolve_device(dev_link), dev)
-
-        # should resolve the symlink even without the "/dev" prefix
-        self.assertEqual(BlockDev.utils_resolve_device(dev_link[5:]), dev)
 
 class UtilsDevUtilsTestCase(UtilsTestCase):
     @tag_test(TestTags.NOSTORAGE, TestTags.CORE)
-- 
2.20.1


From 09cee5780854f92b28aaeb7c67ea76c6fc853e30 Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Thu, 4 Apr 2019 11:23:12 +0200
Subject: [PATCH 05/10] Allow running tests against installed libblockdev

---
 tests/overrides_hack.py |   5 +-
 tests/run_tests.py      | 106 +++++++++++++++++++++++-----------------
 2 files changed, 64 insertions(+), 47 deletions(-)

diff --git a/tests/overrides_hack.py b/tests/overrides_hack.py
index 0f10ee5..509a961 100644
--- a/tests/overrides_hack.py
+++ b/tests/overrides_hack.py
@@ -1,5 +1,8 @@
+import os
 import gi.overrides
-if not gi.overrides.__path__[0].endswith("src/python/gi/overrides"):
+
+if 'LIBBLOCKDEV_TESTS_SKIP_OVERRIDE' not in os.environ and \
+   not gi.overrides.__path__[0].endswith("src/python/gi/overrides"):
     local_overrides = None
     # our overrides don't take precedence, let's fix it
     for i, path in enumerate(gi.overrides.__path__):
diff --git a/tests/run_tests.py b/tests/run_tests.py
index 5301d07..b67c2e7 100644
--- a/tests/run_tests.py
+++ b/tests/run_tests.py
@@ -65,49 +65,8 @@ def _get_test_tags(test):
     return tags
 
 
-def _print_skip_message(test, skip_tag):
-
-    # test.id() looks like 'crypto_test.CryptoTestResize.test_luks2_resize'
-    # and we want to print 'test_luks2_resize (crypto_test.CryptoTestResize)'
-    test_desc = test.id().split(".")
-    test_name = test_desc[-1]
-    test_module = ".".join(test_desc[:-1])
-
-    if skip_tag == TestTags.SLOW:
-        reason = "skipping slow tests"
-    elif skip_tag == TestTags.UNSTABLE:
-        reason = "skipping unstable tests"
-    elif skip_tag == TestTags.UNSAFE:
-        reason = "skipping test that modifies system configuration"
-    elif skip_tag == TestTags.EXTRADEPS:
-        reason = "skipping test that requires special configuration"
-    elif skip_tag == TestTags.CORE:
-        reason = "skipping non-core test"
-    else:
-        reason = "unknown reason"  # just to be sure there is some default value
-
-    if test._testMethodDoc:
-        print("%s (%s)\n%s ... skipped '%s'" % (test_name, test_module, test._testMethodDoc, reason))
-    else:
-        print("%s (%s) ... skipped '%s'" % (test_name, test_module, reason))
-
-
-if __name__ == '__main__':
-
-    testdir = os.path.abspath(os.path.dirname(__file__))
-    projdir = os.path.abspath(os.path.normpath(os.path.join(testdir, '..')))
-
-    if 'LD_LIBRARY_PATH' not in os.environ and 'GI_TYPELIB_PATH' not in os.environ:
-        os.environ['LD_LIBRARY_PATH'] = LIBDIRS
-        os.environ['GI_TYPELIB_PATH'] = GIDIR
-        os.environ['LIBBLOCKDEV_CONFIG_DIR'] = os.path.join(testdir, 'default_config')
-
-        try:
-            pyver = 'python3' if six.PY3 else 'python'
-            os.execv(sys.executable, [pyver] + sys.argv)
-        except OSError as e:
-            print('Failed re-exec with a new LD_LIBRARY_PATH and GI_TYPELIB_PATH: %s' % str(e))
-            sys.exit(1)
+def parse_args():
+    """ Parse cmdline arguments """
 
     argparser = argparse.ArgumentParser(description='libblockdev test suite')
     argparser.add_argument('testname', nargs='*', help='name of test class or '
@@ -127,6 +86,9 @@ if __name__ == '__main__':
     argparser.add_argument('-s', '--stop', dest='stop',
                            help='stop executing after first failed test',
                            action='store_true')
+    argparser.add_argument('-i', '--installed', dest='installed',
+                           help='run tests against installed version of libblockdev',
+                           action='store_true')
     args = argparser.parse_args()
 
     if args.fast:
@@ -144,9 +106,61 @@ if __name__ == '__main__':
     if 'FEELINGLUCKY' in os.environ:
         args.lucky = True
 
-    sys.path.append(testdir)
-    sys.path.append(projdir)
-    sys.path.append(os.path.join(projdir, 'src/python'))
+    return args
+
+
+def _print_skip_message(test, skip_tag):
+
+    # test.id() looks like 'crypto_test.CryptoTestResize.test_luks2_resize'
+    # and we want to print 'test_luks2_resize (crypto_test.CryptoTestResize)'
+    test_desc = test.id().split(".")
+    test_name = test_desc[-1]
+    test_module = ".".join(test_desc[:-1])
+
+    if skip_tag == TestTags.SLOW:
+        reason = "skipping slow tests"
+    elif skip_tag == TestTags.UNSTABLE:
+        reason = "skipping unstable tests"
+    elif skip_tag == TestTags.UNSAFE:
+        reason = "skipping test that modifies system configuration"
+    elif skip_tag == TestTags.EXTRADEPS:
+        reason = "skipping test that requires special configuration"
+    elif skip_tag == TestTags.CORE:
+        reason = "skipping non-core test"
+    else:
+        reason = "unknown reason"  # just to be sure there is some default value
+
+    if test._testMethodDoc:
+        print("%s (%s)\n%s ... skipped '%s'" % (test_name, test_module, test._testMethodDoc, reason))
+    else:
+        print("%s (%s) ... skipped '%s'" % (test_name, test_module, reason))
+
+
+if __name__ == '__main__':
+
+    testdir = os.path.abspath(os.path.dirname(__file__))
+    projdir = os.path.abspath(os.path.normpath(os.path.join(testdir, '..')))
+
+    args = parse_args()
+    if args.installed:
+        os.environ['LIBBLOCKDEV_TESTS_SKIP_OVERRIDE'] = ''
+        os.environ['LIBBLOCKDEV_CONFIG_DIR'] = '/etc/libblockdev/conf.d/'
+    else:
+        if 'LD_LIBRARY_PATH' not in os.environ and 'GI_TYPELIB_PATH' not in os.environ:
+            os.environ['LD_LIBRARY_PATH'] = LIBDIRS
+            os.environ['GI_TYPELIB_PATH'] = GIDIR
+            os.environ['LIBBLOCKDEV_CONFIG_DIR'] = os.path.join(testdir, 'default_config')
+
+            try:
+                pyver = 'python3' if six.PY3 else 'python'
+                os.execv(sys.executable, [pyver] + sys.argv)
+            except OSError as e:
+                print('Failed re-exec with a new LD_LIBRARY_PATH and GI_TYPELIB_PATH: %s' % str(e))
+                sys.exit(1)
+
+        sys.path.append(testdir)
+        sys.path.append(projdir)
+        sys.path.append(os.path.join(projdir, 'src/python'))
 
     start_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
 
-- 
2.20.1


From df65462618e72602b7760f6c750085094b2bddff Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Thu, 4 Apr 2019 11:50:04 +0200
Subject: [PATCH 06/10] Add a special test tag for library tests that recompile
 plugins

We have some tests that changes lvm plugin and recompile it to
test loading of plugins. We can't run these tests against
installed library, so we need a special tag to skip them.
---
 tests/library_test.py | 8 ++++----
 tests/run_tests.py    | 2 ++
 tests/utils.py        | 2 ++
 3 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/tests/library_test.py b/tests/library_test.py
index fa33b53..e8bb175 100644
--- a/tests/library_test.py
+++ b/tests/library_test.py
@@ -40,7 +40,7 @@ class LibraryOpsTestCase(unittest.TestCase):
         BlockDev.reinit(self.requested_plugins, True, None)
 
     # recompiles the LVM plugin
-    @tag_test(TestTags.SLOW, TestTags.CORE)
+    @tag_test(TestTags.SLOW, TestTags.CORE, TestTags.SOURCEONLY)
     def test_reload(self):
         """Verify that reloading plugins works as expected"""
 
@@ -72,7 +72,7 @@ class LibraryOpsTestCase(unittest.TestCase):
         self.assertTrue(BlockDev.reinit(self.requested_plugins, True, None))
 
     # recompiles the LVM plugin
-    @tag_test(TestTags.SLOW)
+    @tag_test(TestTags.SLOW, TestTags.SOURCEONLY)
     def test_force_plugin(self):
         """Verify that forcing plugin to be used works as expected"""
 
@@ -118,7 +118,7 @@ class LibraryOpsTestCase(unittest.TestCase):
         self.assertEqual(BlockDev.lvm_get_max_lv_size(), orig_max_size)
 
     # recompiles the LVM plugin
-    @tag_test(TestTags.SLOW)
+    @tag_test(TestTags.SLOW, TestTags.SOURCEONLY)
     def test_plugin_priority(self):
         """Verify that preferring plugin to be used works as expected"""
 
@@ -181,7 +181,7 @@ class LibraryOpsTestCase(unittest.TestCase):
         os.system ("rm -f src/plugins/.libs/libbd_lvm2.so")
 
     # recompiles the LVM plugin
-    @tag_test(TestTags.SLOW)
+    @tag_test(TestTags.SLOW, TestTags.SOURCEONLY)
     def test_plugin_fallback(self):
         """Verify that fallback when loading plugins works as expected"""
 
diff --git a/tests/run_tests.py b/tests/run_tests.py
index b67c2e7..7df9e7d 100644
--- a/tests/run_tests.py
+++ b/tests/run_tests.py
@@ -61,6 +61,8 @@ def _get_test_tags(test):
         tags.append(TestTags.EXTRADEPS)
     if getattr(test_fn, "regression", False) or getattr(test_fn.__self__, "regression", False):
         tags.append(TestTags.REGRESSION)
+    if getattr(test_fn, "sourceonly", False) or getattr(test_fn.__self__, "sourceonly", False):
+        tags.append(TestTags.SOURCEONLY)
 
     return tags
 
diff --git a/tests/utils.py b/tests/utils.py
index 82b5494..df8e787 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -351,6 +351,7 @@ class TestTags(Enum):
     NOSTORAGE = 5   # tests that don't work with storage
     EXTRADEPS = 6   # tests that require special configuration and/or device to run
     REGRESSION = 7  # regression tests
+    SOURCEONLY = 8  # tests that can't run against installed library
 
 
 def tag_test(*tags):
@@ -362,6 +363,7 @@ def tag_test(*tags):
         func.nostorage = TestTags.NOSTORAGE in tags
         func.extradeps = TestTags.EXTRADEPS in tags
         func.regression = TestTags.REGRESSION in tags
+        func.sourceonly = TestTags.SOURCEONLY in tags
 
         return func
 
-- 
2.20.1


From 74a6630db44bd141b76aec80c4eb81fa15dea593 Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Thu, 4 Apr 2019 11:55:32 +0200
Subject: [PATCH 07/10] Skip "source only" tests when running against installed
 version

---
 tests/run_tests.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/tests/run_tests.py b/tests/run_tests.py
index 7df9e7d..4244c06 100644
--- a/tests/run_tests.py
+++ b/tests/run_tests.py
@@ -129,6 +129,8 @@ def _print_skip_message(test, skip_tag):
         reason = "skipping test that requires special configuration"
     elif skip_tag == TestTags.CORE:
         reason = "skipping non-core test"
+    elif skip_tag == TestTags.SOURCEONLY:
+        reason = "skipping test that can run only against library compiled from source"
     else:
         reason = "unknown reason"  # just to be sure there is some default value
 
@@ -198,6 +200,9 @@ if __name__ == '__main__':
         if TestTags.EXTRADEPS in tags and not args.jenkins:
             _print_skip_message(test, TestTags.EXTRADEPS)
             continue
+        if TestTags.SOURCEONLY in tags and args.installed:
+            _print_skip_message(test, TestTags.SOURCEONLY)
+            continue
 
         if args.core and TestTags.CORE not in tags and TestTags.REGRESSION not in tags:
             _print_skip_message(test, TestTags.CORE)
-- 
2.20.1


From d19f2508dbfb00473b21ab1bd6f5603aec66bf4e Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Thu, 4 Apr 2019 14:26:22 +0200
Subject: [PATCH 08/10] Force LVM cli plugin in lvm_test

We can't rely on LVM cli plugin being default in config when
running against installed library.
---
 tests/lvm_test.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/tests/lvm_test.py b/tests/lvm_test.py
index 28a4b05..0b2c5ad 100644
--- a/tests/lvm_test.py
+++ b/tests/lvm_test.py
@@ -12,10 +12,14 @@ from gi.repository import BlockDev, GLib
 
 
 class LVMTestCase(unittest.TestCase):
-    requested_plugins = BlockDev.plugin_specs_from_names(("lvm",))
 
     @classmethod
     def setUpClass(cls):
+        ps = BlockDev.PluginSpec()
+        ps.name = BlockDev.Plugin.LVM
+        ps.so_name = "libbd_lvm.so"
+        cls.requested_plugins = [ps]
+
         if not BlockDev.is_initialized():
             BlockDev.init(cls.requested_plugins, None)
         else:
-- 
2.20.1


From f15a7428382d5ee086ed13755b3ba1f8705b79cf Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Thu, 6 Sep 2018 10:27:52 +0200
Subject: [PATCH 09/10] Fix how we check zram stats from /sys/block/zram0/stat

There are four new stats since kernel 4.19. Checking if we read
more than 11 values should be enough to be sure that the file
has the stats we want.
---
 tests/kbd_test.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/kbd_test.py b/tests/kbd_test.py
index 5e872c4..23b63c9 100644
--- a/tests/kbd_test.py
+++ b/tests/kbd_test.py
@@ -192,7 +192,7 @@ class KbdZRAMStatsTestCase(KbdZRAMTestCase):
 
         # read 'num_reads' and 'num_writes' from '/sys/block/zram0/stat'
         sys_stats = read_file("/sys/block/zram0/stat").strip().split()
-        self.assertEqual(len(sys_stats), 11)
+        self.assertGreaterEqual(len(sys_stats), 11)  # 15 stats since 4.19
         num_reads = int(sys_stats[0])
         num_writes = int(sys_stats[4])
         self.assertEqual(stats.num_reads, num_reads)
-- 
2.20.1


From 7e8f6ef031fe3e5e0b117d910cbf8de36f5bd75e Mon Sep 17 00:00:00 2001
From: Vojtech Trefny <vtrefny@redhat.com>
Date: Wed, 10 Apr 2019 07:51:48 +0200
Subject: [PATCH 10/10] Mark 'test_set_bitmap_location' as unstable

This test randomly fails with error message from mdadm:
"mdadm: failed to remove internal bitmap".
---
 tests/mdraid_test.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/mdraid_test.py b/tests/mdraid_test.py
index ea489db..14d0bd4 100644
--- a/tests/mdraid_test.py
+++ b/tests/mdraid_test.py
@@ -520,7 +520,7 @@ class MDTestNameNodeBijection(MDTestCase):
         self.assertTrue(succ)
 
 class MDTestSetBitmapLocation(MDTestCase):
-    @tag_test(TestTags.SLOW)
+    @tag_test(TestTags.SLOW, TestTags.UNSTABLE)
     def test_set_bitmap_location(self):
         """Verify we can change bitmap location for an existing MD array"""
 
-- 
2.20.1